Skip to content

Commit d7ae8f2

Browse files
committed
feat: support last cycle prime summary modal
1 parent 2131c0f commit d7ae8f2

5 files changed

Lines changed: 162 additions & 15 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: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { cn } from '@venusprotocol/ui';
22

3-
import primeLogoSrc from 'assets/img/primeLogo.svg';
43
import { Icon } from 'components';
54
import { useTranslation } from 'libs/translations';
65
import type { Token } from 'types';
76
import { formatCentsToReadableValue, formatPercentageToReadableValue } from 'utilities';
87

98
import { MarketActions } from '../MarketActions';
109
import { MarketRewardRow } from '../MarketRewardRow';
10+
import { PrimeRewardBadge } from '../PrimeRewardBadge';
1111

1212
export interface UserMarketReward {
1313
token: Token;
@@ -18,12 +18,20 @@ export interface UserMarketReward {
1818
export interface UserRewardsCardProps {
1919
totalRewardsCents: number;
2020
marketRewards: UserMarketReward[];
21+
// Replaces the default headline (Prime badge + total amount). Used by the rules modal to show a
22+
// contextual message instead of the amount
23+
content?: React.ReactNode;
24+
// Toggles the per-market Prime APY and actions menu, which are hidden when the card is used as a
25+
// read-only summary
26+
showMarketActions?: boolean;
2127
className?: string;
2228
}
2329

2430
export const UserRewardsCard: React.FC<UserRewardsCardProps> = ({
2531
totalRewardsCents,
2632
marketRewards,
33+
content,
34+
showMarketActions = true,
2735
className,
2836
}) => {
2937
const { t } = useTranslation();
@@ -38,15 +46,15 @@ export const UserRewardsCard: React.FC<UserRewardsCardProps> = ({
3846
<div>
3947
<p className="text-b1r text-light-grey">{t('primeLeaderboard.userRewards.title')}</p>
4048

41-
<div className="flex items-center gap-x-3">
42-
<span className="flex size-10 shrink-0 items-center justify-center rounded-lg border border-[#805c4e]">
43-
<img src={primeLogoSrc} alt="" className="h-5" />
44-
</span>
49+
{content ?? (
50+
<div className="flex items-center gap-x-3">
51+
<PrimeRewardBadge />
4552

46-
<p className="text-h5 text-white">
47-
{formatCentsToReadableValue({ value: totalRewardsCents })}
48-
</p>
49-
</div>
53+
<p className="text-h5 text-white">
54+
{formatCentsToReadableValue({ value: totalRewardsCents })}
55+
</p>
56+
</div>
57+
)}
5058
</div>
5159

5260
<div className="flex flex-col gap-2">
@@ -57,13 +65,17 @@ export const UserRewardsCard: React.FC<UserRewardsCardProps> = ({
5765
rewardsCents={rewardsCents}
5866
totalRewardsCents={totalRewardsCents}
5967
>
60-
<div className="ml-2 flex items-center gap-x-1 text-green">
61-
<Icon name="sparkle" />
68+
{showMarketActions && (
69+
<>
70+
<div className="ml-2 flex items-center gap-x-1 text-green">
71+
<Icon name="sparkle" />
6272

63-
<span className="text-b1s">{formatPercentageToReadableValue(apyPercentage)}</span>
64-
</div>
73+
<span className="text-b1s">{formatPercentageToReadableValue(apyPercentage)}</span>
74+
</div>
6575

66-
<MarketActions token={token} />
76+
<MarketActions token={token} />
77+
</>
78+
)}
6779
</MarketRewardRow>
6880
))}
6981
</div>

0 commit comments

Comments
 (0)