From 7eae69137b26f6dfb23624a695c60a7e63e85056 Mon Sep 17 00:00:00 2001 From: Aleka Cheung Date: Mon, 8 Jan 2024 18:25:00 -0500 Subject: [PATCH] reward history table, polish etc --- package.json | 2 +- pnpm-lock.yaml | 8 +- src/components/Accordion.stories.tsx | 2 +- src/components/Output.tsx | 1 - src/components/Table.tsx | 138 +++++++++-------- src/constants/abacus.ts | 15 ++ src/icons/caret-down.svg | 2 +- src/lib/abacus/index.ts | 17 ++- src/pages/rewards/MigratePanel.tsx | 3 +- src/pages/rewards/RewardHistoryPanel.tsx | 111 ++++++++++++++ src/pages/rewards/RewardsPage.tsx | 14 +- .../rewards/TradingRewardsSummaryPanel.tsx | 28 ++-- .../tables/TradingRewardHistoryTable.tsx | 141 ++++++++++++++++++ 13 files changed, 395 insertions(+), 87 deletions(-) create mode 100644 src/pages/rewards/RewardHistoryPanel.tsx create mode 100644 src/views/tables/TradingRewardHistoryTable.tsx diff --git a/package.json b/package.json index f2ed3f1..9a30cca 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@cosmjs/tendermint-rpc": "^0.31.0", "@dydxprotocol/v4-abacus": "^1.1.33", "@dydxprotocol/v4-client-js": "^1.0.11", - "@dydxprotocol/v4-localization": "^1.1.6", + "@dydxprotocol/v4-localization": "^1.1.7", "@ethersproject/providers": "^5.7.2", "@js-joda/core": "^5.5.3", "@radix-ui/react-accordion": "^1.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12f032e..5bdf292 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,8 @@ dependencies: specifier: ^1.0.11 version: 1.0.11 '@dydxprotocol/v4-localization': - specifier: ^1.1.6 - version: 1.1.6 + specifier: ^1.1.7 + version: 1.1.7 '@ethersproject/providers': specifier: ^5.7.2 version: 5.7.2 @@ -1023,8 +1023,8 @@ packages: - utf-8-validate dev: false - /@dydxprotocol/v4-localization@1.1.6: - resolution: {integrity: sha512-Bon6NSRU4/FqneAbnP2G28EAPr0hp4LhvAayX61o0O1PGkxnLzAHkXeFppdM0Zn0fOcp1S1MJ+gvz138ZDephQ==} + /@dydxprotocol/v4-localization@1.1.7: + resolution: {integrity: sha512-pmRZTszmoee9Ml2v7E1wmIiaZCZpqQE66d/20I/xdnwwEIPt0iOriP59nlep+O/P1r9/Qxf/l+ZzvVbO720xjg==} dev: false /@dydxprotocol/v4-proto@0.4.1: diff --git a/src/components/Accordion.stories.tsx b/src/components/Accordion.stories.tsx index be7b479..40a532a 100644 --- a/src/components/Accordion.stories.tsx +++ b/src/components/Accordion.stories.tsx @@ -23,4 +23,4 @@ Accordion.args = { content: 'Answer 2.', }, ], -}; \ No newline at end of file +}; diff --git a/src/components/Output.tsx b/src/components/Output.tsx index 984a828..986a0ac 100644 --- a/src/components/Output.tsx +++ b/src/components/Output.tsx @@ -66,7 +66,6 @@ type ElementProps = { stripRelativeWords?: boolean; }; timeOptions?: { - format?: 'long' | 'short' | 'narrow' | 'singleCharacter'; useUTC?: boolean; }; tag?: React.ReactNode; diff --git a/src/components/Table.tsx b/src/components/Table.tsx index d82a94a..df7dc1a 100644 --- a/src/components/Table.tsx +++ b/src/components/Table.tsx @@ -44,6 +44,8 @@ import { layoutMixins } from '@/styles/layoutMixins'; import { Icon, IconName } from './Icon'; import { Tag } from './Tag'; import { MustBigNumber } from '@/lib/numbers'; +import { Button } from './Button'; +import { CaretIcon } from '@/icons'; export { TableCell } from './Table/TableCell'; export { TableColumnHeader } from './Table/TableColumnHeader'; @@ -92,6 +94,7 @@ type ElementProps void; slotEmpty?: React.ReactNode; + initialNumRowsToShow?: number; // collection: TableCollection; // children: React.ReactNode; }; @@ -121,6 +124,7 @@ export const Table = ({ selectionMode = 'single', selectionBehavior = 'toggle', slotEmpty, + initialNumRowsToShow = data.length, // shouldRowRender, // collection, @@ -136,6 +140,7 @@ export const Table = ({ style, }: ElementProps & StyleProps) => { const [selectedKeys, setSelectedKeys] = useState(new Set()); + const [numRowsToShow, setNumRowsToShow] = useState(initialNumRowsToShow); const currentBreakpoints = useBreakpoints(); const shownColumns = columns.filter( @@ -188,67 +193,77 @@ export const Table = ({ const isEmpty = data.length === 0; return ( - - {!isEmpty ? ( - onRowAction(key, data.find((row) => getRowKey(row) === key)!)) - } - // shouldRowRender={shouldRowRender} - hideHeader={hideHeader} - withGradientCardRows={withGradientCardRows} - withFocusStickyRows={withFocusStickyRows} - withOuterBorder={withOuterBorder} - withInnerBorders={withInnerBorders} - withScrollSnapColumns={withScrollSnapColumns} - withScrollSnapRows={withScrollSnapRows} - > - - {(column) => ( - - {column.label} - {column.tag && {column.tag}} - - )} - + <> + + {!isEmpty ? ( + onRowAction(key, data.find((row) => getRowKey(row) === key)!)) + } + // shouldRowRender={shouldRowRender} + hideHeader={hideHeader} + withGradientCardRows={withGradientCardRows} + withFocusStickyRows={withFocusStickyRows} + withOuterBorder={withOuterBorder} + withInnerBorders={withInnerBorders} + withScrollSnapColumns={withScrollSnapColumns} + withScrollSnapRows={withScrollSnapRows} + > + + {(column) => ( + + {column.label} + {column.tag && {column.tag}} + + )} + - - {(item) => ( - - {(columnKey) => ( - - {columns.find((column) => column.columnKey === columnKey)?.renderCell?.(item)} - - )} - - )} - - - ) : ( - {slotEmpty} + + {(item) => ( + + {(columnKey) => ( + + {columns.find((column) => column.columnKey === columnKey)?.renderCell?.(item)} + + )} + + )} + + + ) : ( + {slotEmpty} + )} + + {numRowsToShow !== undefined && numRowsToShow < data.length && ( + setNumRowsToShow(data.length)} + slotRight={} + > + View more + )} - + ); }; @@ -922,3 +937,8 @@ Styled.Row = styled.div` ${layoutMixins.inlineRow} padding: var(--tableCell-padding); `; + +Styled.ViewMoreButton = styled(Button)` + --button-backgroundColor: var(--color-layer-2); + width: 100%; +`; diff --git a/src/constants/abacus.ts b/src/constants/abacus.ts index 1e7623f..bbfe5fe 100644 --- a/src/constants/abacus.ts +++ b/src/constants/abacus.ts @@ -121,6 +121,11 @@ export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet; export type AccountBalance = Abacus.exchange.dydx.abacus.output.AccountBalance; export type TradingRewards = Abacus.exchange.dydx.abacus.output.TradingRewards; export type HistoricalTradingReward = Abacus.exchange.dydx.abacus.output.HistoricalTradingReward; +export const HistoricaTradingRewardsPeriod = + Abacus.exchange.dydx.abacus.state.manager.HistoricaTradingRewardsPeriod; +const historicalTradingRewardsPeriod = [...HistoricaTradingRewardsPeriod.values()] as const; +export type HistoricaTradingRewardsPeriods = (typeof historicalTradingRewardsPeriod)[number]; + export type Subaccount = Abacus.exchange.dydx.abacus.output.Subaccount; export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPosition; export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder; @@ -238,6 +243,16 @@ export const HISTORICAL_PNL_PERIODS: Record< [HistoricalPnlPeriod.Period90d.name]: HistoricalPnlPeriod.Period90d, }; +export const HISTORICAL_TRADING_REWARDS_PERIODS: Record< + KotlinIrEnumValues, + HistoricaTradingRewardsPeriods +> = { + [HistoricaTradingRewardsPeriod.MONTHLY.name]: HistoricaTradingRewardsPeriod.MONTHLY, + [HistoricaTradingRewardsPeriod.WEEKLY.name]: HistoricaTradingRewardsPeriod.WEEKLY, + [HistoricaTradingRewardsPeriod.DAILY.name]: HistoricaTradingRewardsPeriod.DAILY, + [HistoricaTradingRewardsPeriod.BLOCK.name]: HistoricaTradingRewardsPeriod.BLOCK, +}; + export const ORDER_STATUS_STRINGS: Record, string> = { [AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS, [AbacusOrderStatus.open.rawValue]: STRING_KEYS.OPEN_STATUS, diff --git a/src/icons/caret-down.svg b/src/icons/caret-down.svg index c55d391..7f9c130 100644 --- a/src/icons/caret-down.svg +++ b/src/icons/caret-down.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/lib/abacus/index.ts b/src/lib/abacus/index.ts index 796fb94..6567057 100644 --- a/src/lib/abacus/index.ts +++ b/src/lib/abacus/index.ts @@ -9,6 +9,8 @@ import type { TransferInputFields, HistoricalPnlPeriods, ParsingError, + HistoricaTradingRewardsPeriods, + HistoricaTradingRewardsPeriod, } from '@/constants/abacus'; import { @@ -218,6 +220,12 @@ class AbacusStateManager { this.stateManager.historicalPnlPeriod = period; }; + setHistoricalTradingRewardPeriod = ( + period: (typeof HistoricaTradingRewardsPeriod)[keyof typeof HistoricaTradingRewardsPeriod] + ) => { + this.stateManager.historicalTradingRewardPeriod = period; + }; + switchNetwork = (network: DydxNetwork) => { this.stateManager.environmentId = network; @@ -262,17 +270,16 @@ class AbacusStateManager { ) => this.stateManager.cancelOrder(orderId, callback); cctpWithdraw = ( - callback: ( - success: boolean, - parsingError: Nullable, - data: string, - ) => void + callback: (success: boolean, parsingError: Nullable, data: string) => void ): void => this.stateManager.commitCCTPWithdraw(callback); // ------ Utils ------ // getHistoricalPnlPeriod = (): Nullable => this.stateManager.historicalPnlPeriod; + getHistoricalTradingRewardPeriod = (): HistoricaTradingRewardsPeriods => + this.stateManager.historicalTradingRewardPeriod; + handleCandlesSubscription = ({ channelId, subscribe, diff --git a/src/pages/rewards/MigratePanel.tsx b/src/pages/rewards/MigratePanel.tsx index 38aacf1..9be9d10 100644 --- a/src/pages/rewards/MigratePanel.tsx +++ b/src/pages/rewards/MigratePanel.tsx @@ -146,14 +146,15 @@ Styled.Title = styled.h3` color: var(--color-text-2); padding: var(--panel-paddingY) var(--panel-paddingX) 0; + margin-bottom: -0.5rem; `; Styled.MigrateAction = styled.div` ${layoutMixins.flexEqualColumns} align-items: center; - margin: 1rem; gap: 1rem; padding: 1rem; + margin: 1rem; width: 100%; background-color: var(--color-layer-2); diff --git a/src/pages/rewards/RewardHistoryPanel.tsx b/src/pages/rewards/RewardHistoryPanel.tsx new file mode 100644 index 0000000..39c9ef1 --- /dev/null +++ b/src/pages/rewards/RewardHistoryPanel.tsx @@ -0,0 +1,111 @@ +import { useCallback, useState } from 'react'; +import styled, { AnyStyledComponent } from 'styled-components'; +import { shallowEqual, useSelector } from 'react-redux'; + +import breakpoints from '@/styles/breakpoints'; +import { layoutMixins } from '@/styles/layoutMixins'; +import { useStringGetter } from '@/hooks'; + +import { + HISTORICAL_TRADING_REWARDS_PERIODS, + HistoricaTradingRewardsPeriod, + HistoricaTradingRewardsPeriods, +} from '@/constants/abacus'; + +import { ComingSoon } from '@/components/ComingSoon'; +import { Panel } from '@/components/Panel'; +import { ToggleGroup } from '@/components/ToggleGroup'; +import { TradingRewardHistoryTable } from '@/views/tables/TradingRewardHistoryTable'; + +import { getHistoricalTradingRewards } from '@/state/accountSelectors'; +import abacusStateManager from '@/lib/abacus'; + +export const RewardHistoryPanel = () => { + const stringGetter = useStringGetter(); + const historicalTradingRewards = useSelector(getHistoricalTradingRewards, shallowEqual); + + const [selectedPeriod, setSelectedPeriod] = useState( + abacusStateManager.getHistoricalTradingRewardPeriod() || HistoricaTradingRewardsPeriod.WEEKLY + ); + + const onSelectPeriod = useCallback( + (periodName: string) => { + setSelectedPeriod( + HISTORICAL_TRADING_REWARDS_PERIODS[ + periodName as keyof typeof HISTORICAL_TRADING_REWARDS_PERIODS + ] + ); + }, + [setSelectedPeriod, selectedPeriod] + ); + + return ( + + +

