diff --git a/src/constants/abacus.ts b/src/constants/abacus.ts index f9e7b69..1e7623f 100644 --- a/src/constants/abacus.ts +++ b/src/constants/abacus.ts @@ -119,6 +119,8 @@ export const InputSelectionOption = Abacus.exchange.dydx.abacus.output.input.Sel // ------ Wallet ------ // 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 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; diff --git a/src/lib/abacus/stateNotification.ts b/src/lib/abacus/stateNotification.ts index 216b7b7..b8b328c 100644 --- a/src/lib/abacus/stateNotification.ts +++ b/src/lib/abacus/stateNotification.ts @@ -29,6 +29,7 @@ import { setSubaccount, setTransfers, setWallet, + setTradingRewards, } from '@/state/account'; import { setApiState } from '@/state/app'; @@ -96,6 +97,12 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol { } } + if (changes.has(Changes.tradingRewards)) { + if (updatedState.account?.tradingRewards) { + dispatch(setTradingRewards(updatedState.account?.tradingRewards)); + } + } + if (changes.has(Changes.configs)) { dispatch(setConfigs(updatedState.configs)); } diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index 2f9a0bd..5117cd5 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -26,7 +26,6 @@ import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks'; import { getOnboardingState } from '@/state/accountSelectors'; import { openDialog } from '@/state/dialogs'; -import abacusStateManager from '@/lib/abacus'; import { isTruthy } from '@/lib/isTruthy'; import { truncateAddress } from '@/lib/wallet'; diff --git a/src/pages/rewards/RewardsPage.tsx b/src/pages/rewards/RewardsPage.tsx index f69078a..73331cc 100644 --- a/src/pages/rewards/RewardsPage.tsx +++ b/src/pages/rewards/RewardsPage.tsx @@ -24,6 +24,7 @@ import { DYDXBalancePanel } from './DYDXBalancePanel'; import { MigratePanel } from './MigratePanel'; import { LaunchIncentivesPanel } from './LaunchIncentivesPanel'; import { RewardsHelpPanel } from './RewardsHelpPanel'; +import { TradingRewardsSummaryPanel } from './TradingRewardsSummaryPanel'; const RewardsPage = () => { const dispatch = useDispatch(); @@ -50,50 +51,49 @@ const RewardsPage = () => { {stringGetter({ key: STRING_KEYS.TRADING_REWARDS })} )} - {import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && } + + {import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && } - {isTablet ? ( - - ) : ( - - - - - )} + + + {isTablet && Help} />} + Reward History} /> + - - {stringGetter({ key: STRING_KEYS.GOVERNANCE })} - } - slotRight={panelArrow} - onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))} - > - - {stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })} - e.stopPropagation()}> - {stringGetter({ key: STRING_KEYS.LEARN_MORE })} → - - - + {isNotTablet && ( + + {stringGetter({ key: STRING_KEYS.GOVERNANCE })} + } + slotRight={panelArrow} + onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))} + > + + {stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })} + e.stopPropagation()}> + {stringGetter({ key: STRING_KEYS.LEARN_MORE })} → + + + - {stringGetter({ key: STRING_KEYS.STAKING })} - } - slotRight={panelArrow} - onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))} - > - - {stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })} - e.stopPropagation()}> - {stringGetter({ key: STRING_KEYS.LEARN_MORE })} → - - - - + {stringGetter({ key: STRING_KEYS.STAKING })}} + slotRight={panelArrow} + onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))} + > + + {stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })} + e.stopPropagation()}> + {stringGetter({ key: STRING_KEYS.LEARN_MORE })} → + + + - + Help} /> + + )} + ); }; @@ -104,7 +104,6 @@ const Styled: Record = {}; Styled.Page = styled.div` ${layoutMixins.contentContainerPage} - gap: 1.5rem; padding: 2rem; align-items: center; @@ -138,6 +137,54 @@ Styled.MobileHeader = styled.header` Styled.Panel = styled(Panel)` height: fit-content; + + font: var(--font-large-medium); + color: var(--color-text-2); + background-color: var(--color-layer-2); +`; + +Styled.GridLayout = styled.div` + --gap: 1.5rem; + display: grid; + grid-template-columns: 2fr 1fr; + gap: var(--gap); + + > * { + gap: var(--gap); + } + + grid-template-areas: + 'migrate migrate' + 'incentives balance' + 'rewards other'; + + @media ${breakpoints.tablet} { + --gap: 1rem; + grid-template-columns: 1fr; + grid-template-areas: + 'incentives' + 'rewards'; + } +`; + +Styled.MigratePanel = styled(MigratePanel)` + grid-area: migrate; +`; + +Styled.LaunchIncentivesPanel = styled(LaunchIncentivesPanel)` + grid-area: incentives; +`; +Styled.DYDXBalancePanel = styled(DYDXBalancePanel)` + grid-area: balance; +`; + +Styled.TradingRewardsColumn = styled.div` + grid-area: rewards; + ${layoutMixins.flexColumn} +`; +Styled.OtherColumn = styled.div` + grid-area: other; + ${layoutMixins.flexColumn} `; Styled.Title = styled.h3` @@ -158,20 +205,6 @@ Styled.Description = styled.div` } `; -Styled.PanelRow = styled.div` - ${layoutMixins.gridEqualColumns} - gap: 1.5rem; - - @media ${breakpoints.tablet} { - grid-auto-flow: row; - grid-template-columns: 1fr; - } -`; - -Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)` - grid-template-columns: 2fr 1fr; -`; - Styled.IconButton = styled(IconButton)` color: var(--color-text-0); --color-border: var(--color-layer-6); diff --git a/src/pages/rewards/TradingRewardsSummaryPanel.tsx b/src/pages/rewards/TradingRewardsSummaryPanel.tsx new file mode 100644 index 0000000..236d183 --- /dev/null +++ b/src/pages/rewards/TradingRewardsSummaryPanel.tsx @@ -0,0 +1,119 @@ +import styled, { AnyStyledComponent } from 'styled-components'; +import { shallowEqual, useSelector } from 'react-redux'; + +import { STRING_KEYS } from '@/constants/localization'; +import breakpoints from '@/styles/breakpoints'; +import { layoutMixins } from '@/styles/layoutMixins'; +import { useStringGetter, useTokenConfigs } from '@/hooks'; + +import { AssetIcon } from '@/components/AssetIcon'; +import { Details } from '@/components/Details'; +import { Output, OutputType } from '@/components/Output'; +import { Panel } from '@/components/Panel'; + +import { getHistoricalTradingRewards } from '@/state/accountSelectors'; + +export const TradingRewardsSummaryPanel = () => { + const stringGetter = useStringGetter(); + const historicalTradingRewards = useSelector(getHistoricalTradingRewards, shallowEqual); + const currentWeekTradingReward = historicalTradingRewards?.get('WEEKLY')?.firstOrNull()?.amount; + const { chainTokenLabel } = useTokenConfigs(); + + return ( + + Trading Rewards Summary + + } + > + + +

