Skip to content

Commit 0a7626e

Browse files
committed
feat: support last cycle prime summary modal
1 parent 6eef450 commit 0a7626e

5 files changed

Lines changed: 150 additions & 4 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { screen } from '@testing-library/react';
2+
3+
import { renderComponent } from 'testUtils/render';
4+
5+
import { LastCycleSummaryModal } from '..';
6+
7+
describe('pages/PrimeLeaderboard/LastCycleSummaryModal', () => {
8+
it('renders the last cycle total and user Prime rewards', async () => {
9+
renderComponent(<LastCycleSummaryModal isOpen handleClose={() => {}} />);
10+
11+
expect(await screen.findByText('Last Cycle Prime Summary')).toBeInTheDocument();
12+
expect(
13+
screen.getByText('Total Prime rewards distributed during the last cycle'),
14+
).toBeInTheDocument();
15+
expect(screen.getByText('Your Prime rewards this cycle')).toBeInTheDocument();
16+
});
17+
});
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { cn } from '@venusprotocol/ui';
2+
3+
import { Icon, Modal } from 'components';
4+
import { useGetTokens } from 'libs/tokens';
5+
import { useTranslation } from 'libs/translations';
6+
7+
import { PrimeRewardBadge } from '../PrimeRewardBadge';
8+
import { TotalRewardsCard } from '../TotalRewardsCard';
9+
import { UserRewardsCard } from '../UserRewardsCard';
10+
11+
// TODO: replace these placeholder values with the data returned by the API
12+
const placeholderTotalRewardsCents = 46_230_000;
13+
const placeholderTotalMarketRewardsCents = [28_040_000, 17_190_000];
14+
const placeholderUserRewardsCents = 1_840_000;
15+
const placeholderUserMarketRewardsCents = [1_140_000, 700_000];
16+
const placeholderApyPercentage = 3.78;
17+
const placeholderHasRewards = true;
18+
const placeholderIsEligible = true;
19+
20+
export interface LastCycleSummaryModalProps {
21+
isOpen: boolean;
22+
handleClose: () => void;
23+
}
24+
25+
export const LastCycleSummaryModal: React.FC<LastCycleSummaryModalProps> = ({
26+
isOpen,
27+
handleClose,
28+
}) => {
29+
const { t } = useTranslation();
30+
const tokens = useGetTokens();
31+
32+
// TODO: replace these placeholder tokens with the real Prime markets returned by the API
33+
const markets = tokens.slice(0, placeholderTotalMarketRewardsCents.length);
34+
35+
const totalMarketRewards = markets.map((token, index) => ({
36+
token,
37+
rewardsCents: placeholderTotalMarketRewardsCents[index],
38+
}));
39+
40+
const userMarketRewards = markets.map((token, index) => ({
41+
token,
42+
rewardsCents: placeholderUserMarketRewardsCents[index],
43+
apyPercentage: placeholderApyPercentage,
44+
}));
45+
46+
let userRewardsContent: React.ReactNode;
47+
48+
if (!placeholderHasRewards) {
49+
userRewardsContent = (
50+
<div className="flex items-center gap-x-3">
51+
{placeholderIsEligible ? (
52+
<PrimeRewardBadge />
53+
) : (
54+
<span className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-dark-blue-hover">
55+
<Icon name="person" className="text-light-grey" />
56+
</span>
57+
)}
58+
59+
<p
60+
className={cn(
61+
'flex-1 text-b1r',
62+
placeholderIsEligible ? 'text-light-grey' : 'text-yellow',
63+
)}
64+
>
65+
{placeholderIsEligible
66+
? t('primeLeaderboard.lastCycleSummary.eligibleMessage')
67+
: t('primeLeaderboard.lastCycleSummary.notEligibleMessage')}
68+
</p>
69+
</div>
70+
);
71+
}
72+
73+
return (
74+
<Modal
75+
isOpen={isOpen}
76+
handleClose={handleClose}
77+
title={t('primeLeaderboard.lastCycleSummary.title')}
78+
className="max-w-113"
79+
>
80+
<div className="flex flex-col gap-3">
81+
<TotalRewardsCard
82+
title={t('primeLeaderboard.lastCycleSummary.totalRewardsTitle')}
83+
totalRewardsCents={placeholderTotalRewardsCents}
84+
marketRewards={totalMarketRewards}
85+
/>
86+
87+
<UserRewardsCard
88+
totalRewardsCents={placeholderUserRewardsCents}
89+
marketRewards={userMarketRewards}
90+
content={userRewardsContent}
91+
showMarketActions={false}
92+
/>
93+
</div>
94+
</Modal>
95+
);
96+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { cn } from '@venusprotocol/ui';
2+
3+
import primeLogoSrc from 'assets/img/primeLogo.svg';
4+
5+
export interface PrimeRewardBadgeProps {
6+
className?: string;
7+
}
8+
9+
export const PrimeRewardBadge: React.FC<PrimeRewardBadgeProps> = ({ className }) => (
10+
<span
11+
className={cn(
12+
'flex size-10 shrink-0 items-center justify-center rounded-lg border border-[#805c4e]',
13+
className,
14+
)}
15+
>
16+
<img src={primeLogoSrc} alt="" className="h-5" />
17+
</span>
18+
);

apps/evm/src/pages/PrimeLeaderboard/TotalRewardsCard/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ export interface MarketReward {
1414
export interface TotalRewardsCardProps {
1515
totalRewardsCents: number;
1616
marketRewards: MarketReward[];
17+
title?: React.ReactNode;
1718
className?: string;
1819
}
1920

2021
export const TotalRewardsCard: React.FC<TotalRewardsCardProps> = ({
2122
totalRewardsCents,
2223
marketRewards,
24+
title,
2325
className,
2426
}) => {
2527
const { t } = useTranslation();
@@ -32,7 +34,9 @@ export const TotalRewardsCard: React.FC<TotalRewardsCardProps> = ({
3234
)}
3335
>
3436
<div>
35-
<p className="text-b1r text-light-grey">{t('primeLeaderboard.totalRewards.title')}</p>
37+
<p className="text-b1r text-light-grey">
38+
{title ?? t('primeLeaderboard.totalRewards.title')}
39+
</p>
3640

3741
<p className="text-h5 text-white">
3842
{formatCentsToReadableValue({ value: totalRewardsCents })}

apps/evm/src/pages/PrimeLeaderboard/UserRewardsCard/index.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,20 @@ export interface UserMarketReward {
1919
export interface UserRewardsCardProps {
2020
totalRewardsCents: number;
2121
marketRewards: UserMarketReward[];
22+
title?: React.ReactNode;
2223
// Replaces the default headline (Prime badge + total amount), e.g. an eligibility message
2324
content?: React.ReactNode;
25+
// Toggles the per-market Prime APY and actions menu, hidden when the card is a read-only summary
26+
showMarketActions?: boolean;
2427
className?: string;
2528
}
2629

2730
export const UserRewardsCard: React.FC<UserRewardsCardProps> = ({
2831
totalRewardsCents,
2932
marketRewards,
33+
title,
3034
content,
35+
showMarketActions = true,
3136
className,
3237
}) => {
3338
const { t } = useTranslation();
@@ -42,7 +47,9 @@ export const UserRewardsCard: React.FC<UserRewardsCardProps> = ({
4247
)}
4348
>
4449
<div className={cn(content && 'flex flex-col gap-1')}>
45-
<p className="text-b1r text-light-grey">{t('primeLeaderboard.userRewards.title')}</p>
50+
<p className="text-b1r text-light-grey">
51+
{title ?? t('primeLeaderboard.userRewards.title')}
52+
</p>
4653

4754
{content ?? (
4855
<div className="flex items-center gap-x-3">
@@ -72,9 +79,13 @@ export const UserRewardsCard: React.FC<UserRewardsCardProps> = ({
7279
rewardsCents={rewardsCents}
7380
totalRewardsCents={totalRewardsCents}
7481
>
75-
{asset && <Apy asset={asset} type="supply" className="ml-2" />}
82+
{showMarketActions && (
83+
<>
84+
{asset && <Apy asset={asset} type="supply" className="ml-2" />}
7685

77-
<MarketActions token={token} />
86+
<MarketActions token={token} />
87+
</>
88+
)}
7889
</MarketRewardRow>
7990
);
8091
})}

0 commit comments

Comments
 (0)