From ac53b1f97a33d5ea93a0a38c2a73c651340a9a64 Mon Sep 17 00:00:00 2001 From: Sam Keen Date: Mon, 20 Feb 2023 14:30:11 +0000 Subject: [PATCH] feat(governance,ui-toolkit): individual rewards table (#2928) --- .../src/hooks/use-pending-balances-manager.ts | 5 +- apps/token/src/i18n/translations/dev.json | 2 + .../rewards/connect-to-see-rewards.spec.tsx | 25 ++ .../routes/rewards/connect-to-see-rewards.tsx | 40 +++ .../epoch-individual-awards/reward-table.tsx | 79 ------ .../epoch-individual-rewards-table.spec.tsx | 63 +++++ .../epoch-individual-rewards-table.tsx | 111 +++++++++ .../epoch-individual-rewards.tsx} | 38 ++- ...rate-epoch-individual-rewards-list.spec.ts | 235 ++++++++++++++++++ .../generate-epoch-individual-rewards-list.ts | 79 ++++++ .../epoch-total-rewards-table.spec.tsx | 45 +++- .../epoch-total-rewards-table.tsx | 218 +++------------- ...ch-rewards.tsx => epoch-total-rewards.tsx} | 10 +- .../generate-epoch-total-rewards-list.spec.ts | 115 ++++----- .../generate-epoch-total-rewards-list.ts | 47 +++- .../src/routes/rewards/home/rewards-page.tsx | 55 ++-- .../shared-rewards-table-assets.tsx | 125 ++++++++++ .../src/components/toggle/toggle.tsx | 6 +- 18 files changed, 892 insertions(+), 406 deletions(-) create mode 100644 apps/token/src/routes/rewards/connect-to-see-rewards.spec.tsx create mode 100644 apps/token/src/routes/rewards/connect-to-see-rewards.tsx delete mode 100644 apps/token/src/routes/rewards/epoch-individual-awards/reward-table.tsx create mode 100644 apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.spec.tsx create mode 100644 apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.tsx rename apps/token/src/routes/rewards/{epoch-individual-awards/reward-info.tsx => epoch-individual-rewards/epoch-individual-rewards.tsx} (60%) create mode 100644 apps/token/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts create mode 100644 apps/token/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts rename apps/token/src/routes/rewards/epoch-total-rewards/{epoch-rewards.tsx => epoch-total-rewards.tsx} (75%) create mode 100644 apps/token/src/routes/rewards/shared-rewards-table-assets/shared-rewards-table-assets.tsx diff --git a/apps/token/src/hooks/use-pending-balances-manager.ts b/apps/token/src/hooks/use-pending-balances-manager.ts index 9346fb3db..7eee9bde8 100644 --- a/apps/token/src/hooks/use-pending-balances-manager.ts +++ b/apps/token/src/hooks/use-pending-balances-manager.ts @@ -1,7 +1,6 @@ -import type { Event } from 'ethers'; import uniqBy from 'lodash/uniqBy'; - -import create from 'zustand'; +import { create } from 'zustand'; +import type { Event } from 'ethers'; export type PendingTxsStore = { pendingBalances: Event[]; diff --git a/apps/token/src/i18n/translations/dev.json b/apps/token/src/i18n/translations/dev.json index 5c71b2a81..19514210e 100644 --- a/apps/token/src/i18n/translations/dev.json +++ b/apps/token/src/i18n/translations/dev.json @@ -432,6 +432,7 @@ "associatedWithVegaKeys": "Associated with Vega keys", "thisEpoch": "This Epoch", "nextEpoch": "Next epoch", + "toSeeYourRewardsConnectYourWallet": "TO SEE YOUR REWARDS, CONNECT YOUR WALLET", "rewardsIntro": "Earn rewards and infrastructure fees for trading and maintaining the network.", "rewardsCallout": "Rewards are credited {{duration}} after the epoch ends.", "rewardsCalloutDetail": "This delay is set by a network parameter", @@ -457,6 +458,7 @@ "rewardsColMarketCreationHeader": "MARKET CREATION", "rewardsColMarketCreationTooltip": "Market creation rewards are paid out to the creator of any market that exceeds a set threshold of cumulative volume in a given epoch, currently [rewards.marketCreationQuantumMultiple]", "rewardsColTotalHeader": "TOTAL", + "ofTotalDistributed": "of total distributed", "checkBackSoon": "Check back soon", "yourStake": "Your stake", "reward": "Reward", diff --git a/apps/token/src/routes/rewards/connect-to-see-rewards.spec.tsx b/apps/token/src/routes/rewards/connect-to-see-rewards.spec.tsx new file mode 100644 index 000000000..b6e0060dc --- /dev/null +++ b/apps/token/src/routes/rewards/connect-to-see-rewards.spec.tsx @@ -0,0 +1,25 @@ +import { render } from '@testing-library/react'; +import { AppStateProvider } from '../../contexts/app-state/app-state-provider'; +import { ConnectToSeeRewards } from './connect-to-see-rewards'; + +describe('ConnectToSeeRewards', () => { + it('should render button correctly', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('connect-to-vega-wallet-btn')).toBeInTheDocument(); + }); + + it('should render the correct text', () => { + const { getByText } = render( + + + + ); + expect( + getByText('TO SEE YOUR REWARDS, CONNECT YOUR WALLET') + ).toBeInTheDocument(); + }); +}); diff --git a/apps/token/src/routes/rewards/connect-to-see-rewards.tsx b/apps/token/src/routes/rewards/connect-to-see-rewards.tsx new file mode 100644 index 000000000..c76fc4afa --- /dev/null +++ b/apps/token/src/routes/rewards/connect-to-see-rewards.tsx @@ -0,0 +1,40 @@ +import classNames from 'classnames'; +import { useTranslation } from 'react-i18next'; +import { useVegaWalletDialogStore } from '@vegaprotocol/wallet'; +import { Button } from '@vegaprotocol/ui-toolkit'; +import { + AppStateActionType, + useAppState, +} from '../../contexts/app-state/app-state-context'; +import { SubHeading } from '../../components/heading'; + +export const ConnectToSeeRewards = () => { + const { appDispatch } = useAppState(); + const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({ + openVegaWalletDialog: store.openVegaWalletDialog, + })); + const { t } = useTranslation(); + + const classes = classNames( + 'flex flex-col items-center justify-center h-[300px] w-full', + 'border border-vega-dark-200' + ); + + return ( +
+ + +
+ ); +}; diff --git a/apps/token/src/routes/rewards/epoch-individual-awards/reward-table.tsx b/apps/token/src/routes/rewards/epoch-individual-awards/reward-table.tsx deleted file mode 100644 index 1619d5e49..000000000 --- a/apps/token/src/routes/rewards/epoch-individual-awards/reward-table.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useAppState } from '../../../contexts/app-state/app-state-context'; -import { formatNumber, toBigNum } from '@vegaprotocol/react-helpers'; -import { BigNumber } from '../../../lib/bignumber'; -import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; -import { format } from 'date-fns'; -import { DATE_FORMAT_DETAILED } from '../../../lib/date-formats'; -import type { - DelegationFieldsFragment, - RewardFieldsFragment, -} from '../home/__generated__/Rewards'; - -interface RewardTableProps { - reward: RewardFieldsFragment; - delegations: DelegationFieldsFragment[] | []; -} - -export const RewardTable = ({ reward, delegations }: RewardTableProps) => { - const { t } = useTranslation(); - const { - appState: { decimals }, - } = useAppState(); - - // Get your stake for epoch in which you have rewards - const stakeForEpoch = useMemo(() => { - if (!delegations.length) return '0'; - - const delegationsForEpoch = delegations - .filter((d) => d.epoch.toString() === reward.epoch.id) - .map((d) => toBigNum(d.amount, decimals)); - - if (delegationsForEpoch.length) { - return BigNumber.sum.apply(null, [ - new BigNumber(0), - ...delegationsForEpoch, - ]); - } - - return new BigNumber(0); - }, [decimals, delegations, reward.epoch.id]); - - return ( -
-

- {t('Epoch')} {reward.epoch.id} -

- - - {t('rewardType')} - {reward.rewardType} - - - {t('yourStake')} - {stakeForEpoch.toString()} - - - {t('reward')} - - {formatNumber(toBigNum(reward.amount, decimals))}{' '} - {reward.asset.symbol} - - - - {t('shareOfReward')} - - {new BigNumber(reward.percentageOfTotal).dp(2).toString()}% - - - - {t('received')} - - {format(new Date(reward.receivedAt), DATE_FORMAT_DETAILED)} - - - -
- ); -}; diff --git a/apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.spec.tsx b/apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.spec.tsx new file mode 100644 index 000000000..4b84c3436 --- /dev/null +++ b/apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.spec.tsx @@ -0,0 +1,63 @@ +import { render } from '@testing-library/react'; +import { AppStateProvider } from '../../../contexts/app-state/app-state-provider'; +import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table'; + +const mockData = { + epoch: '4441', + rewards: [ + { + asset: 'tDAI', + totalAmount: '5', + rewardTypes: { + ACCOUNT_TYPE_GLOBAL_REWARD: { + amount: '0', + percentageOfTotal: '0', + }, + ACCOUNT_TYPE_FEES_INFRASTRUCTURE: { + amount: '5', + percentageOfTotal: '0.00305237260923', + }, + ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES: { + amount: '0', + percentageOfTotal: '0', + }, + ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES: { + amount: '0', + percentageOfTotal: '0', + }, + ACCOUNT_TYPE_FEES_LIQUIDITY: { + amount: '0', + percentageOfTotal: '0', + }, + ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: { + amount: '0', + percentageOfTotal: '0', + }, + }, + }, + ], +}; + +describe('EpochIndividualRewardsTable', () => { + it('should render correctly', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('epoch-individual-rewards-table')).toBeInTheDocument(); + expect(getByTestId('individual-rewards-asset')).toBeInTheDocument(); + expect(getByTestId('ACCOUNT_TYPE_GLOBAL_REWARD')).toBeInTheDocument(); + expect(getByTestId('ACCOUNT_TYPE_FEES_INFRASTRUCTURE')).toBeInTheDocument(); + expect( + getByTestId('ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES') + ).toBeInTheDocument(); + expect( + getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES') + ).toBeInTheDocument(); + expect(getByTestId('ACCOUNT_TYPE_FEES_LIQUIDITY')).toBeInTheDocument(); + expect( + getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS') + ).toBeInTheDocument(); + }); +}); diff --git a/apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.tsx b/apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.tsx new file mode 100644 index 000000000..60f9affb2 --- /dev/null +++ b/apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards-table.tsx @@ -0,0 +1,111 @@ +import { formatNumber, toBigNum } from '@vegaprotocol/react-helpers'; +import { Tooltip } from '@vegaprotocol/ui-toolkit'; +import { useAppState } from '../../../contexts/app-state/app-state-context'; +import { + rowGridItemStyles, + RewardsTable, +} from '../shared-rewards-table-assets/shared-rewards-table-assets'; +import type { EpochIndividualReward } from './generate-epoch-individual-rewards-list'; +import { useTranslation } from 'react-i18next'; + +interface EpochIndividualRewardsGridProps { + data: EpochIndividualReward; +} + +interface RewardItemProps { + value: string; + percentageOfTotal?: string; + dataTestId: string; + last?: boolean; +} + +const DisplayReward = ({ + reward, + percentageOfTotal, +}: { + reward: string; + percentageOfTotal?: string; +}) => { + const { t } = useTranslation(); + const { + appState: { decimals }, + } = useAppState(); + + if (Number(reward) === 0) { + return -; + } + + return ( + + {formatNumber(toBigNum(reward, decimals), decimals)} + {percentageOfTotal && ( + + ({percentageOfTotal}% {t('ofTotalDistributed')}) + + )} + + } + > + + + ); +}; + +const RewardItem = ({ + value, + percentageOfTotal, + dataTestId, + last, +}: RewardItemProps) => ( +
+
+
+ +
+
+
+); + +export const EpochIndividualRewardsTable = ({ + data, +}: EpochIndividualRewardsGridProps) => { + return ( + + {data.rewards.map(({ asset, rewardTypes, totalAmount }, i) => ( +
+
+ {asset} +
+ {Object.entries(rewardTypes).map( + ([key, { amount, percentageOfTotal }]) => ( + + ) + )} + +
+ ))} +
+ ); +}; diff --git a/apps/token/src/routes/rewards/epoch-individual-awards/reward-info.tsx b/apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx similarity index 60% rename from apps/token/src/routes/rewards/epoch-individual-awards/reward-info.tsx rename to apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx index 877d55402..a3a3ec01d 100644 --- a/apps/token/src/routes/rewards/epoch-individual-awards/reward-info.tsx +++ b/apps/token/src/routes/rewards/epoch-individual-rewards/epoch-individual-rewards.tsx @@ -5,9 +5,10 @@ import { removePaginationWrapper } from '@vegaprotocol/react-helpers'; import { useRewardsQuery } from '../home/__generated__/Rewards'; import { ENV } from '../../../config'; import { useVegaWallet } from '@vegaprotocol/wallet'; -import { RewardTable } from './reward-table'; +import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table'; +import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list'; -export const RewardInfo = () => { +export const EpochIndividualRewards = () => { const { t } = useTranslation(); const { pubKey } = useVegaWallet(); const { delegationsPagination } = ENV; @@ -30,13 +31,10 @@ export const RewardInfo = () => { return removePaginationWrapper(data.party.rewardsConnection.edges); }, [data]); - const delegations = useMemo(() => { - if (!data?.party || !data.party.delegationsConnection?.edges?.length) { - return []; - } - - return removePaginationWrapper(data.party.delegationsConnection.edges); - }, [data]); + const epochIndividualRewardSummaries = useMemo(() => { + if (!data?.party) return []; + return generateEpochIndividualRewardsList(rewards); + }, [data?.party, rewards]); return ( { data={data} render={() => (
-

- {t('Connected Vega key')}: {pubKey} +

+ {t('Connected Vega key')}:{' '} + {pubKey}

- {rewards.length ? ( - rewards.map((reward, i) => { - if (!reward) return null; - return ( - ( + - ); - }) + ) + ) ) : (

{t('noRewards')}

)} diff --git a/apps/token/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts b/apps/token/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts new file mode 100644 index 000000000..9ac901f4f --- /dev/null +++ b/apps/token/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.spec.ts @@ -0,0 +1,235 @@ +import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list'; +import { AccountType } from '@vegaprotocol/types'; +import type { RewardFieldsFragment } from '../home/__generated__/Rewards'; + +describe('generateEpochIndividualRewardsList', () => { + const reward1: RewardFieldsFragment = { + rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, + amount: '100', + percentageOfTotal: '0.1', + receivedAt: new Date(), + asset: { id: 'usd', symbol: 'USD' }, + party: { id: 'blah' }, + epoch: { id: '1' }, + }; + + const reward2: RewardFieldsFragment = { + rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, + amount: '50', + percentageOfTotal: '0.05', + receivedAt: new Date(), + asset: { id: 'eur', symbol: 'EUR' }, + party: { id: 'blah' }, + epoch: { id: '2' }, + }; + + const reward3: RewardFieldsFragment = { + rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY, + amount: '200', + percentageOfTotal: '0.2', + receivedAt: new Date(), + asset: { id: 'gbp', symbol: 'GBP' }, + party: { id: 'blah' }, + epoch: { id: '2' }, + }; + + const reward4: RewardFieldsFragment = { + rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY, + amount: '100', + percentageOfTotal: '0.1', + receivedAt: new Date(), + asset: { id: 'usd', symbol: 'USD' }, + party: { id: 'blah' }, + epoch: { id: '1' }, + }; + + const rewardWrongType: RewardFieldsFragment = { + rewardType: AccountType.ACCOUNT_TYPE_INSURANCE, + amount: '50', + percentageOfTotal: '0.05', + receivedAt: new Date(), + asset: { id: 'eur', symbol: 'EUR' }, + party: { id: 'blah' }, + epoch: { id: '2' }, + }; + + it('should return an empty array if no rewards are provided', () => { + expect(generateEpochIndividualRewardsList([])).toEqual([]); + }); + + it('should filter out any rewards of the wrong type', () => { + const result = generateEpochIndividualRewardsList([rewardWrongType]); + + expect(result).toEqual([]); + }); + + it('should return reward in the correct format', () => { + const result = generateEpochIndividualRewardsList([reward1]); + + expect(result[0]).toEqual({ + epoch: '1', + rewards: [ + { + asset: 'USD', + totalAmount: '100', + rewardTypes: { + [AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: { + amount: '100', + percentageOfTotal: '0.1', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: { + amount: '0', + percentageOfTotal: '0', + }, + }, + }, + ], + }); + }); + + it('should return an array sorted by epoch descending', () => { + const rewards = [reward1, reward2, reward3, reward4]; + const result1 = generateEpochIndividualRewardsList(rewards); + + expect(result1[0].epoch).toEqual('2'); + expect(result1[1].epoch).toEqual('1'); + + const reorderedRewards = [reward4, reward3, reward2, reward1]; + const result2 = generateEpochIndividualRewardsList(reorderedRewards); + + expect(result2[0].epoch).toEqual('2'); + expect(result2[1].epoch).toEqual('1'); + }); + + it('correctly calculates the total value of rewards for an asset', () => { + const rewards = [reward1, reward4]; + const result = generateEpochIndividualRewardsList(rewards); + + expect(result[0].rewards[0].totalAmount).toEqual('200'); + }); + + it('returns data in the expected shape', () => { + // Just sanity checking the whole structure here + const rewards = [reward1, reward2, reward3, reward4]; + const result = generateEpochIndividualRewardsList(rewards); + + expect(result).toEqual([ + { + epoch: '2', + rewards: [ + { + asset: 'EUR', + totalAmount: '50', + rewardTypes: { + [AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: { + amount: '50', + percentageOfTotal: '0.05', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: { + amount: '0', + percentageOfTotal: '0', + }, + }, + }, + { + asset: 'GBP', + totalAmount: '200', + rewardTypes: { + [AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: { + amount: '200', + percentageOfTotal: '0.2', + }, + [AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: { + amount: '0', + percentageOfTotal: '0', + }, + }, + }, + ], + }, + { + epoch: '1', + rewards: [ + { + asset: 'USD', + totalAmount: '200', + rewardTypes: { + [AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: { + amount: '100', + percentageOfTotal: '0.1', + }, + [AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: { + amount: '100', + percentageOfTotal: '0.1', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: { + amount: '0', + percentageOfTotal: '0', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: { + amount: '0', + percentageOfTotal: '0', + }, + }, + }, + ], + }, + ]); + }); +}); diff --git a/apps/token/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts b/apps/token/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts new file mode 100644 index 000000000..712d9f5c5 --- /dev/null +++ b/apps/token/src/routes/rewards/epoch-individual-rewards/generate-epoch-individual-rewards-list.ts @@ -0,0 +1,79 @@ +import { BigNumber } from '../../../lib/bignumber'; +import { RowAccountTypes } from '../shared-rewards-table-assets/shared-rewards-table-assets'; +import type { RewardFieldsFragment } from '../home/__generated__/Rewards'; +import type { AccountType } from '@vegaprotocol/types'; + +export interface EpochIndividualReward { + epoch: string; + rewards: { + asset: string; + totalAmount: string; + rewardTypes: { + [key in AccountType]?: { + amount: string; + percentageOfTotal: string; + }; + }; + }[]; +} + +const accountTypes = Object.keys(RowAccountTypes); + +const emptyRowAccountTypes = accountTypes.map((type) => [ + type, + { + amount: '0', + percentageOfTotal: '0', + }, +]); + +export const generateEpochIndividualRewardsList = ( + rewards: RewardFieldsFragment[] +) => { + // We take the rewards and aggregate them by epoch and asset. + const epochIndividualRewards = rewards.reduce((map, reward) => { + const epochId = reward.epoch.id; + const assetName = reward.asset.symbol; + const rewardType = reward.rewardType; + const amount = reward.amount; + const percentageOfTotal = reward.percentageOfTotal; + + // if the rewardType is not of a type we display in the table, we skip it. + if (!accountTypes.includes(rewardType)) { + return map; + } + + if (!map.has(epochId)) { + map.set(epochId, { epoch: epochId, rewards: [] }); + } + + const epoch = map.get(epochId); + + let asset = epoch?.rewards.find((r) => r.asset === assetName); + + if (!asset) { + asset = { + asset: assetName, + totalAmount: '0', + rewardTypes: Object.fromEntries(emptyRowAccountTypes), + }; + epoch?.rewards.push(asset); + } + + asset.rewardTypes[rewardType] = { amount, percentageOfTotal }; + + // totalAmount is the sum of all rewardTypes amounts + asset.totalAmount = Object.values(asset.rewardTypes).reduce( + (sum, rewardType) => { + return new BigNumber(sum).plus(rewardType.amount).toString(); + }, + '0' + ); + + return map; + }, new Map()); + + return Array.from(epochIndividualRewards.values()).sort( + (a, b) => Number(b.epoch) - Number(a.epoch) + ); +}; diff --git a/apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.spec.tsx b/apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.spec.tsx index 911d391a8..6c8cb7394 100644 --- a/apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.spec.tsx +++ b/apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.spec.tsx @@ -1,4 +1,5 @@ import { render } from '@testing-library/react'; +import { AppStateProvider } from '../../../contexts/app-state/app-state-provider'; import { EpochTotalRewardsTable } from './epoch-total-rewards-table'; import { AccountType } from '@vegaprotocol/types'; @@ -10,10 +11,30 @@ const mockData = { 'b340c130096819428a62e5df407fd6abe66e444b89ad64f670beb98621c9c663', name: 'tDAI TEST', rewards: [ + { + rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, + amount: '0', + }, { rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE, amount: '295', }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES, + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES, + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY, + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS, + amount: '0', + }, ], totalAmount: '295', }, @@ -22,15 +43,25 @@ const mockData = { describe('EpochTotalRewardsTable', () => { it('should render correctly', () => { - const { getByTestId } = render(); + const { getByTestId } = render( + + + + ); expect(getByTestId('epoch-total-rewards-table')).toBeInTheDocument(); expect(getByTestId('asset')).toBeInTheDocument(); - expect(getByTestId('global')).toBeInTheDocument(); - expect(getByTestId('infra')).toBeInTheDocument(); - expect(getByTestId('taker')).toBeInTheDocument(); - expect(getByTestId('maker')).toBeInTheDocument(); - expect(getByTestId('liquidity')).toBeInTheDocument(); - expect(getByTestId('market-maker')).toBeInTheDocument(); + expect(getByTestId('ACCOUNT_TYPE_GLOBAL_REWARD')).toBeInTheDocument(); + expect(getByTestId('ACCOUNT_TYPE_FEES_INFRASTRUCTURE')).toBeInTheDocument(); + expect( + getByTestId('ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES') + ).toBeInTheDocument(); + expect( + getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES') + ).toBeInTheDocument(); + expect(getByTestId('ACCOUNT_TYPE_FEES_LIQUIDITY')).toBeInTheDocument(); + expect( + getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS') + ).toBeInTheDocument(); expect(getByTestId('total')).toBeInTheDocument(); }); }); diff --git a/apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.tsx b/apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.tsx index 2b02d38fe..094afa8ac 100644 --- a/apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.tsx +++ b/apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards-table.tsx @@ -1,19 +1,14 @@ -import classNames from 'classnames'; -import { useTranslation } from 'react-i18next'; -import { formatNumber } from '@vegaprotocol/react-helpers'; -import { Tooltip, Icon } from '@vegaprotocol/ui-toolkit'; -import { AccountType } from '@vegaprotocol/types'; -import { SubHeading } from '../../../components/heading'; -import type { AggregatedEpochSummary } from './generate-epoch-total-rewards-list'; +import { formatNumber, toBigNum } from '@vegaprotocol/react-helpers'; +import { Tooltip } from '@vegaprotocol/ui-toolkit'; +import { useAppState } from '../../../contexts/app-state/app-state-context'; +import { + rowGridItemStyles, + RewardsTable, +} from '../shared-rewards-table-assets/shared-rewards-table-assets'; +import type { EpochTotalSummary } from './generate-epoch-total-rewards-list'; interface EpochTotalRewardsGridProps { - data: AggregatedEpochSummary; -} - -interface ColumnHeaderProps { - title: string; - tooltipContent?: string; - className?: string; + data: EpochTotalSummary; } interface RewardItemProps { @@ -22,61 +17,28 @@ interface RewardItemProps { last?: boolean; } -const displayReward = (reward: string) => { +const DisplayReward = ({ reward }: { reward: string }) => { + const { + appState: { decimals }, + } = useAppState(); + if (Number(reward) === 0) { - return 0; + return -; } - if (reward.split('.')[1] && reward.split('.')[1].length > 4) { - return ( - - - - ); - } - - return {formatNumber(reward)}; + return ( + + + + ); }; -const gridStyles = classNames( - 'grid grid-cols-[repeat(8,minmax(100px,auto))] max-w-full overflow-auto', - `border-t border-vega-dark-200`, - 'text-sm' -); - -const headerGridItemStyles = (last = false) => - classNames('border-r border-b border-b-vega-dark-200', 'py-3 px-5', { - 'border-r-vega-dark-150': !last, - 'border-r-0': last, - }); - -const rowGridItemStyles = (last = false) => - classNames('relative', 'border-r border-b border-b-vega-dark-150', { - 'border-r-vega-dark-150': !last, - 'border-r-0': last, - }); - -const ColumnHeader = ({ - title, - tooltipContent, - className, -}: ColumnHeaderProps) => ( -
-

{title}

- {tooltipContent && ( - - - - )} -
-); - const RewardItem = ({ value, dataTestId, last }: RewardItemProps) => (
-
{displayReward(value)}
+
+ +
); @@ -84,129 +46,19 @@ const RewardItem = ({ value, dataTestId, last }: RewardItemProps) => ( export const EpochTotalRewardsTable = ({ data, }: EpochTotalRewardsGridProps) => { - const { t } = useTranslation(); - - const rowData = data.assetRewards.map(({ name, rewards, totalAmount }) => ({ - name, - ACCOUNT_TYPE_GLOBAL_REWARD: - rewards - .filter((r) => r.rewardType === AccountType.ACCOUNT_TYPE_GLOBAL_REWARD) - .map((r) => r.amount)[0] || '0', - ACCOUNT_TYPE_FEES_INFRASTRUCTURE: - rewards - .filter( - (r) => r.rewardType === AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE - ) - .map((r) => r.amount)[0] || '0', - ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES: - rewards - .filter( - (r) => - r.rewardType === AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES - ) - .map((r) => r.amount)[0] || '0', - ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES: - rewards - .filter( - (r) => - r.rewardType === AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES - ) - .map((r) => r.amount)[0] || '0', - ACCOUNT_TYPE_FEES_LIQUIDITY: - rewards - .filter((r) => r.rewardType === AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY) - .map((r) => r.amount)[0] || '0', - ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: - rewards - .filter( - (r) => - r.rewardType === AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS - ) - .map((r) => r.amount)[0] || '0', - totalAmount: totalAmount, - })); - return ( -
- - -
- - - - - - - - - - {rowData.map((row, i) => ( -
-
- {row.name} -
- - - - - - - + + {data.assetRewards.map(({ name, rewards, totalAmount }, i) => ( +
+
+ {name}
- ))} -
-
+ {rewards.map(({ rewardType, amount }, i) => ( + + ))} + +
+ ))} + ); }; diff --git a/apps/token/src/routes/rewards/epoch-total-rewards/epoch-rewards.tsx b/apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards.tsx similarity index 75% rename from apps/token/src/routes/rewards/epoch-total-rewards/epoch-rewards.tsx rename to apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards.tsx index 57240b1c9..10410e5b7 100644 --- a/apps/token/src/routes/rewards/epoch-total-rewards/epoch-rewards.tsx +++ b/apps/token/src/routes/rewards/epoch-total-rewards/epoch-total-rewards.tsx @@ -5,7 +5,7 @@ import { generateEpochTotalRewardsList } from './generate-epoch-total-rewards-li import { NoRewards } from '../no-rewards'; import { EpochTotalRewardsTable } from './epoch-total-rewards-table'; -export const EpochRewards = () => { +export const EpochTotalRewards = () => { const { data, loading, error, refetch } = useEpochAssetsRewardsQuery({ variables: { epochRewardSummariesPagination: { @@ -15,7 +15,7 @@ export const EpochRewards = () => { }); useRefreshAfterEpoch(data?.epoch.timestamps.expiry, refetch); - const epochRewardSummaries = generateEpochTotalRewardsList(data) || []; + const epochTotalRewardSummaries = generateEpochTotalRewardsList(data) || []; return ( { className="max-w-full overflow-auto" data-testid="epoch-rewards-total" > - {epochRewardSummaries.length === 0 ? ( + {epochTotalRewardSummaries.length === 0 ? ( ) : ( <> - {epochRewardSummaries.map((aggregatedEpochSummary) => ( - + {epochTotalRewardSummaries.map((epochTotalSummary, index) => ( + ))} )} diff --git a/apps/token/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.spec.ts b/apps/token/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.spec.ts index 429a2d75c..51b9d6e31 100644 --- a/apps/token/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.spec.ts +++ b/apps/token/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.spec.ts @@ -61,7 +61,7 @@ describe('generateEpochAssetRewardsList', () => { expect(result).toEqual([]); }); - it('should return an array of unnamed assets if no assets are provided (should not happen)', () => { + it('should return an array of unnamed assets if no asset names are provided (should not happen)', () => { const epochData = { assetsConnection: { edges: [], @@ -72,18 +72,10 @@ describe('generateEpochAssetRewardsList', () => { node: { epoch: 1, assetId: '1', - rewardType: AccountType.ACCOUNT_TYPE_INSURANCE, + rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, amount: '123', }, }, - { - node: { - epoch: 2, - assetId: '1', - rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY, - amount: '5', - }, - }, ], }, epoch: { @@ -104,30 +96,34 @@ describe('generateEpochAssetRewardsList', () => { name: '', rewards: [ { - rewardType: AccountType.ACCOUNT_TYPE_INSURANCE, + rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, amount: '123', }, + { + rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE, + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES, + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES, + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY, + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS, + amount: '0', + }, ], totalAmount: '123', }, ], }, - { - epoch: 2, - assetRewards: [ - { - assetId: '1', - name: '', - rewards: [ - { - rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY, - amount: '5', - }, - ], - totalAmount: '5', - }, - ], - }, ]); }); @@ -155,7 +151,7 @@ describe('generateEpochAssetRewardsList', () => { node: { epoch: 1, assetId: '1', - rewardType: AccountType.ACCOUNT_TYPE_INSURANCE, + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES, amount: '123', }, }, @@ -167,22 +163,6 @@ describe('generateEpochAssetRewardsList', () => { amount: '100', }, }, - { - node: { - epoch: 1, - assetId: '2', - rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE, - amount: '17.9873', - }, - }, - { - node: { - epoch: 1, - assetId: '2', - rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY, - amount: '1', - }, - }, { node: { epoch: 2, @@ -211,30 +191,31 @@ describe('generateEpochAssetRewardsList', () => { name: 'Asset 1', rewards: [ { - rewardType: AccountType.ACCOUNT_TYPE_INSURANCE, - amount: '123', + rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, + amount: '0', }, { rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE, amount: '100', }, - ], - totalAmount: '223', - }, - { - assetId: '2', - name: 'Asset 2', - rewards: [ { - rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE, - amount: '17.9873', + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES, + amount: '123', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES, + amount: '0', }, { rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY, - amount: '1', + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS, + amount: '0', }, ], - totalAmount: '18.9873', + totalAmount: '223', }, ], }, @@ -245,10 +226,30 @@ describe('generateEpochAssetRewardsList', () => { assetId: '1', name: 'Asset 1', rewards: [ + { + rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD, + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE, + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES, + amount: '0', + }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES, + amount: '0', + }, { rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY, amount: '5', }, + { + rewardType: AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS, + amount: '0', + }, ], totalAmount: '5', }, diff --git a/apps/token/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.ts b/apps/token/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.ts index 2f86ccd4b..35407846c 100644 --- a/apps/token/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.ts +++ b/apps/token/src/routes/rewards/epoch-total-rewards/generate-epoch-total-rewards-list.ts @@ -3,6 +3,8 @@ import type { EpochRewardSummaryFieldsFragment, } from '../home/__generated__/Rewards'; import { removePaginationWrapper } from '@vegaprotocol/react-helpers'; +import { RowAccountTypes } from '../shared-rewards-table-assets/shared-rewards-table-assets'; +import type { AccountType } from '@vegaprotocol/types'; interface EpochSummaryWithNamedReward extends EpochRewardSummaryFieldsFragment { name: string; @@ -18,11 +20,16 @@ export interface AggregatedEpochRewardSummary { totalAmount: string; } -export interface AggregatedEpochSummary { +export interface EpochTotalSummary { epoch: EpochRewardSummaryFieldsFragment['epoch']; assetRewards: AggregatedEpochRewardSummary[]; } +const emptyRowAccountTypes = Object.keys(RowAccountTypes).map((type) => ({ + rewardType: type as AccountType, + amount: '0', +})); + export const generateEpochTotalRewardsList = ( epochData: EpochAssetsRewardsQuery | undefined ) => { @@ -58,7 +65,7 @@ export const generateEpochTotalRewardsList = ( }, [] as EpochSummaryWithNamedReward[][]); // Now aggregate the array of arrays of epoch summaries by asset rewards. - const aggregatedEpochSummaries: AggregatedEpochSummary[] = + const epochTotalRewards: EpochTotalSummary[] = aggregatedEpochSummariesByEpochNumber.map((epochSummaries) => { const assetRewards = epochSummaries.reduce((acc, epochSummary) => { const assetRewardIndex = acc.findIndex( @@ -72,18 +79,36 @@ export const generateEpochTotalRewardsList = ( assetId: epochSummary.assetId, name: epochSummary.name, rewards: [ - { - rewardType: epochSummary.rewardType, - amount: epochSummary.amount, - }, + ...emptyRowAccountTypes.map((emptyRowAccountType) => { + if ( + emptyRowAccountType.rewardType === epochSummary.rewardType + ) { + return { + rewardType: epochSummary.rewardType, + amount: epochSummary.amount, + }; + } else { + return emptyRowAccountType; + } + }), ], totalAmount: epochSummary.amount, }); } else { - acc[assetRewardIndex].rewards.push({ - rewardType: epochSummary.rewardType, - amount: epochSummary.amount, - }); + acc[assetRewardIndex].rewards = acc[assetRewardIndex].rewards.map( + (reward) => { + if (reward.rewardType === epochSummary.rewardType) { + return { + rewardType: epochSummary.rewardType, + amount: ( + Number(reward.amount) + Number(epochSummary.amount) + ).toString(), + }; + } else { + return reward; + } + } + ); acc[assetRewardIndex].totalAmount = ( Number(acc[assetRewardIndex].totalAmount) + Number(epochSummary.amount) @@ -99,5 +124,5 @@ export const generateEpochTotalRewardsList = ( }; }); - return aggregatedEpochSummaries; + return epochTotalRewards; }; diff --git a/apps/token/src/routes/rewards/home/rewards-page.tsx b/apps/token/src/routes/rewards/home/rewards-page.tsx index 5acf2ae50..b65359786 100644 --- a/apps/token/src/routes/rewards/home/rewards-page.tsx +++ b/apps/token/src/routes/rewards/home/rewards-page.tsx @@ -1,34 +1,30 @@ // @ts-ignore No types available for duration-js import Duration from 'duration-js'; -import React, { useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { formatDistance } from 'date-fns'; import { useTranslation } from 'react-i18next'; import { - Button, Callout, Intent, AsyncRenderer, Toggle, ExternalLink, } from '@vegaprotocol/ui-toolkit'; -import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; +import { useVegaWallet } from '@vegaprotocol/wallet'; import { useNetworkParams, NetworkParams, createDocsLinks, } from '@vegaprotocol/react-helpers'; -import { - AppStateActionType, - useAppState, -} from '../../../contexts/app-state/app-state-context'; import { useEpochQuery } from './__generated__/Rewards'; import { EpochCountdown } from '../../../components/epoch-countdown'; import { Heading, SubHeading } from '../../../components/heading'; -import { RewardInfo } from '../epoch-individual-awards/reward-info'; -import { EpochRewards } from '../epoch-total-rewards/epoch-rewards'; +import { EpochIndividualRewards } from '../epoch-individual-rewards/epoch-individual-rewards'; import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch'; import { useEnvironment } from '@vegaprotocol/environment'; +import { ConnectToSeeRewards } from '../connect-to-see-rewards'; +import { EpochTotalRewards } from '../epoch-total-rewards/epoch-total-rewards'; type RewardsView = 'total' | 'individual'; @@ -39,25 +35,21 @@ export const RewardsPage = () => { const [toggleRewardsView, setToggleRewardsView] = useState('total'); - const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({ - openVegaWalletDialog: store.openVegaWalletDialog, - })); - const { appDispatch } = useAppState(); - - const { - params, - loading: paramsLoading, - error: paramsError, - } = useNetworkParams([NetworkParams.reward_staking_delegation_payoutDelay]); - const { data: epochData, loading: epochLoading, error: epochError, refetch, } = useEpochQuery(); + useRefreshAfterEpoch(epochData?.epoch.timestamps.expiry, refetch); + const { + params, + loading: paramsLoading, + error: paramsError, + } = useNetworkParams([NetworkParams.reward_staking_delegation_payoutDelay]); + const payoutDuration = useMemo(() => { if (!params) { return 0; @@ -103,8 +95,8 @@ export const RewardsPage = () => {
) : null} - {epochData && - epochData.epoch.id && + {epochData?.epoch && + epochData.epoch?.id && epochData.epoch.timestamps.start && epochData.epoch.timestamps.expiry && (
@@ -148,26 +140,13 @@ export const RewardsPage = () => {
{toggleRewardsView === 'total' ? ( - + ) : (
{pubKey && pubKeys?.length ? ( - + ) : ( -
- -
+ )}
)} diff --git a/apps/token/src/routes/rewards/shared-rewards-table-assets/shared-rewards-table-assets.tsx b/apps/token/src/routes/rewards/shared-rewards-table-assets/shared-rewards-table-assets.tsx new file mode 100644 index 000000000..97405cd8d --- /dev/null +++ b/apps/token/src/routes/rewards/shared-rewards-table-assets/shared-rewards-table-assets.tsx @@ -0,0 +1,125 @@ +import classNames from 'classnames'; +import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit'; +import { useTranslation } from 'react-i18next'; +import type { ReactNode } from 'react'; +import { SubHeading } from '../../../components/heading'; +import { AccountType } from '@vegaprotocol/types'; + +// This is the data structure that matters for defining which Account types +// are displayed in the rewards tables. It sets column titles and tooltips, +// and is used to filter the data that is passed to functions to generate +// the table rows. It's important to preserve the order. +export const RowAccountTypes = { + [AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: { + columnTitle: 'rewardsColStakingHeader', + description: 'rewardsColStakingTooltip', + }, + [AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: { + columnTitle: 'rewardsColInfraHeader', + description: 'rewardsColInfraTooltip', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: { + columnTitle: 'rewardsColPriceTakingHeader', + description: 'rewardsColPriceTakingTooltip', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: { + columnTitle: 'rewardsColPriceMakingHeader', + description: 'rewardsColPriceMakingTooltip', + }, + [AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: { + columnTitle: 'rewardsColLiquidityProvisionHeader', + description: 'rewardsColLiquidityProvisionTooltip', + }, + [AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: { + columnTitle: 'rewardsColMarketCreationHeader', + description: 'rewardsColMarketCreationTooltip', + }, +}; + +interface ColumnHeaderProps { + title: string; + tooltipContent?: string; + className?: string; +} + +const headerGridItemStyles = (last = false) => + classNames('border-r border-b border-b-vega-dark-200', 'py-3 px-5', { + 'border-r-vega-dark-150': !last, + 'border-r-0': last, + }); + +export const rowGridItemStyles = (last = false) => + classNames('relative', 'border-r border-b border-b-vega-dark-150', { + 'border-r-vega-dark-150': !last, + 'border-r-0': last, + }); + +const gridStyles = classNames( + 'grid grid-cols-[repeat(8,minmax(100px,auto))] max-w-full overflow-auto', + `border-t border-vega-dark-200`, + 'text-sm' +); + +const ColumnHeader = ({ + title, + tooltipContent, + className, +}: ColumnHeaderProps) => ( +
+

{title}

+ {tooltipContent && ( + + + + )} +
+); + +const ColumnHeaders = () => { + const { t } = useTranslation(); + return ( +
+ + {Object.values(RowAccountTypes).map(({ columnTitle, description }) => ( + + ))} + +
+ ); +}; + +export interface RewardTableProps { + dataTestId: string; + epoch: number; + children: ReactNode; +} + +// Rewards table children will be the row items. Make sure they contain +// the same number of columns and map to the data of the ColumnHeaders component. +export const RewardsTable = ({ + dataTestId, + epoch, + children, +}: RewardTableProps) => ( +
+ + +
+ + {children} +
+
+); diff --git a/libs/ui-toolkit/src/components/toggle/toggle.tsx b/libs/ui-toolkit/src/components/toggle/toggle.tsx index fc5b028c0..8bb69d44c 100644 --- a/libs/ui-toolkit/src/components/toggle/toggle.tsx +++ b/libs/ui-toolkit/src/components/toggle/toggle.tsx @@ -35,7 +35,7 @@ export const Toggle = ({ ); const radioClasses = classnames('sr-only', 'peer'); const buttonClasses = classnames( - 'relative inline-block w-full text-center', + 'relative inline-flex w-full h-full text-center items-center justify-center', 'peer-checked:rounded-full', { 'peer-checked:bg-neutral-400 dark:peer-checked:bg-white dark:peer-checked:text-black': @@ -76,7 +76,9 @@ export const Toggle = ({ } className={radioClasses} /> - {label} + + {label} + ); })}