{stringGetter({ key: STRING_KEYS.THIS_WEEK })}

+ + ), + value: ( + } + type={OutputType.Asset} + value={currentWeekTradingReward} + /> + ), + }, + // TODO(@aforaleka): add all-time when supported + ]} + /> +
+
+ ); +}; + +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} + gap: 1rem; + padding: var(--panel-paddingY) var(--panel-paddingX) 0; +`; + +Styled.Title = styled.h3` + ${layoutMixins.inlineRow} + font: var(--font-medium-book); + color: var(--color-text-2); + + img { + font-size: 1.5rem; + } +`; + +Styled.Content = styled.div` + ${layoutMixins.flexColumn} + gap: 0.75rem; +`; + +Styled.TradingRewardsDetails = styled(Details)` + --details-item-backgroundColor: var(--color-layer-6); + + grid-template-columns: 1fr; // TODO(@aforaleka): change to 1fr 1fr when all-time is supported + gap: 1rem; + + > div { + gap: 1rem; + + padding: 1rem; + + border-radius: 0.75em; + background-color: var(--color-layer-5); + } + + dt { + width: 100%; + } + + output { + color: var(--color-text-2); + font: var(--font-large-book); + } +`; + +Styled.Label = styled.div` + ${layoutMixins.spacedRow} + + font: var(--font-base-book); + color: var(--color-text-1); +`; diff --git a/src/state/account.ts b/src/state/account.ts index aae7b2d..57d7a54 100644 --- a/src/state/account.ts +++ b/src/state/account.ts @@ -13,6 +13,7 @@ import type { HistoricalPnlPeriods, SubAccountHistoricalPNLs, UsageRestriction, + TradingRewards, } from '@/constants/abacus'; import { OnboardingGuard, OnboardingState } from '@/constants/account'; @@ -24,6 +25,7 @@ import { getLocalStorage } from '@/lib/localStorage'; export type AccountState = { balances?: Record; stakingBalances?: Record; + tradingRewards?: TradingRewards; wallet?: Nullable; walletType?: WalletType; @@ -179,6 +181,9 @@ export const accountSlice = createSlice({ setStakingBalances: (state, action: PayloadAction>) => { state.stakingBalances = action.payload; }, + setTradingRewards: (state, action: PayloadAction) => { + state.tradingRewards = action.payload; + }, addUncommittedOrderClientId: (state, action: PayloadAction) => { state.uncommittedOrderClientIds.push(action.payload); }, @@ -206,6 +211,7 @@ export const { viewedOrders, setBalances, setStakingBalances, + setTradingRewards, addUncommittedOrderClientId, removeUncommittedOrderClientId, } = accountSlice.actions; diff --git a/src/state/accountSelectors.ts b/src/state/accountSelectors.ts index 9594b46..49c9d32 100644 --- a/src/state/accountSelectors.ts +++ b/src/state/accountSelectors.ts @@ -343,6 +343,17 @@ export const getBalances = (state: RootState) => state.account?.balances; * */ export const getStakingBalances = (state: RootState) => state.account?.stakingBalances; +/** + * @returns account all time trading rewards + */ +export const getTotalTradingRewards = (state: RootState) => state.account?.tradingRewards?.total; + +/** + * @returns account trading rewards aggregated by period + */ +export const getHistoricalTradingRewards = (state: RootState) => + state.account?.tradingRewards?.historical; + /** * @returns UsageRestriction of the current session */