diff --git a/src/components/screens/atoms/HelpIconHeader.tsx b/src/components/screens/atoms/HelpIconHeader.tsx new file mode 100644 index 0000000..5283d23 --- /dev/null +++ b/src/components/screens/atoms/HelpIconHeader.tsx @@ -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 = ({ title, tooltipContent }) => { + const classes = useStyles(); + + return ( + + {title} + + + + + ); +}; diff --git a/src/components/screens/molecules/correlation-heatmap-chart.tsx b/src/components/screens/molecules/correlation-heatmap-chart.tsx index 429e8c1..cde1cb4 100644 --- a/src/components/screens/molecules/correlation-heatmap-chart.tsx +++ b/src/components/screens/molecules/correlation-heatmap-chart.tsx @@ -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}`, @@ -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, }, ], diff --git a/src/components/screens/molecules/fear-greed-index-gaugeChart.tsx b/src/components/screens/molecules/fear-greed-index-gaugeChart.tsx index 775fbb1..1038f8f 100644 --- a/src/components/screens/molecules/fear-greed-index-gaugeChart.tsx +++ b/src/components/screens/molecules/fear-greed-index-gaugeChart.tsx @@ -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} /> ); diff --git a/src/components/screens/molecules/history-fear-greed-chart.tsx b/src/components/screens/molecules/history-fear-greed-chart.tsx new file mode 100644 index 0000000..2206630 --- /dev/null +++ b/src/components/screens/molecules/history-fear-greed-chart.tsx @@ -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 ? ( + + ) : ( + + + + + + + + + + dataMin * 0.95, (dataMax: number) => dataMax * 1.05]} hide /> + { + if (active && payload && payload.length) { + return ( + + {convertTimestamp(label * 1000, true)} + + {payload[0].payload.classification} ({payload[0].value}%) + + + ); + } else { + return null; + } + }} + /> + + + + )} + + ); +}; diff --git a/src/components/screens/organisms/fear-greed-index-card.tsx b/src/components/screens/organisms/fear-greed-index-card.tsx index 8dc059a..6ccf804 100644 --- a/src/components/screens/organisms/fear-greed-index-card.tsx +++ b/src/components/screens/organisms/fear-greed-index-card.tsx @@ -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 = () => { @@ -48,35 +40,40 @@ export const FearGreedIndexCard: React.FunctionComponent = () => { - Fear & Greed Index{" "} - - - Data Sources Breakdown: - - - Price Volatility (25%) - - Market Momentum / Volume (25%) - - Social Media (15%) - - Surveys (15%) - - Bitcoin Dominance (10%) - - Google Trends (10%) - - } - classes={{ - tooltip: classes.customTooltip, - }} - > - - - + + + Data Sources Breakdown: + + + - Price Volatility (25%) + + + - Market Momentum / Volume (25%) + + + - Social Media (15%) + + + - Surveys (15%) + + + - Bitcoin Dominance (10%) + + + - Google Trends (10%) + + + } + /> } subheader={ - fearGreedIndex.value.length === 0 ? ( + fearGreedIndex.today === null ? ( ) : ( - `Now: ${fearGreedIndex.value[0].valueClassification}` + `Now: ${fearGreedIndex.today.valueClassification}` ) } subheaderTypographyProps={{ variant: "h6", color: "textPrimary" }} @@ -86,8 +83,11 @@ export const FearGreedIndexCard: React.FunctionComponent = () => { } /> - + + + + ); diff --git a/src/features/fear-greed-index-slice.ts b/src/features/fear-greed-index-slice.ts index 7b6def9..93770b1 100644 --- a/src/features/fear-greed-index-slice.ts +++ b/src/features/fear-greed-index-slice.ts @@ -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 = { +const initialState: FearGreedIndexState = { value: [], + today: null, status: "IDLE", }; @@ -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, }); @@ -26,7 +27,7 @@ export const fetchFearGreedIndex = createAsyncThunk("fearGreedIndex", async () = return normalizedResponse.data as FearGreedIndex[]; }); -const fearGreedIndexSlice: Slice, {}, "fearGreedIndex"> = createSlice({ +const fearGreedIndexSlice: Slice = createSlice({ name: "fearGreedIndex", initialState, reducers: {}, @@ -38,6 +39,7 @@ const fearGreedIndexSlice: Slice, {}, "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"; @@ -46,7 +48,7 @@ const fearGreedIndexSlice: Slice, {}, "fearGreedI }, }); -export const selectFearGreedIndex: (state: RootState) => GenericState = (state: RootState) => +export const selectFearGreedIndex: (state: RootState) => FearGreedIndexState = (state: RootState) => state.fearGreedIndex; export default fearGreedIndexSlice.reducer; diff --git a/src/models/api/fear-greed-index.ts b/src/models/api/fear-greed-index.ts index 8c87649..6bbbf6c 100644 --- a/src/models/api/fear-greed-index.ts +++ b/src/models/api/fear-greed-index.ts @@ -1,3 +1,5 @@ +import { GenericState } from "@/src/models"; + export interface FearGreedIndex { value: string; valueClassification: string; @@ -14,3 +16,7 @@ export interface FearGreedIndexRootObject { data: FearGreedIndex[]; metadata: FearGreedIndexMetadata; } + +export interface FearGreedIndexState extends GenericState { + today: FearGreedIndex | null; +} diff --git a/src/models/api/gas-oracle.ts b/src/models/api/gas-oracle.ts index b8c0812..3deba36 100644 --- a/src/models/api/gas-oracle.ts +++ b/src/models/api/gas-oracle.ts @@ -7,13 +7,13 @@ export interface GasOracle { fastGasPrice: string; } -export interface GasOracleState extends GenericState { - selectedGasFee: string | null; - gasLimit: number; -} - export interface GasOracleRootObject { status: string; message: string; result: GasOracle; } + +export interface GasOracleState extends GenericState { + selectedGasFee: string | null; + gasLimit: number; +} diff --git a/src/models/index.ts b/src/models/index.ts index 8262345..382cc8a 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -13,6 +13,7 @@ import { FearGreedIndex as FearGreedIndexInterface, FearGreedIndexMetadata as FearGreedIndexMetadataInterface, FearGreedIndexRootObject as FearGreedIndexRootObjectInterface, + FearGreedIndexState as FearGreedIndexStateInterface, } from "./api/fear-greed-index"; import { GasOracle as GasOracleInterface, @@ -44,6 +45,7 @@ export type MarketCapPercentage = MarketCapPercentageInterface; export type CoinMarketChartListState = CoinMarketChartListStateInterface; export type FearGreedIndex = FearGreedIndexInterface; export type FearGreedIndexMetadata = FearGreedIndexMetadataInterface; +export type FearGreedIndexState = FearGreedIndexStateInterface; export type FearGreedIndexRootObject = FearGreedIndexRootObjectInterface; export type Page = PageInterface; export type RootModule = RootModuleInterface;