Reward History

+ Rewards are distrubted after every block. +
+ + + } + > + {historicalTradingRewards ? ( + + ) : ( + + )} +
+ ); +}; + +const Styled: Record = {}; + +Styled.Panel = styled(Panel)` + --panel-paddingX: 1.5rem; + --panel-paddingY: 1.25rem; + + @media ${breakpoints.tablet} { + --panel-paddingY: 1.5rem; + } +`; + +Styled.Header = styled.div` + ${layoutMixins.spacedRow} + + padding: 1rem 1.5rem 0; + margin-bottom: -0.5rem; +`; + +Styled.Title = styled.div` + color: var(--color-text-0); + font: var(--font-small-book); + + h3 { + font: var(--font-medium-book); + color: var(--color-text-2); + } +`; + +Styled.Content = styled.div` + ${layoutMixins.flexColumn} + gap: 0.75rem; +`; diff --git a/src/pages/rewards/RewardsPage.tsx b/src/pages/rewards/RewardsPage.tsx index 81bf45c..08c1d06 100644 --- a/src/pages/rewards/RewardsPage.tsx +++ b/src/pages/rewards/RewardsPage.tsx @@ -25,7 +25,7 @@ import { MigratePanel } from './MigratePanel'; import { LaunchIncentivesPanel } from './LaunchIncentivesPanel'; import { RewardsHelpPanel } from './RewardsHelpPanel'; import { TradingRewardsSummaryPanel } from './TradingRewardsSummaryPanel'; -import { RewardsHelpPanel } from './RewardsHelpPanel'; +import { RewardHistoryPanel } from './RewardHistoryPanel'; const RewardsPage = () => { const dispatch = useDispatch(); @@ -58,7 +58,7 @@ const RewardsPage = () => { {isTablet && } - Reward History} /> + {isNotTablet && ( @@ -190,6 +190,16 @@ Styled.OtherColumn = styled.div` ${layoutMixins.flexColumn} `; +Styled.RewardHistoryHeader = styled.div` + h3 { + font: var(--font-medium-book); + color: var(--color-text-2); + } + + padding: 1rem 1.5rem 0; + margin-bottom: -0.5rem; +`; + Styled.Title = styled.h3` font: var(--font-medium-book); color: var(--color-text-2); diff --git a/src/pages/rewards/TradingRewardsSummaryPanel.tsx b/src/pages/rewards/TradingRewardsSummaryPanel.tsx index 493c319..3050abe 100644 --- a/src/pages/rewards/TradingRewardsSummaryPanel.tsx +++ b/src/pages/rewards/TradingRewardsSummaryPanel.tsx @@ -7,12 +7,12 @@ import { layoutMixins } from '@/styles/layoutMixins'; import { useStringGetter, useTokenConfigs } from '@/hooks'; import { AssetIcon } from '@/components/AssetIcon'; +import { ComingSoon } from '@/components/ComingSoon'; import { Details } from '@/components/Details'; import { Output, OutputType } from '@/components/Output'; import { Panel } from '@/components/Panel'; import { getHistoricalTradingRewards } from '@/state/accountSelectors'; -import { HistoricalTradingReward } from '@/constants/abacus'; export const TradingRewardsSummaryPanel = () => { const stringGetter = useStringGetter(); @@ -29,8 +29,7 @@ export const TradingRewardsSummaryPanel = () => { } > - {stringGetter({ key: STRING_KEYS.COMING_SOON })} - { + {currentWeekTradingReward ? ( { value: ( } + slotRight={} type={OutputType.Asset} - value={currentWeekTradingReward?.amount} + value={currentWeekTradingReward.amount} /> @@ -67,7 +66,9 @@ export const TradingRewardsSummaryPanel = () => { // TODO(@aforaleka): add all-time when supported ]} /> - } + ) : ( + + )} ); @@ -94,6 +95,7 @@ Styled.Title = styled.h3` ${layoutMixins.inlineRow} font: var(--font-medium-book); color: var(--color-text-2); + margin-bottom: -0.5rem; img { font-size: 1.5rem; @@ -112,10 +114,8 @@ Styled.TradingRewardsDetails = styled(Details)` gap: 1rem; > div { - gap: 1rem; - + gap: 0.5rem; padding: 1rem; - border-radius: 0.75em; background-color: var(--color-layer-5); } @@ -152,5 +152,9 @@ Styled.TimePeriod = styled.div` Styled.Column = styled.div` ${layoutMixins.flexColumn} - gap: 0.5rem; + gap: 0.33rem; +`; + +Styled.AssetIcon = styled(AssetIcon)` + margin-left: 0.5ch; `; diff --git a/src/views/tables/TradingRewardHistoryTable.tsx b/src/views/tables/TradingRewardHistoryTable.tsx new file mode 100644 index 0000000..f3fbb98 --- /dev/null +++ b/src/views/tables/TradingRewardHistoryTable.tsx @@ -0,0 +1,141 @@ +import styled, { type AnyStyledComponent } from 'styled-components'; +import { shallowEqual, useSelector } from 'react-redux'; + +import { HistoricaTradingRewardsPeriods } from '@/constants/abacus'; +import { useTokenConfigs } from '@/hooks'; +import { layoutMixins } from '@/styles/layoutMixins'; + +import { AssetIcon } from '@/components/AssetIcon'; +import { Output, OutputType } from '@/components/Output'; +import { Table, TableCell, type ColumnDef } from '@/components/Table'; + +import { getHistoricalTradingRewards } from '@/state/accountSelectors'; + +export enum TradingRewardHistoryTableColumnKey { + Event = 'Event', + Earned = 'Earned', +} + +const getTradingRewardHistoryTableColumnDef = ({ + key, + chainTokenLabel, +}: { + key: TradingRewardHistoryTableColumnKey; + chainTokenLabel: string; +}): ColumnDef => ({ + ...( + { + [TradingRewardHistoryTableColumnKey.Event]: { + columnKey: TradingRewardHistoryTableColumnKey.Event, + getCellValue: (row) => row.startedAtInMilliseconds, + label: 'Event', + renderCell: ({ startedAtInMilliseconds, endedAtInMilliseconds }) => ( + + Rewarded + + For trading + + → + + + + ), + }, + [TradingRewardHistoryTableColumnKey.Earned]: { + columnKey: TradingRewardHistoryTableColumnKey.Earned, + getCellValue: (row) => row.amount, + label: 'Earned', + renderCell: ({ amount }) => ( + } + /> + ), + }, + } as Record> + )[key], +}); + +type ElementProps = { + columnKeys?: TradingRewardHistoryTableColumnKey[]; + period: HistoricaTradingRewardsPeriods; +}; + +type StyleProps = { + withOuterBorder?: boolean; + withInnerBorders?: boolean; +}; + +export const TradingRewardHistoryTable = ({ + period, + columnKeys = Object.values(TradingRewardHistoryTableColumnKey), + withOuterBorder, + withInnerBorders = true, +}: ElementProps & StyleProps) => { + const historicalTradingRewards = useSelector(getHistoricalTradingRewards, shallowEqual); + const periodTradingRewards = historicalTradingRewards?.get(period.name) ?? []; + const { chainTokenLabel } = useTokenConfigs(); + + return ( + row.startedAtInMilliseconds} + columns={columnKeys.map((key: TradingRewardHistoryTableColumnKey) => + getTradingRewardHistoryTableColumnDef({ + key, + chainTokenLabel, + }) + )} + selectionBehavior="replace" + withOuterBorder={withOuterBorder} + withInnerBorders={withInnerBorders} + initialNumRowsToShow={5} + withScrollSnapColumns + withScrollSnapRows + /> + ); +}; + +const Styled: Record = {}; + +Styled.Table = styled(Table)` + --tableCell-padding: 0.5rem 0; + --tableHeader-backgroundColor: var(--color-layer-3); + --tableRow-backgroundColor: var(--color-layer-3); + + tbody { + font: var(--font-medium-book); + } +`; + +Styled.Rewarded = styled.span` + color: var(--color-text-2); +`; + +Styled.TimePeriod = styled.div` + ${layoutMixins.inlineRow} + + && { + color: var(--color-text-0); + font: var(--font-base-book); + } + + output { + color: var(--color-text-1); + font: var(--font-base-book); + } +`; + +Styled.AssetIcon = styled(AssetIcon)` + margin-left: 0.5ch; +`;