Skip to content

Commit

Permalink
Completed fear-greed card component
Browse files Browse the repository at this point in the history
  • Loading branch information
zourdyzou committed Oct 17, 2022
1 parent e372868 commit 0241208
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 60 deletions.
44 changes: 44 additions & 0 deletions src/components/screens/atoms/HelpIconHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react";
import { Tooltip, Typography } from "@material-ui/core";
import { makeStyles, Theme } from "@material-ui/core/styles";
import { HelpOutlineRounded } from "@material-ui/icons";

const useStyles = makeStyles((theme: Theme) => ({
header: {
display: "flex",
alignItems: "center",
"& .MuiSvgIcon-root": {
cursor: "pointer",
marginLeft: 6,
height: 16,
width: 16,
},
},
customTooltip: {
backgroundColor: theme.palette.background.default,
borderRadius: 12,
},
}));

interface Props {
title: string;
tooltipContent: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal;
}

export const HelpIconHeader: React.FunctionComponent<Props> = ({ title, tooltipContent }) => {
const classes = useStyles();

return (
<Typography className={classes.header} variant="body2" color="textSecondary">
{title}
<Tooltip
title={tooltipContent}
classes={{
tooltip: classes.customTooltip,
}}
>
<HelpOutlineRounded />
</Tooltip>
</Typography>
);
};
11 changes: 7 additions & 4 deletions src/components/screens/molecules/correlation-heatmap-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export const CorrelationHeatmap: React.FunctionComponent = () => {
colors: ["#000000"],
xaxis: {
categories: coins.value.slice(0, 15).map((coin: Coin) => coin.symbol.toUpperCase()),
tooltip: {
enabled: false,
},
labels: {
style: {
fontSize: `${theme.typography.subtitle2.fontSize}`,
Expand All @@ -154,25 +157,25 @@ export const CorrelationHeatmap: React.FunctionComponent = () => {
{
from: -100,
to: 9.999999999,
name: "Almost No Correlation (<10)",
name: "Almost None (<10)",
color: theme.palette.success.main,
},
{
from: 10,
to: 69.999999999,
name: "Medium Correlation (<70)",
name: "Medium (<70)",
color: theme.palette.info.main,
},
{
from: 70,
to: 84.999999999,
name: "High Correlation (<85)",
name: "High (<85)",
color: theme.palette.warning.main,
},
{
from: 85,
to: 99.999999999,
name: "Extreme Correlation (<99.999)",
name: "Extreme (<99.999)",
color: theme.palette.error.main,
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@ export const FearGreedIndexGaugeChart: React.FC = () => {
id="fear-greed-index-gauge"
nrOfLevels={20}
colors={[theme.palette.error.main, theme.palette.success.main]}
percent={
fearGreedIndex.value.length === 0 || fearGreedIndex.status === "LOADING"
? 0
: Number(fearGreedIndex.value[0].value) / 100
}
percent={fearGreedIndex.today === null ? 0 : Number(fearGreedIndex.today.value) / 100}
/>
</Box>
);
Expand Down
98 changes: 98 additions & 0 deletions src/components/screens/molecules/history-fear-greed-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from "react";
import { Box, Typography, useTheme } from "@material-ui/core";
import { makeStyles, Theme } from "@material-ui/core/styles";
import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";

import { convertTimestamp } from "@/common/helpers/date-handler";
import { ChartSkeleton } from "@/components/screens/atoms/chart-skeleton-loader";
import { selectFearGreedIndex } from "@/features/fear-greed-index-slice";
import { useAppSelector } from "@/hooks/*";
import { FearGreedIndex } from "@/src/models";

const useStyles = makeStyles((theme: Theme) => ({
responsiveContainer: {
"& .recharts-surface": {
cursor: "pointer",
},
},
customTooltip: {
borderRadius: 12,
padding: 12,
backgroundColor: `${theme.palette.background.default}dd`,
"& .MuiTypography-root:not(:first-child)": {
color: theme.palette.secondary.main,
},
},
}));

interface DataFormat {
date: number;
value: number;
classification: string;
}

export const HistoricFearGreedIndexChart: React.FunctionComponent = () => {
const classes = useStyles();
const theme = useTheme();

const fearGreedIndex = useAppSelector(selectFearGreedIndex);

const formatRawData = () => {
const chartData: DataFormat[] = [];
fearGreedIndex.value.forEach((indexData: FearGreedIndex) => {
chartData.push({
date: Number(indexData.timestamp),
value: Number(indexData.value),
classification: indexData.valueClassification,
});
});
return chartData;
};

return (
<>
{fearGreedIndex.value.length === 0 ? (
<ChartSkeleton />
) : (
<ResponsiveContainer height="100%" width="100%" className={classes.responsiveContainer}>
<AreaChart data={formatRawData()} margin={{ top: 8, right: 8, left: 8, bottom: 8 }}>
<defs>
<linearGradient id="chart-gradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={theme.palette.primary.main} stopOpacity={0.3} />
<stop offset="100%" stopColor={theme.palette.primary.main} stopOpacity={0} />
</linearGradient>
</defs>
<XAxis dataKey="date" hide />
<YAxis domain={[(dataMin: number) => dataMin * 0.95, (dataMax: number) => dataMax * 1.05]} hide />
<Tooltip
content={({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
<Box className={classes.customTooltip}>
<Typography variant="body1">{convertTimestamp(label * 1000, true)}</Typography>
<Typography variant="body2">
{payload[0].payload.classification} ({payload[0].value}%)
</Typography>
</Box>
);
} else {
return null;
}
}}
/>
<Area
type="monotone"
dataKey="value"
dot={false}
animationDuration={3000}
strokeWidth={2}
stroke={theme.palette.primary.main}
fillOpacity={1}
fill={`url(#chart-gradient)`}
/>
</AreaChart>
</ResponsiveContainer>
)}
</>
);
};
82 changes: 41 additions & 41 deletions src/components/screens/organisms/fear-greed-index-card.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,27 @@
import React, { useEffect } from "react";
import { Avatar, CardContent, CardHeader, Tooltip, Typography } from "@material-ui/core";
import { Avatar, Box, CardContent, CardHeader, Typography } from "@material-ui/core";
import { makeStyles, Theme } from "@material-ui/core/styles";
import { FaceRounded, HelpOutlineRounded } from "@material-ui/icons";
import { FaceRounded } from "@material-ui/icons";
import { Skeleton } from "@material-ui/lab";

import { HelpIconHeader } from "@/components/screens/atoms/HelpIconHeader";
import { MappedSentimentIcon } from "@/components/screens/atoms/mapped-sentiment-icon";
import { CardLayout } from "@/components/screens/molecules/card-layout";
import { FearGreedIndexGaugeChart } from "@/components/screens/molecules/fear-greed-index-gaugeChart";
import { HistoricFearGreedIndexChart } from "@/components/screens/molecules/history-fear-greed-chart";
import { fetchFearGreedIndex, selectFearGreedIndex } from "@/features/fear-greed-index-slice";
import { useAppDispatch, useAppSelector } from "@/hooks/*";

const useStyles = makeStyles((theme: Theme) => ({
header: {
display: "flex",
alignItems: "center",
"& .MuiSvgIcon-root": {
cursor: "pointer",
marginLeft: 6,
height: 16,
width: 16,
},
},
customTooltip: {
backgroundColor: theme.palette.background.default,
},
avatarColor: {
marginRight: 6,
color: theme.palette.warning.main,
backgroundColor: theme.palette.card.paper,
borderRadius: 8,
},
contentWrapper: {
height: "calc(100% - 84px) !important",
},
}));

export const FearGreedIndexCard: React.FunctionComponent = () => {
Expand All @@ -48,35 +40,40 @@ export const FearGreedIndexCard: React.FunctionComponent = () => {
<CardLayout>
<CardHeader
title={
<Typography className={classes.header} variant="body2" color="textSecondary">
Fear & Greed Index{" "}
<Tooltip
title={
<div>
<Typography variant="subtitle1" color="primary">
Data Sources Breakdown:
</Typography>
<Typography variant="body2">- Price Volatility (25%)</Typography>
<Typography variant="body2">- Market Momentum / Volume (25%)</Typography>
<Typography variant="body2">- Social Media (15%)</Typography>
<Typography variant="body2">- Surveys (15%)</Typography>
<Typography variant="body2">- Bitcoin Dominance (10%)</Typography>
<Typography variant="body2">- Google Trends (10%)</Typography>
</div>
}
classes={{
tooltip: classes.customTooltip,
}}
>
<HelpOutlineRounded />
</Tooltip>
</Typography>
<HelpIconHeader
title="Fear & Greed Index"
tooltipContent={
<div>
<Typography variant="subtitle1" color="secondary">
Data Sources Breakdown:
</Typography>
<Typography variant="body2" color="textSecondary">
- Price Volatility (25%)
</Typography>
<Typography variant="body2" color="textSecondary">
- Market Momentum / Volume (25%)
</Typography>
<Typography variant="body2" color="textSecondary">
- Social Media (15%)
</Typography>
<Typography variant="body2" color="textSecondary">
- Surveys (15%)
</Typography>
<Typography variant="body2" color="textSecondary">
- Bitcoin Dominance (10%)
</Typography>
<Typography variant="body2" color="textSecondary">
- Google Trends (10%)
</Typography>
</div>
}
/>
}
subheader={
fearGreedIndex.value.length === 0 ? (
fearGreedIndex.today === null ? (
<Skeleton animation="wave" height={32} width={150} />
) : (
`Now: ${fearGreedIndex.value[0].valueClassification}`
`Now: ${fearGreedIndex.today.valueClassification}`
)
}
subheaderTypographyProps={{ variant: "h6", color: "textPrimary" }}
Expand All @@ -86,8 +83,11 @@ export const FearGreedIndexCard: React.FunctionComponent = () => {
</Avatar>
}
/>
<CardContent>
<CardContent className={classes.contentWrapper}>
<FearGreedIndexGaugeChart />
<Box height="calc(100% - 96px)" width="100%">
<HistoricFearGreedIndexChart />
</Box>
</CardContent>
</CardLayout>
);
Expand Down
12 changes: 7 additions & 5 deletions src/features/fear-greed-index-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { API_CONFIG as config } from "@/common/constants";
import { alternativeMe as API } from "@/common/endpoints";
import { toCamelCase } from "@/common/helpers/case-transformer";
import { RootState } from "@/components/app/store";
import { FearGreedIndex, FearGreedIndexRootObject, GenericState } from "@/src/models";
import { FearGreedIndex, FearGreedIndexRootObject, FearGreedIndexState } from "@/src/models";

const initialState: GenericState<FearGreedIndex[]> = {
const initialState: FearGreedIndexState = {
value: [],
today: null,
status: "IDLE",
};

Expand All @@ -17,7 +18,7 @@ export const fetchFearGreedIndex = createAsyncThunk("fearGreedIndex", async () =

const response = await axios.request({
...config("alternative.me"),
url: API.fearGreedIndex(7),
url: API.fearGreedIndex(30),
cancelToken: canceler.token,
});

Expand All @@ -26,7 +27,7 @@ export const fetchFearGreedIndex = createAsyncThunk("fearGreedIndex", async () =
return normalizedResponse.data as FearGreedIndex[];
});

const fearGreedIndexSlice: Slice<GenericState<FearGreedIndex[]>, {}, "fearGreedIndex"> = createSlice({
const fearGreedIndexSlice: Slice<FearGreedIndexState, {}, "fearGreedIndex"> = createSlice({
name: "fearGreedIndex",
initialState,
reducers: {},
Expand All @@ -38,6 +39,7 @@ const fearGreedIndexSlice: Slice<GenericState<FearGreedIndex[]>, {}, "fearGreedI
.addCase(fetchFearGreedIndex.fulfilled, (state, action) => {
state.status = "IDLE";
state.value = action.payload;
state.today = action.payload[action.payload.length - 1];
})
.addCase(fetchFearGreedIndex.rejected, (state, action) => {
state.status = "FAILED";
Expand All @@ -46,7 +48,7 @@ const fearGreedIndexSlice: Slice<GenericState<FearGreedIndex[]>, {}, "fearGreedI
},
});

export const selectFearGreedIndex: (state: RootState) => GenericState<FearGreedIndex[]> = (state: RootState) =>
export const selectFearGreedIndex: (state: RootState) => FearGreedIndexState = (state: RootState) =>
state.fearGreedIndex;

export default fearGreedIndexSlice.reducer;
6 changes: 6 additions & 0 deletions src/models/api/fear-greed-index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { GenericState } from "@/src/models";

export interface FearGreedIndex {
value: string;
valueClassification: string;
Expand All @@ -14,3 +16,7 @@ export interface FearGreedIndexRootObject {
data: FearGreedIndex[];
metadata: FearGreedIndexMetadata;
}

export interface FearGreedIndexState extends GenericState<FearGreedIndex[]> {
today: FearGreedIndex | null;
}
Loading

0 comments on commit 0241208

Please sign in to comment.