From 524aace6332fcfbe044aa45d2a8468371d5312ed Mon Sep 17 00:00:00 2001 From: toniocodo Date: Mon, 25 Sep 2023 08:59:35 +0200 Subject: [PATCH 1/6] feat: add date-fns and chart js adapter --- package.json | 2 ++ pnpm-lock.yaml | 35 ++++++++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index e8fd301f1..5bfd3eb8a 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "@wagmi/core": "^1.4.1", "axios": "^1.5.0", "chart.js": "^4.4.0", + "chartjs-adapter-date-fns": "^3.0.0", + "date-fns": "^2.30.0", "graphql": "^16.8.0", "immer": "^10.0.2", "ramda": "^0.29.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de0bdc9ab..54e8e07a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,12 @@ dependencies: chart.js: specifier: ^4.4.0 version: 4.4.0 + chartjs-adapter-date-fns: + specifier: ^3.0.0 + version: 3.0.0(chart.js@4.4.0)(date-fns@2.30.0) + date-fns: + specifier: ^2.30.0 + version: 2.30.0 graphql: specifier: ^16.8.0 version: 16.8.0 @@ -1703,12 +1709,6 @@ packages: resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} dev: true - /@babel/runtime@7.22.10: - resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.0 - /@babel/runtime@7.22.15: resolution: {integrity: sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==} engines: {node: '>=6.9.0'} @@ -1833,7 +1833,7 @@ packages: resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: '@babel/helper-module-imports': 7.22.15 - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.22.15 '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/serialize': 1.1.2 @@ -6439,7 +6439,7 @@ packages: engines: {node: '>=14'} dependencies: '@babel/code-frame': 7.22.13 - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.22.15 '@types/aria-query': 5.0.1 aria-query: 5.1.3 chalk: 4.1.2 @@ -8425,7 +8425,7 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.22.15 cosmiconfig: 7.1.0 resolve: 1.22.4 dev: false @@ -8885,6 +8885,16 @@ packages: '@kurkle/color': 0.3.2 dev: false + /chartjs-adapter-date-fns@3.0.0(chart.js@4.4.0)(date-fns@2.30.0): + resolution: {integrity: sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==} + peerDependencies: + chart.js: '>=2.8.0' + date-fns: '>=2.0.0' + dependencies: + chart.js: 4.4.0 + date-fns: 2.30.0 + dev: false + /check-error@1.0.2: resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} dev: true @@ -9309,6 +9319,13 @@ packages: resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==} dev: true + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.22.15 + dev: false + /de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} dev: true From 0fbe4dd99f32e122f061d6c0a27612e08df5ae91 Mon Sep 17 00:00:00 2001 From: toniocodo Date: Mon, 25 Sep 2023 08:59:56 +0200 Subject: [PATCH 2/6] chore: move .env back to root --- libs/oeth/shared/.env => .env | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename libs/oeth/shared/.env => .env (100%) diff --git a/libs/oeth/shared/.env b/.env similarity index 100% rename from libs/oeth/shared/.env rename to .env From 70feee17280daa8d15927da4ec4e543dc522a485 Mon Sep 17 00:00:00 2001 From: toniocodo Date: Mon, 25 Sep 2023 09:01:47 +0200 Subject: [PATCH 3/6] feat: add ApyHeader component, add queries --- apps/oeth/src/App.tsx | 17 +- .../history/src/components/APYContainer.tsx | 2 +- .../src/components/ApyHeader.tsx | 115 ++++-- ...ies.generated.tsx => queries.generated.ts} | 39 +- libs/oeth/history/src/queries.graphql | 15 +- libs/oeth/redeem/src/views/RedeemView.tsx | 1 - libs/oeth/shared/codegen.ts | 2 +- .../src/components/{index.tsx => index.ts} | 1 - libs/oeth/swap/src/components/ApyChart.tsx | 348 ++++++++++++++++++ libs/oeth/swap/src/queries.generated.ts | 39 ++ libs/oeth/swap/src/queries.graphql | 8 + libs/oeth/swap/src/views/SwapView.tsx | 5 +- .../providers/src/chart/registerChart.ts | 11 +- libs/shared/theme/src/theme.tsx | 19 + 14 files changed, 563 insertions(+), 59 deletions(-) rename libs/oeth/{shared => history}/src/components/ApyHeader.tsx (70%) rename libs/oeth/history/src/{queries.generated.tsx => queries.generated.ts} (72%) rename libs/oeth/shared/src/components/{index.tsx => index.ts} (68%) create mode 100644 libs/oeth/swap/src/components/ApyChart.tsx create mode 100644 libs/oeth/swap/src/queries.generated.ts create mode 100644 libs/oeth/swap/src/queries.graphql diff --git a/apps/oeth/src/App.tsx b/apps/oeth/src/App.tsx index 95a4c00dc..eb4223a4c 100644 --- a/apps/oeth/src/App.tsx +++ b/apps/oeth/src/App.tsx @@ -1,10 +1,23 @@ import { Container, CssBaseline, Stack } from '@mui/material'; -import { registerChart } from '@origin/shared/providers'; +import { + CategoryScale, + Chart as ChartJS, + LinearScale, + LineElement, + PointElement, + registerables, +} from 'chart.js'; import { Outlet } from 'react-router-dom'; import { Topnav } from './components/Topnav'; -registerChart(); +ChartJS.register( + ...registerables, + CategoryScale, + LinearScale, + LineElement, + PointElement, +); export const App = () => { return ( diff --git a/libs/oeth/history/src/components/APYContainer.tsx b/libs/oeth/history/src/components/APYContainer.tsx index f3a05dfcc..cb0ae3ce3 100644 --- a/libs/oeth/history/src/components/APYContainer.tsx +++ b/libs/oeth/history/src/components/APYContainer.tsx @@ -8,7 +8,7 @@ import { useHistoryTableQuery } from '../queries.generated'; export function APYContainer() { const { address, isConnected } = useAccount(); const { data } = useHistoryTableQuery( - { addressId: address?.toLowerCase(), offset: 0 }, + { address: address?.toLowerCase(), offset: 0 }, { enabled: isConnected, }, diff --git a/libs/oeth/shared/src/components/ApyHeader.tsx b/libs/oeth/history/src/components/ApyHeader.tsx similarity index 70% rename from libs/oeth/shared/src/components/ApyHeader.tsx rename to libs/oeth/history/src/components/ApyHeader.tsx index 65ecd5595..1495ae331 100644 --- a/libs/oeth/shared/src/components/ApyHeader.tsx +++ b/libs/oeth/history/src/components/ApyHeader.tsx @@ -7,14 +7,20 @@ import { IconButton, Menu, MenuItem, + Skeleton, Stack, Typography, } from '@mui/material'; import { Icon } from '@origin/shared/components'; import { tokens } from '@origin/shared/contracts'; +import { formatAmount } from '@origin/shared/utils'; import { useIntl } from 'react-intl'; import { useAccount, useBalance } from 'wagmi'; +import { useHistoryApyQuery, useHistoryTableQuery } from '../queries.generated'; + +import type { BoxProps } from '@mui/material'; + const days = [7, 30]; export function ApyHeader() { @@ -24,10 +30,16 @@ export function ApyHeader() { const { address } = useAccount(); const { data: oethBalance } = useBalance({ address, - token: tokens.mainnet.OUSD.address, + token: tokens.mainnet.OETH.address, watch: true, }); + const { data: apy, isLoading: apyLoading } = useHistoryApyQuery(); + const { data: earnings, isLoading: earningsLoading } = useHistoryTableQuery({ + address: address?.toLowerCase(), + offset: 0, + }); + const handleClose = () => { setAnchorEl(null); }; @@ -94,24 +106,32 @@ export function ApyHeader() { alignItems="center" sx={{ justifyContent: { xs: 'center', md: 'flex-start' } }} > - - {intl.formatNumber(6.71 / 100, { - minimumFractionDigits: 2, - style: 'percent', - })} - + {apyLoading ? ( + + ) : ( + + {intl.formatNumber( + selectedPeriod === 30 + ? apy.apies[0].apy30DayAvg + : apy.apies[0].apy7DayAvg, + { + minimumFractionDigits: 2, + }, + )} + + )} setAnchorEl(e.currentTarget)} @@ -144,9 +164,7 @@ export function ApyHeader() { > @@ -185,17 +206,25 @@ export function ApyHeader() { ); } +type ValueContainerProps = { + text: string; + value: string; + icon?: string; + isLoading?: boolean; +} & BoxProps; + function ValueContainer({ text, value, icon, -}: { - text: string; - value: string; - icon?: string; -}) { + isLoading, + ...rest +}: ValueContainerProps) { return ( - + {text} @@ -216,18 +245,24 @@ function ValueContainer({ }, }} > - {icon ? ( - - ) : undefined} + {isLoading ? ( + + ) : ( + <> + {icon ? ( + + ) : undefined} - {value} + {value} + + )} ); diff --git a/libs/oeth/history/src/queries.generated.tsx b/libs/oeth/history/src/queries.generated.ts similarity index 72% rename from libs/oeth/history/src/queries.generated.tsx rename to libs/oeth/history/src/queries.generated.ts index 0082ab230..d1721cfd2 100644 --- a/libs/oeth/history/src/queries.generated.tsx +++ b/libs/oeth/history/src/queries.generated.ts @@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query'; import type * as Types from '@origin/oeth/shared'; import type { UseQueryOptions } from '@tanstack/react-query'; export type HistoryTableQueryVariables = Types.Exact<{ - addressId: Types.Scalars['String']['input']; + address: Types.Scalars['String']['input']; offset: Types.Scalars['Int']['input']; }>; @@ -29,7 +29,7 @@ export type HistoryTableQuery = { }; export type HistoryTableWithFiltersQueryVariables = Types.Exact<{ - addressId: Types.Scalars['String']['input']; + address: Types.Scalars['String']['input']; offset: Types.Scalars['Int']['input']; filters?: Types.InputMaybe< Array | Types.Scalars['String']['input'] @@ -56,9 +56,16 @@ export type HistoryTableWithFiltersQuery = { } | null; }; +export type HistoryApyQueryVariables = Types.Exact<{ [key: string]: never }>; + +export type HistoryApyQuery = { + __typename?: 'Query'; + apies: Array<{ __typename?: 'APY'; apy7DayAvg: number; apy30DayAvg: number }>; +}; + export const HistoryTableDocument = ` - query HistoryTable($addressId: String!, $offset: Int!) { - addressById(id: $addressId) { + query HistoryTable($address: String!, $offset: Int!) { + addressById(id: $address) { balance earned isContract @@ -90,8 +97,8 @@ export const useHistoryTableQuery = < options, ); export const HistoryTableWithFiltersDocument = ` - query HistoryTableWithFilters($addressId: String!, $offset: Int!, $filters: [String!]) { - addressById(id: $addressId) { + query HistoryTableWithFilters($address: String!, $offset: Int!, $filters: [String!]) { + addressById(id: $address) { balance earned isContract @@ -127,3 +134,23 @@ export const useHistoryTableWithFiltersQuery = < >(HistoryTableWithFiltersDocument, variables), options, ); +export const HistoryApyDocument = ` + query HistoryApy { + apies(limit: 1, orderBy: timestamp_DESC) { + apy7DayAvg + apy30DayAvg + } +} + `; +export const useHistoryApyQuery = ( + variables?: HistoryApyQueryVariables, + options?: UseQueryOptions, +) => + useQuery( + variables === undefined ? ['HistoryApy'] : ['HistoryApy', variables], + graphqlClient( + HistoryApyDocument, + variables, + ), + options, + ); diff --git a/libs/oeth/history/src/queries.graphql b/libs/oeth/history/src/queries.graphql index 676841d7e..52741cbcb 100644 --- a/libs/oeth/history/src/queries.graphql +++ b/libs/oeth/history/src/queries.graphql @@ -1,5 +1,5 @@ -query HistoryTable($addressId: String!, $offset: Int!) { - addressById(id: $addressId) { +query HistoryTable($address: String!, $offset: Int!) { + addressById(id: $address) { balance earned isContract @@ -16,11 +16,11 @@ query HistoryTable($addressId: String!, $offset: Int!) { } query HistoryTableWithFilters( - $addressId: String! + $address: String! $offset: Int! $filters: [String!] ) { - addressById(id: $addressId) { + addressById(id: $address) { balance earned isContract @@ -40,3 +40,10 @@ query HistoryTableWithFilters( } } } + +query HistoryApy { + apies(limit: 1, orderBy: timestamp_DESC) { + apy7DayAvg + apy30DayAvg + } +} diff --git a/libs/oeth/redeem/src/views/RedeemView.tsx b/libs/oeth/redeem/src/views/RedeemView.tsx index 02603585a..bac846849 100644 --- a/libs/oeth/redeem/src/views/RedeemView.tsx +++ b/libs/oeth/redeem/src/views/RedeemView.tsx @@ -78,7 +78,6 @@ function RedeemViewWrapped() { return ( { + const intl = useIntl(); + const theme = useTheme(); + const chartRef = useRef>(null); + const [apy, setApy] = useState(0); + const [timestamp, setTimestamp] = useState(null); + const [limit, setLimit] = useState(limitOptions[0]); + const [trailing, setTrailing] = useState(trailingOptions[0]); + const [anchorEl, setAnchorEl] = useState(null); + const { data: apies, isLoading: apiesLoading } = useApiesQuery( + { + limit: limit.value, + }, + { + select: (data) => sort(ascend(prop('timestamp')), data.apies), + }, + ); + + useEffect(() => { + if (!isNilOrEmpty(apies)) { + setApy( + trailing.value === 30 + ? last(apies).apy30DayAvg + : last(apies).apy7DayAvg, + ); + setTimestamp(last(apies).timestamp); + } + }, [apies, trailing.value]); + + useEffect(() => { + if (chartRef.current) { + chartRef.current.options.scales.x['time'].unit = + isNilOrEmpty(limit.value) || limit.value > 30 ? 'month' : 'day'; + chartRef.current.update(); + } + }, [limit.value]); + + const handleMouseLeave = useCallback(() => { + setApy( + trailing.value === 30 ? last(apies).apy30DayAvg : last(apies).apy7DayAvg, + ); + setTimestamp(last(apies).timestamp); + }, [apies, trailing.value]); + + const hoverline: Plugin = { + id: 'hoverline', + afterDatasetDraw: (chart, args, options) => { + const { + ctx, + tooltip, + chartArea: { top, bottom }, + scales: { x }, + } = chart; + + if ( + !isNilOrEmpty(tooltip?.['_active']) && + !isNilOrEmpty(tooltip?.dataPoints?.[0]?.parsed?.x) + ) { + const xCoor = x.getPixelForValue( + tooltip.dataPoints[0].parsed.x, + tooltip.dataPoints[0].dataIndex, + ); + + ctx.save(); + ctx.beginPath(); + ctx.lineWidth = 1; + ctx.strokeStyle = theme.palette.grey[200]; + ctx.setLineDash([3, 3]); + ctx.moveTo(xCoor, top); + ctx.lineTo(xCoor, bottom); + ctx.stroke(); + ctx.restore(); + } + }, + }; + + const chartData = useMemo>( + () => ({ + datasets: [ + { + data: apies?.map((item) => ({ + x: item.timestamp, + y: trailing.value === 7 ? item.apy7DayAvg : item.apy30DayAvg, + })), + }, + ], + }), + [apies, trailing.value], + ); + + const chartOptions = useMemo>( + () => ({ + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + }, + events: ['mousemove', 'mouseout', 'mouseenter'], + datasets: { + line: { + backgroundColor: 'transparent', + pointRadius: 0, + pointHoverRadius: 0, + borderWidth: 2, + tension: 0.4, + }, + }, + borderColor: (ctx) => { + const gradient = ctx.chart.ctx.createLinearGradient( + 0, + 0, + ctx.chart.width, + ctx.chart.height, + ); + gradient.addColorStop(0, theme.palette.primary.main); + gradient.addColorStop(1, theme.palette.primary.dark); + + return gradient; + }, + layout: { + padding: 0, + }, + scales: { + y: { + display: false, + grid: { + display: false, + }, + }, + x: { + type: 'time', + border: { display: false }, + grid: { + display: false, + }, + ticks: { + align: 'inner', + maxRotation: 0, + padding: 4, + font: { + size: 11, + family: 'Inter', + weight: '400', + }, + }, + }, + }, + title: { + display: false, + }, + plugins: { + legend: { + display: false, + }, + tooltip: { + backgroundColor: 'transparent', + titleColor: 'transparent', + bodyColor: 'transparent', + borderColor: 'transparent', + footerColor: 'transparent', + borderWidth: 0, + padding: 0, + boxHeight: 0, + boxWidth: 0, + mode: 'index', + }, + }, + onHover: (event, elements, chart) => { + if (!isNilOrEmpty(chart?.tooltip?.dataPoints?.[0]?.parsed)) { + setTimestamp(chart.tooltip.dataPoints[0].parsed.x); + setApy(chart.tooltip.dataPoints[0].parsed.y); + } + }, + }), + [theme.palette.primary.dark, theme.palette.primary.main], + ); + + return ( + + + {intl.formatMessage({ defaultMessage: 'APY' })} + + + {limitOptions.map((d) => ( + + ))} + + + } + > + + + + {apiesLoading ? ( + + ) : ( + + {intl.formatNumber(apy, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} + % + + )} + {apiesLoading ? ( + + ) : ( + + {intl.formatDate(new Date(timestamp), { + month: 'short', + day: 'numeric', + year: 'numeric', + })} + + )} + + + + { + setAnchorEl(null); + }} + MenuListProps={{ dense: true }} + > + {trailingOptions.map((t) => ( + { + setTrailing(t); + setAnchorEl(null); + }} + > + {intl.formatMessage(t.label)} + + ))} + + + + + + + + ); +}; diff --git a/libs/oeth/swap/src/queries.generated.ts b/libs/oeth/swap/src/queries.generated.ts new file mode 100644 index 000000000..144ceb398 --- /dev/null +++ b/libs/oeth/swap/src/queries.generated.ts @@ -0,0 +1,39 @@ +import { graphqlClient } from '@origin/oeth/shared'; +import { useQuery } from '@tanstack/react-query'; + +import type * as Types from '@origin/oeth/shared'; +import type { UseQueryOptions } from '@tanstack/react-query'; +export type ApiesQueryVariables = Types.Exact<{ + limit?: Types.InputMaybe; +}>; + +export type ApiesQuery = { + __typename?: 'Query'; + apies: Array<{ + __typename?: 'APY'; + id: string; + timestamp: any; + apy7DayAvg: number; + apy30DayAvg: number; + }>; +}; + +export const ApiesDocument = ` + query Apies($limit: Int) { + apies(limit: $limit, orderBy: timestamp_DESC) { + id + timestamp + apy7DayAvg + apy30DayAvg + } +} + `; +export const useApiesQuery = ( + variables?: ApiesQueryVariables, + options?: UseQueryOptions, +) => + useQuery( + variables === undefined ? ['Apies'] : ['Apies', variables], + graphqlClient(ApiesDocument, variables), + options, + ); diff --git a/libs/oeth/swap/src/queries.graphql b/libs/oeth/swap/src/queries.graphql new file mode 100644 index 000000000..5e2258a0b --- /dev/null +++ b/libs/oeth/swap/src/queries.graphql @@ -0,0 +1,8 @@ +query Apies($limit: Int) { + apies(limit: $limit, orderBy: timestamp_DESC) { + id + timestamp + apy7DayAvg + apy30DayAvg + } +} diff --git a/libs/oeth/swap/src/views/SwapView.tsx b/libs/oeth/swap/src/views/SwapView.tsx index 756fcc0db..a9490a550 100644 --- a/libs/oeth/swap/src/views/SwapView.tsx +++ b/libs/oeth/swap/src/views/SwapView.tsx @@ -10,13 +10,14 @@ import { Stack, Typography, } from '@mui/material'; -import { ApyHeader, GasPopover } from '@origin/oeth/shared'; +import { GasPopover } from '@origin/oeth/shared'; import { Card, TokenInput } from '@origin/shared/components'; import { ConnectedButton, usePrices } from '@origin/shared/providers'; import { composeContexts, isNilOrEmpty } from '@origin/shared/utils'; import { useIntl } from 'react-intl'; import { useAccount, useBalance } from 'wagmi'; +import { ApyChart } from '../components/ApyChart'; import { SwapRoute } from '../components/SwapRoute'; import { TokenSelectModal } from '../components/TokenSelectModal'; import { routeActionLabel } from '../constants'; @@ -154,7 +155,7 @@ function SwapViewWrapped() { return ( <> - + ({ + color: theme.palette.primary.contrastText, '&.Mui-selected': { backgroundColor: 'transparent', color: theme.palette.text.secondary, From c0d9278a9e5688973f515390528c229f0ae6ade4 Mon Sep 17 00:00:00 2001 From: toniocodo Date: Mon, 25 Sep 2023 09:31:20 +0200 Subject: [PATCH 4/6] feat: add rounding to day --- libs/oeth/swap/src/components/ApyChart.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/oeth/swap/src/components/ApyChart.tsx b/libs/oeth/swap/src/components/ApyChart.tsx index 36bf2a2c1..968a89d5e 100644 --- a/libs/oeth/swap/src/components/ApyChart.tsx +++ b/libs/oeth/swap/src/components/ApyChart.tsx @@ -92,10 +92,7 @@ export const ApyChart = (props: StackProps) => { !isNilOrEmpty(tooltip?.['_active']) && !isNilOrEmpty(tooltip?.dataPoints?.[0]?.parsed?.x) ) { - const xCoor = x.getPixelForValue( - tooltip.dataPoints[0].parsed.x, - tooltip.dataPoints[0].dataIndex, - ); + const xCoor = x.getPixelForValue(tooltip.dataPoints[0].parsed.x); ctx.save(); ctx.beginPath(); @@ -165,6 +162,9 @@ export const ApyChart = (props: StackProps) => { }, x: { type: 'time', + time: { + round: 'day', + }, border: { display: false }, grid: { display: false, From 188f9973d7871d49dcd94433064090f38e22b3cf Mon Sep 17 00:00:00 2001 From: toniocodo Date: Mon, 25 Sep 2023 11:40:05 +0200 Subject: [PATCH 5/6] feat: Apy headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add awesome codegen options for generating keys and queryFn - add pending yield hook WIP🚧 - fix history card to use generated hook --- .../history/src/components/APYContainer.tsx | 68 +++-- .../oeth/history/src/components/ApyHeader.tsx | 269 ------------------ .../history/src/components/HistoryCard.tsx | 34 +-- libs/oeth/history/src/hooks.ts | 39 +++ libs/oeth/history/src/queries.generated.ts | 40 +++ libs/oeth/history/src/queries.graphql | 1 + libs/oeth/shared/codegen.ts | 2 + libs/oeth/shared/src/clients/graphql.ts | 7 +- libs/oeth/swap/src/queries.generated.ts | 12 + libs/shared/utils/src/formatters.ts | 5 + 10 files changed, 164 insertions(+), 313 deletions(-) delete mode 100644 libs/oeth/history/src/components/ApyHeader.tsx create mode 100644 libs/oeth/history/src/hooks.ts diff --git a/libs/oeth/history/src/components/APYContainer.tsx b/libs/oeth/history/src/components/APYContainer.tsx index cb0ae3ce3..9e95389ac 100644 --- a/libs/oeth/history/src/components/APYContainer.tsx +++ b/libs/oeth/history/src/components/APYContainer.tsx @@ -1,19 +1,31 @@ -import { Divider, Stack, Typography } from '@mui/material'; -import { valueFormat } from '@origin/shared/utils'; +import { Divider, Skeleton, Stack, Typography } from '@mui/material'; +import { tokens } from '@origin/shared/contracts'; +import { balanceFormat } from '@origin/shared/utils'; import { useIntl } from 'react-intl'; -import { useAccount } from 'wagmi'; +import { useAccount, useBalance } from 'wagmi'; +import { usePendingYield } from '../hooks'; import { useHistoryTableQuery } from '../queries.generated'; +import type { StackProps } from '@mui/material'; + export function APYContainer() { + const intl = useIntl(); const { address, isConnected } = useAccount(); - const { data } = useHistoryTableQuery( + const { data: oethBalance, isLoading: oethLoading } = useBalance({ + address, + token: tokens.mainnet.OETH.address, + watch: true, + }); + const { data: earnings, isLoading: earningsLoading } = useHistoryTableQuery( { address: address?.toLowerCase(), offset: 0 }, { enabled: isConnected, }, ); - const intl = useIntl(); + const { data: pendingYield, isLoading: pendingYieldLoading } = + usePendingYield(tokens.mainnet.OETH); + return ( ); } -interface Props { +type ValueContainerProps = { label: string; - value: number; -} + value: string; + isLoading?: boolean; +} & StackProps; -function ValueContainer(props: Props) { - const intl = useIntl(); +function ValueContainer({ + label, + value, + isLoading, + ...rest +}: ValueContainerProps) { return ( - + - {props.label} + {label} - {intl.formatNumber(props.value, valueFormat)} + {isLoading ? : value} ); diff --git a/libs/oeth/history/src/components/ApyHeader.tsx b/libs/oeth/history/src/components/ApyHeader.tsx deleted file mode 100644 index 1495ae331..000000000 --- a/libs/oeth/history/src/components/ApyHeader.tsx +++ /dev/null @@ -1,269 +0,0 @@ -import { useState } from 'react'; - -import { - alpha, - Box, - Divider, - IconButton, - Menu, - MenuItem, - Skeleton, - Stack, - Typography, -} from '@mui/material'; -import { Icon } from '@origin/shared/components'; -import { tokens } from '@origin/shared/contracts'; -import { formatAmount } from '@origin/shared/utils'; -import { useIntl } from 'react-intl'; -import { useAccount, useBalance } from 'wagmi'; - -import { useHistoryApyQuery, useHistoryTableQuery } from '../queries.generated'; - -import type { BoxProps } from '@mui/material'; - -const days = [7, 30]; - -export function ApyHeader() { - const intl = useIntl(); - const [selectedPeriod, setSelectedPeriod] = useState(30); - const [anchorEl, setAnchorEl] = useState(null); - const { address } = useAccount(); - const { data: oethBalance } = useBalance({ - address, - token: tokens.mainnet.OETH.address, - watch: true, - }); - - const { data: apy, isLoading: apyLoading } = useHistoryApyQuery(); - const { data: earnings, isLoading: earningsLoading } = useHistoryTableQuery({ - address: address?.toLowerCase(), - offset: 0, - }); - - const handleClose = () => { - setAnchorEl(null); - }; - - return ( - <> - - {days.map((day) => ( - { - setSelectedPeriod(day); - setAnchorEl(null); - }} - > - {intl.formatMessage( - { defaultMessage: '{days} day trailing' }, - { days: day }, - )} - - ))} - - - - - {intl.formatMessage( - { defaultMessage: '{days} day trailing APY' }, - { days: selectedPeriod }, - )} - - - {apyLoading ? ( - - ) : ( - - {intl.formatNumber( - selectedPeriod === 30 - ? apy.apies[0].apy30DayAvg - : apy.apies[0].apy7DayAvg, - { - minimumFractionDigits: 2, - }, - )} - - )} - setAnchorEl(e.currentTarget)} - sx={{ - backgroundColor: (theme) => - alpha(theme.palette.common.white, 0.15), - marginInlineStart: 1, - alignSelf: 'center', - position: 'relative', - height: '1rem', - width: '1rem', - borderRadius: '100%', - top: '-2px', - }} - > - - - - - - - - - - - - - - - - - - - ); -} - -type ValueContainerProps = { - text: string; - value: string; - icon?: string; - isLoading?: boolean; -} & BoxProps; - -function ValueContainer({ - text, - value, - icon, - isLoading, - ...rest -}: ValueContainerProps) { - return ( - - - {text} - - - {isLoading ? ( - - ) : ( - <> - {icon ? ( - - ) : undefined} - - {value} - - )} - - - ); -} diff --git a/libs/oeth/history/src/components/HistoryCard.tsx b/libs/oeth/history/src/components/HistoryCard.tsx index d87e068ee..65678ec3d 100644 --- a/libs/oeth/history/src/components/HistoryCard.tsx +++ b/libs/oeth/history/src/components/HistoryCard.tsx @@ -1,51 +1,31 @@ import { useState } from 'react'; import { Box, Button, Divider, Stack, Typography } from '@mui/material'; -import { graphqlClient } from '@origin/oeth/shared'; -import { useQuery } from '@tanstack/react-query'; import { useIntl } from 'react-intl'; import { useAccount } from 'wagmi'; -import { - HistoryTableDocument, - HistoryTableWithFiltersDocument, -} from '../queries.generated'; +import { useHistoryTableWithFiltersQuery } from '../queries.generated'; import { ExportData } from './ExportData'; import { HistoryFilters } from './Filters'; import { HistoryTable } from './HistoryTable'; -import type { HistoryTableQuery } from '../queries.generated'; - const PAGE_SIZE = 20; export function HistoryCard() { + const intl = useIntl(); const [page, setPage] = useState(0); const [filters, setFilters] = useState([]); const { address, isConnected } = useAccount(); - const { data, isFetching } = useQuery( - ['history-table', address, filters, page], - () => { - return graphqlClient< - HistoryTableQuery, - { addressId: string; filters?: string[]; offset: number } - >( - filters.length ? HistoryTableWithFiltersDocument : HistoryTableDocument, - { - addressId: address?.toLowerCase(), - filters: filters.length ? filters : undefined, - offset: page * PAGE_SIZE, - }, - )(); - }, - + const { data, isFetching } = useHistoryTableWithFiltersQuery( { - enabled: isConnected, + address: address.toLowerCase(), + filters: filters.length ? filters : undefined, + offset: page * PAGE_SIZE, }, + { enabled: isConnected }, ); - const intl = useIntl(); - return ( + useQuery({ + queryKey: ['usePendingYield', token.symbol], + queryFn: async () => { + const { address, isConnected } = getAccount(); + + if (!isConnected) { + return 0; + } + + // TODO endpoint for next userCredits is not yet available on subsquid + const [balance, data] = await Promise.all([ + fetchBalance({ address, token: token.address }), + queryClient.fetchQuery({ + queryKey: useHistoryTableQuery.getKey({ address, offset: 0 }), + queryFn: useHistoryTableQuery.fetcher({ + address: address.toLowerCase(), + offset: 0, + }), + }), + ]); + + if (isNilOrEmpty(data?.addressById) || balance?.value === 0n) return 0; + + return 0; + }, + }); diff --git a/libs/oeth/history/src/queries.generated.ts b/libs/oeth/history/src/queries.generated.ts index d1721cfd2..501572792 100644 --- a/libs/oeth/history/src/queries.generated.ts +++ b/libs/oeth/history/src/queries.generated.ts @@ -44,6 +44,7 @@ export type HistoryTableWithFiltersQuery = { earned: number; isContract: boolean; rebasingOption: string; + credits: any; lastUpdated: any; history: Array<{ __typename?: 'History'; @@ -96,6 +97,20 @@ export const useHistoryTableQuery = < ), options, ); + +useHistoryTableQuery.getKey = (variables: HistoryTableQueryVariables) => [ + 'HistoryTable', + variables, +]; +useHistoryTableQuery.fetcher = ( + variables: HistoryTableQueryVariables, + options?: RequestInit['headers'], +) => + graphqlClient( + HistoryTableDocument, + variables, + options, + ); export const HistoryTableWithFiltersDocument = ` query HistoryTableWithFilters($address: String!, $offset: Int!, $filters: [String!]) { addressById(id: $address) { @@ -103,6 +118,7 @@ export const HistoryTableWithFiltersDocument = ` earned isContract rebasingOption + credits lastUpdated history( limit: 20 @@ -134,6 +150,18 @@ export const useHistoryTableWithFiltersQuery = < >(HistoryTableWithFiltersDocument, variables), options, ); + +useHistoryTableWithFiltersQuery.getKey = ( + variables: HistoryTableWithFiltersQueryVariables, +) => ['HistoryTableWithFilters', variables]; +useHistoryTableWithFiltersQuery.fetcher = ( + variables: HistoryTableWithFiltersQueryVariables, + options?: RequestInit['headers'], +) => + graphqlClient< + HistoryTableWithFiltersQuery, + HistoryTableWithFiltersQueryVariables + >(HistoryTableWithFiltersDocument, variables, options); export const HistoryApyDocument = ` query HistoryApy { apies(limit: 1, orderBy: timestamp_DESC) { @@ -154,3 +182,15 @@ export const useHistoryApyQuery = ( ), options, ); + +useHistoryApyQuery.getKey = (variables?: HistoryApyQueryVariables) => + variables === undefined ? ['HistoryApy'] : ['HistoryApy', variables]; +useHistoryApyQuery.fetcher = ( + variables?: HistoryApyQueryVariables, + options?: RequestInit['headers'], +) => + graphqlClient( + HistoryApyDocument, + variables, + options, + ); diff --git a/libs/oeth/history/src/queries.graphql b/libs/oeth/history/src/queries.graphql index 52741cbcb..856707099 100644 --- a/libs/oeth/history/src/queries.graphql +++ b/libs/oeth/history/src/queries.graphql @@ -4,6 +4,7 @@ query HistoryTable($address: String!, $offset: Int!) { earned isContract rebasingOption + credits lastUpdated history(limit: 20, orderBy: timestamp_DESC, offset: $offset) { type diff --git a/libs/oeth/shared/codegen.ts b/libs/oeth/shared/codegen.ts index b46925b90..d6642c245 100644 --- a/libs/oeth/shared/codegen.ts +++ b/libs/oeth/shared/codegen.ts @@ -19,6 +19,8 @@ const config: CodegenConfig = { hooks: { afterOneFileWrite: ['prettier --write', 'eslint --fix'] }, plugins: ['typescript-operations', 'typescript-react-query'], config: { + exposeFetcher: true, + exposeQueryKeys: true, fetcher: { func: '@origin/oeth/shared#graphqlClient', }, diff --git a/libs/oeth/shared/src/clients/graphql.ts b/libs/oeth/shared/src/clients/graphql.ts index c57ab4ab0..69069cc09 100644 --- a/libs/oeth/shared/src/clients/graphql.ts +++ b/libs/oeth/shared/src/clients/graphql.ts @@ -6,7 +6,11 @@ export const axiosInstance = axios.create({ }); export const graphqlClient = - (query: string, variables?: TVariables) => + ( + query: string, + variables?: TVariables, + options?: RequestInit['headers'], + ) => async () => { const res = await axiosInstance({ url: '/graphql', @@ -14,6 +18,7 @@ export const graphqlClient = headers: { 'Content-Type': 'application/json', }, + ...options, data: { query, variables }, }); diff --git a/libs/oeth/swap/src/queries.generated.ts b/libs/oeth/swap/src/queries.generated.ts index 144ceb398..4c5ec1771 100644 --- a/libs/oeth/swap/src/queries.generated.ts +++ b/libs/oeth/swap/src/queries.generated.ts @@ -37,3 +37,15 @@ export const useApiesQuery = ( graphqlClient(ApiesDocument, variables), options, ); + +useApiesQuery.getKey = (variables?: ApiesQueryVariables) => + variables === undefined ? ['Apies'] : ['Apies', variables]; +useApiesQuery.fetcher = ( + variables?: ApiesQueryVariables, + options?: RequestInit['headers'], +) => + graphqlClient( + ApiesDocument, + variables, + options, + ); diff --git a/libs/shared/utils/src/formatters.ts b/libs/shared/utils/src/formatters.ts index 3e02d1058..b75382e95 100644 --- a/libs/shared/utils/src/formatters.ts +++ b/libs/shared/utils/src/formatters.ts @@ -20,6 +20,11 @@ export const quantityFormat: FormatNumberOptions = { maximumFractionDigits: 4, }; +export const balanceFormat: FormatNumberOptions = { + minimumFractionDigits: 2, + maximumFractionDigits: 2, +}; + // Format map [value, maxDigits] const mappings = [ [10000000, 0], From 1fa3dc9bc0ca6a1c8a0e68112340d35308651483 Mon Sep 17 00:00:00 2001 From: toniocodo Date: Mon, 25 Sep 2023 11:44:58 +0200 Subject: [PATCH 6/6] fix: chart registering --- apps/oeth/src/App.tsx | 16 ---------------- apps/oeth/src/main.tsx | 8 +++++++- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/apps/oeth/src/App.tsx b/apps/oeth/src/App.tsx index eb4223a4c..0b44fde71 100644 --- a/apps/oeth/src/App.tsx +++ b/apps/oeth/src/App.tsx @@ -1,24 +1,8 @@ import { Container, CssBaseline, Stack } from '@mui/material'; -import { - CategoryScale, - Chart as ChartJS, - LinearScale, - LineElement, - PointElement, - registerables, -} from 'chart.js'; import { Outlet } from 'react-router-dom'; import { Topnav } from './components/Topnav'; -ChartJS.register( - ...registerables, - CategoryScale, - LinearScale, - LineElement, - PointElement, -); - export const App = () => { return ( <> diff --git a/apps/oeth/src/main.tsx b/apps/oeth/src/main.tsx index e6cbe3e94..2076a176c 100644 --- a/apps/oeth/src/main.tsx +++ b/apps/oeth/src/main.tsx @@ -6,7 +6,11 @@ import * as ReactDOM from 'react-dom/client'; import { Experimental_CssVarsProvider as CssVarsProvider } from '@mui/material'; import { chains, queryClient, wagmiConfig } from '@origin/oeth/shared'; -import { CurveProvider, NotificationsProvider } from '@origin/shared/providers'; +import { + CurveProvider, + NotificationsProvider, + registerChart, +} from '@origin/shared/providers'; import { theme } from '@origin/shared/theme'; import { composeContexts } from '@origin/shared/utils'; import { darkTheme, RainbowKitProvider } from '@rainbow-me/rainbowkit'; @@ -22,6 +26,8 @@ import { routes } from './routes'; // https://github.com/dai-shi/proxy-compare/pull/8 setAutoFreeze(false); +registerChart(); + const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement, );