Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOV-2993: Vesting reward history #577

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nervous-brooms-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"frontend": patch
---

SOV-2993: Vesting reward history
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RewardHistoryType } from './RewardHistory.types';
import { RewardsEarnedHistory } from './components/RewardsEarnedHistory/RewardsEarnedHistory';
import { StabilityPoolRewards } from './components/StabilityPoolRewards/StabilityPoolRewards';
import { StabilityPoolSubsidies } from './components/StabilityPoolSubsidies/StabilityPoolSubsidies';
import { VestingRewards } from './components/VestingRewards/VestingRewards';

export const RewardHistory: FC = () => {
const [selectedHistoryType, setSelectedHistoryType] = useState(
Expand Down Expand Up @@ -37,6 +38,12 @@ export const RewardHistory: FC = () => {
onChangeRewardHistory={onChangeRewardHistory}
/>
)}
{selectedHistoryType === RewardHistoryType.vestingRewards && (
<VestingRewards
selectedHistoryType={selectedHistoryType}
onChangeRewardHistory={onChangeRewardHistory}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export enum RewardHistoryType {
stabilityPoolSubsidies = 'stabilityPoolSubsidies',
stakingRevenue = 'stakingRevenue',
stakingSubsidies = 'stakingSubsidies',
vestingRewards = 'vestingRewards',
}

export type RewardHistoryProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ export const rewardHistoryOptions = [
value: RewardHistoryType.stakingSubsidies,
label: t(translations.rewardHistory.types.stakingSubsidies),
},
{
value: RewardHistoryType.vestingRewards,
label: t(translations.rewardHistory.types.vestingRewards),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';

import { t } from 'i18next';

import { SupportedTokens } from '@sovryn/contracts';

import { AmountRenderer } from '../../../../2_molecules/AmountRenderer/AmountRenderer';
import { TransactionIdRenderer } from '../../../../2_molecules/TransactionIdRenderer/TransactionIdRenderer';
import { TOKEN_RENDER_PRECISION } from '../../../../../constants/currencies';
import { translations } from '../../../../../locales/i18n';
import { VestingHistoryItem } from '../../../../../utils/graphql/rsk/generated';
import { dateFormat } from '../../../../../utils/helpers';
import { getTransactionType } from './VestingRewards.utils';

const renderAmount = (item: VestingHistoryItem) => {
if (item.amount === '-') {
return '⁠—';
}

return (
<AmountRenderer
value={item.amount}
suffix={SupportedTokens.sov}
precision={TOKEN_RENDER_PRECISION}
dataAttribute="vesting-reward-history-amount"
/>
);
};

export const COLUMNS_CONFIG = [
{
id: 'timestamp',
title: t(translations.common.tables.columnTitles.timestamp),
cellRenderer: (tx: VestingHistoryItem) => dateFormat(tx.timestamp),
sortable: true,
},
{
id: 'action',
title: t(translations.common.tables.columnTitles.transactionType),
cellRenderer: (tx: VestingHistoryItem) => getTransactionType(tx.action),
},
{
id: 'amount',
title: t(translations.common.tables.columnTitles.amount),
cellRenderer: renderAmount,
},
{
id: 'transactionID',
title: t(translations.common.tables.columnTitles.transactionID),
cellRenderer: (tx: VestingHistoryItem) => (
<TransactionIdRenderer
hash={tx.transaction.id}
dataAttribute="vesting-reward-history-tx-hash"
/>
),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';

import { t } from 'i18next';
import { nanoid } from 'nanoid';

import {
ErrorBadge,
ErrorLevel,
NotificationType,
OrderDirection,
OrderOptions,
Pagination,
Select,
Table,
} from '@sovryn/ui';

import { ExportCSV } from '../../../../2_molecules/ExportCSV/ExportCSV';
import {
DEFAULT_HISTORY_FRAME_PAGE_SIZE,
EXPORT_RECORD_LIMIT,
} from '../../../../../constants/general';
import { useNotificationContext } from '../../../../../contexts/NotificationContext';
import { useMaintenance } from '../../../../../hooks/useMaintenance';
import { translations } from '../../../../../locales/i18n';
import { rskClient } from '../../../../../utils/clients';
import {
VestingHistoryItem_OrderBy,
useGetVestingHistoryItemsLazyQuery,
} from '../../../../../utils/graphql/rsk/generated';
import { dateFormat } from '../../../../../utils/helpers';
import { decimalic } from '../../../../../utils/math';
import { RewardHistoryProps } from '../../RewardHistory.types';
import { rewardHistoryOptions } from '../../RewardHistory.utils';
import { COLUMNS_CONFIG } from './VestingRewards.constants';
import { generateRowTitle, getTransactionType } from './VestingRewards.utils';
import { useGetVestingContractsId } from './hooks/useGetVestingContractsId';
import { useGetVestingHistoryItems } from './hooks/useGetVestingHistoryItems';

const pageSize = DEFAULT_HISTORY_FRAME_PAGE_SIZE;

export const VestingRewards: FC<RewardHistoryProps> = ({
selectedHistoryType,
onChangeRewardHistory,
}) => {
const [page, setPage] = useState(0);
const { data: vestings, loading: loadingVestingContracts } =
useGetVestingContractsId();

const { addNotification } = useNotificationContext();
const { checkMaintenance, States } = useMaintenance();

const [orderOptions, setOrderOptions] = useState<OrderOptions>({
orderBy: 'timestamp',
orderDirection: OrderDirection.Desc,
});

const { data, loading } = useGetVestingHistoryItems(
vestings || [],
pageSize,
page,
orderOptions,
);

const [getVestingHistoryItems] = useGetVestingHistoryItemsLazyQuery({
client: rskClient,
});

const onPageChange = useCallback(
(value: number) => {
if (data?.length < pageSize && value > page) {
return;
}
setPage(value);
},
[page, data],
);

const isNextButtonDisabled = useMemo(
() => !loading && data?.length < pageSize && !loadingVestingContracts,
[loading, data, loadingVestingContracts],
);

const exportData = useCallback(async () => {
const { data } = await getVestingHistoryItems({
variables: {
stakers: vestings,
skip: 0,
pageSize: EXPORT_RECORD_LIMIT,
orderBy: VestingHistoryItem_OrderBy.Timestamp,
orderDirection: OrderDirection.Desc,
},
});
let list = data?.vestingHistoryItems || [];

if (!list.length) {
addNotification({
type: NotificationType.warning,
title: t(translations.common.tables.actions.noDataToExport),
content: '',
dismissible: true,
id: nanoid(),
});
}

return list.map(tx => ({
timestamp: dateFormat(tx.timestamp),
transactionType: getTransactionType(tx.action),
amount: decimalic(tx.amount).toString(),
transactionID: tx.transaction.id,
}));
}, [addNotification, vestings, getVestingHistoryItems]);

useEffect(() => {
setPage(0);
}, [orderOptions]);

const exportLocked = checkMaintenance(States.ZERO_EXPORT_CSV);

return (
<>
<div className="flex-row items-center gap-4 mb-7 flex justify-center lg:justify-start">
<Select
dataAttribute={`reward-history-${selectedHistoryType}`}
value={selectedHistoryType}
onChange={onChangeRewardHistory}
options={rewardHistoryOptions}
/>
<div className="flex-row items-center ml-2 gap-4 hidden lg:inline-flex">
<ExportCSV
getData={exportData}
filename="vesting-rewards"
disabled={!data || data.length === 0 || exportLocked}
/>
{exportLocked && (
<ErrorBadge
level={ErrorLevel.Warning}
message={t(translations.maintenanceMode.featureDisabled)}
/>
)}
</div>
</div>
<div className="bg-gray-80 py-4 px-4 rounded">
<Table
setOrderOptions={setOrderOptions}
orderOptions={orderOptions}
columns={COLUMNS_CONFIG}
rows={data}
rowTitle={generateRowTitle}
isLoading={loading}
className="bg-gray-80 text-gray-10 lg:px-6 lg:py-4"
noData={t(translations.common.tables.noData)}
dataAttribute="vesting-reward-history-table"
/>
<Pagination
page={page}
className="lg:pb-6 mt-3 lg:mt-6 justify-center lg:justify-start"
onChange={onPageChange}
itemsPerPage={pageSize}
isNextButtonDisabled={isNextButtonDisabled}
dataAttribute="vesting-reward-history-pagination"
/>
</div>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';

import { t } from 'i18next';

import { Paragraph, ParagraphSize } from '@sovryn/ui';

import { translations } from '../../../../../locales/i18n';
import {
VestingHistoryItem,
VestingHistoryItemAction,
} from '../../../../../utils/graphql/rsk/generated';
import { dateFormat } from '../../../../../utils/helpers';

export const getTransactionType = (operation: VestingHistoryItemAction) => {
switch (operation) {
case VestingHistoryItemAction.TokensWithdrawn:
return t(translations.rewardHistory.vestingOperation.tokensWithdrawn);
case VestingHistoryItemAction.TeamTokensRevoked:
return t(translations.rewardHistory.vestingOperation.teamTokensRevoked);
case VestingHistoryItemAction.TokensStaked:
return t(translations.rewardHistory.vestingOperation.tokensStaked);
default:
return operation;
}
};

export const generateRowTitle = (tx: VestingHistoryItem) => (
<Paragraph size={ParagraphSize.small} className="text-left">
{getTransactionType(tx.action)}
{' - '}
{dateFormat(tx.timestamp)}
</Paragraph>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useMemo } from 'react';

import { useAccount } from '../../../../../../hooks/useAccount';
import { rskClient } from '../../../../../../utils/clients';
import { useGetVestingContractsIdQuery } from '../../../../../../utils/graphql/rsk/generated';

type GetVestingContractsResponse = {
loading: boolean;
data?: string[];
};

export const useGetVestingContractsId = (): GetVestingContractsResponse => {
const { account } = useAccount();

const { data, loading } = useGetVestingContractsIdQuery({
variables: {
user: account?.toLowerCase(),
},
client: rskClient,
});

const result = useMemo(
() => data?.vestingContracts.map(item => item.id),
[data?.vestingContracts],
);

return { loading, data: result };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useMemo } from 'react';

import { OrderOptions } from '@sovryn/ui';

import { rskClient } from '../../../../../../utils/clients';
import {
VestingHistoryItem,
VestingHistoryItem_OrderBy,
useGetVestingHistoryItemsQuery,
} from '../../../../../../utils/graphql/rsk/generated';

export const useGetVestingHistoryItems = (
stakers: string[],
pageSize: number,
page: number,
orderOptions: OrderOptions,
) => {
const config = useMemo(
() => ({
stakers: stakers,
skip: page * pageSize,
pageSize,
orderBy: orderOptions.orderBy as VestingHistoryItem_OrderBy,
orderDirection: orderOptions.orderDirection,
}),
[page, orderOptions, pageSize, stakers],
);

const { loading, data } = useGetVestingHistoryItemsQuery({
variables: config,
client: rskClient,
});

const list = useMemo(() => {
if (!data) {
return [];
}

return data.vestingHistoryItems;
}, [data]);

return { loading, data: list as VestingHistoryItem[] };
};
8 changes: 7 additions & 1 deletion apps/frontend/src/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -808,11 +808,17 @@
"stakingRevenue": "Withdraw staking revenue",
"stakingSubsidies": "Withdraw staking subsidy"
},
"vestingOperation": {
"tokensWithdrawn": "Withdraw vested reward",
"teamTokensRevoked": "Team tokens revoked",
"tokensStaked": "Tokens Staked"
},
"types": {
"stabilityPoolRewards": "Stability pool rewards",
"stabilityPoolSubsidies": "Stability pool subsidies",
"stakingRevenue": "Staking Revenue",
"stakingSubsidies": "Staking Subsidies"
"stakingSubsidies": "Staking Subsidies",
"vestingRewards": "Vesting rewards"
}
},
"stakingHistory": {
Expand Down
Loading