Skip to content

Commit 6bd95cb

Browse files
cuzz-venusclaude
andcommitted
feat: show prime eligibility states in user rewards card
Drive the user rewards card headline by Prime eligibility (rewards amount, eligible-to-supply prompt, or not-eligible prompt) and render per-market Prime APYs with the shared Apy component. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2131c0f commit 6bd95cb

11 files changed

Lines changed: 107 additions & 34 deletions

File tree

apps/evm/src/libs/translations/translations/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,9 @@
12811281
"title": "Total Prime rewards distributed this cycle"
12821282
},
12831283
"userRewards": {
1284+
"eligibleMessage": "You are currently eligible for sharing the Prime rewards during this cycle. Supply assets below to share the rewards.",
12841285
"marketActions": "Open market actions",
1286+
"notEligibleMessage": "You are currently NOT eligible for sharing the Prime rewards during this cycle. Stake XVS to compete for Prime for next cycle.",
12851287
"title": "Your Prime rewards this cycle"
12861288
}
12871289
},

apps/evm/src/libs/translations/translations/ja.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,9 @@
12811281
"title": "今サイクルに分配された Prime 報酬の総額"
12821282
},
12831283
"userRewards": {
1284+
"eligibleMessage": "今サイクルで Prime 報酬を分け合う対象になっています。下記の資産を供給して報酬を獲得しましょう。",
12841285
"marketActions": "マーケット操作を開く",
1286+
"notEligibleMessage": "今サイクルでは Prime 報酬を分け合う対象ではありません。XVS をステークして次のサイクルで Prime を目指しましょう。",
12851287
"title": "今サイクルのあなたの Prime 報酬"
12861288
}
12871289
},

apps/evm/src/libs/translations/translations/th.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,9 @@
12811281
"title": "รางวัล Prime ทั้งหมดที่แจกในรอบนี้"
12821282
},
12831283
"userRewards": {
1284+
"eligibleMessage": "คุณมีสิทธิ์ร่วมรับรางวัล Prime ในรอบนี้ จัดหาสินทรัพย์ด้านล่างเพื่อรับรางวัล",
12841285
"marketActions": "เปิดการดำเนินการของตลาด",
1286+
"notEligibleMessage": "คุณยังไม่มีสิทธิ์ร่วมรับรางวัล Prime ในรอบนี้ Stake XVS เพื่อแข่งขันรับ Prime ในรอบถัดไป",
12851287
"title": "รางวัล Prime ของคุณในรอบนี้"
12861288
}
12871289
},

apps/evm/src/libs/translations/translations/tr.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,9 @@
12811281
"title": "Bu döngüde dağıtılan toplam Prime ödülü"
12821282
},
12831283
"userRewards": {
1284+
"eligibleMessage": "Bu döngüde Prime ödüllerini paylaşmaya uygunsun. Ödülleri paylaşmak için aşağıdan varlık sağla.",
12841285
"marketActions": "Piyasa işlemlerini aç",
1286+
"notEligibleMessage": "Bu döngüde Prime ödüllerini paylaşmaya uygun değilsin. Sonraki döngüde Prime için yarışmak üzere XVS stake et.",
12851287
"title": "Bu döngüdeki Prime ödülleriniz"
12861288
}
12871289
},

apps/evm/src/libs/translations/translations/vi.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,9 @@
12811281
"title": "Tổng thưởng Prime đã phân phối trong chu kỳ này"
12821282
},
12831283
"userRewards": {
1284+
"eligibleMessage": "Bạn hiện đủ điều kiện chia sẻ phần thưởng Prime trong chu kỳ này. Hãy cung cấp tài sản bên dưới để nhận phần thưởng.",
12841285
"marketActions": "Mở thao tác thị trường",
1286+
"notEligibleMessage": "Bạn hiện KHÔNG đủ điều kiện chia sẻ phần thưởng Prime trong chu kỳ này. Stake XVS để cạnh tranh Prime cho chu kỳ tiếp theo.",
12851287
"title": "Phần thưởng Prime của bạn trong chu kỳ này"
12861288
}
12871289
},

apps/evm/src/libs/translations/translations/zh-Hans.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,9 @@
12811281
"title": "本周期已分配的 Prime 总奖励"
12821282
},
12831283
"userRewards": {
1284+
"eligibleMessage": "你在本周期已具备分享 Prime 奖励的资格。在下方供给资产以分享奖励。",
12841285
"marketActions": "打开市场操作",
1286+
"notEligibleMessage": "你在本周期暂不具备分享 Prime 奖励的资格。质押 XVS 以在下一周期竞争 Prime。",
12851287
"title": "你本周期的 Prime 奖励"
12861288
}
12871289
},

apps/evm/src/libs/translations/translations/zh-Hant.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,9 @@
12811281
"title": "本週期已分配的 Prime 總獎勵"
12821282
},
12831283
"userRewards": {
1284+
"eligibleMessage": "你在本週期已具備分享 Prime 獎勵的資格。在下方供給資產以分享獎勵。",
12841285
"marketActions": "開啟市場操作",
1286+
"notEligibleMessage": "你在本週期暫不具備分享 Prime 獎勵的資格。質押 XVS 以在下一週期競爭 Prime。",
12851287
"title": "你本週期的 Prime 獎勵"
12861288
}
12871289
},

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,32 @@ import { renderComponent } from 'testUtils/render';
55
import { UserRewardsCard } from '..';
66

77
describe('pages/PrimeLeaderboard/UserRewardsCard', () => {
8-
it('renders the user total, per-market rewards and Prime APYs', () => {
8+
it('renders the user total and per-market rewards', () => {
99
renderComponent(
1010
<UserRewardsCard
1111
totalRewardsCents={1_840_000}
1212
marketRewards={[
13-
{ token: usdc, rewardsCents: 1_140_000, apyPercentage: 3.78 },
14-
{ token: xvs, rewardsCents: 700_000, apyPercentage: 4.2 },
13+
{ token: usdc, rewardsCents: 1_140_000 },
14+
{ token: xvs, rewardsCents: 700_000 },
1515
]}
1616
/>,
1717
);
1818

1919
expect(screen.getByText('Your Prime rewards this cycle')).toBeInTheDocument();
2020
expect(screen.getByText('$18.4K')).toBeInTheDocument();
2121
expect(screen.getByText(usdc.symbol)).toBeInTheDocument();
22-
expect(screen.getByText('3.78%')).toBeInTheDocument();
22+
});
23+
24+
it('renders the provided content instead of the default headline', () => {
25+
renderComponent(
26+
<UserRewardsCard
27+
totalRewardsCents={0}
28+
marketRewards={[]}
29+
content={<span>Eligibility message</span>}
30+
/>,
31+
);
32+
33+
expect(screen.getByText('Eligibility message')).toBeInTheDocument();
34+
expect(screen.queryByText('$0')).not.toBeInTheDocument();
2335
});
2436
});

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

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
11
import { cn } from '@venusprotocol/ui';
22

33
import primeLogoSrc from 'assets/img/primeLogo.svg';
4-
import { Icon } from 'components';
4+
import { useGetPools } from 'clients/api';
5+
import { Apy } from 'components';
56
import { useTranslation } from 'libs/translations';
7+
import { useAccountAddress } from 'libs/wallet';
68
import type { Token } from 'types';
7-
import { formatCentsToReadableValue, formatPercentageToReadableValue } from 'utilities';
9+
import { areAddressesEqual, formatCentsToReadableValue } from 'utilities';
810

911
import { MarketActions } from '../MarketActions';
1012
import { MarketRewardRow } from '../MarketRewardRow';
1113

1214
export interface UserMarketReward {
1315
token: Token;
1416
rewardsCents: number;
15-
apyPercentage: number;
1617
}
1718

1819
export interface UserRewardsCardProps {
1920
totalRewardsCents: number;
2021
marketRewards: UserMarketReward[];
22+
// Replaces the default headline (Prime badge + total amount), e.g. an eligibility message
23+
content?: React.ReactNode;
2124
className?: string;
2225
}
2326

2427
export const UserRewardsCard: React.FC<UserRewardsCardProps> = ({
2528
totalRewardsCents,
2629
marketRewards,
30+
content,
2731
className,
2832
}) => {
2933
const { t } = useTranslation();
34+
const { accountAddress } = useAccountAddress();
35+
const { data: getPoolsData } = useGetPools({ accountAddress });
3036

3137
return (
3238
<div
@@ -35,37 +41,43 @@ export const UserRewardsCard: React.FC<UserRewardsCardProps> = ({
3541
className,
3642
)}
3743
>
38-
<div>
44+
<div className={cn(content && 'flex flex-col gap-1')}>
3945
<p className="text-b1r text-light-grey">{t('primeLeaderboard.userRewards.title')}</p>
4046

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>
47+
{content ?? (
48+
<div className="flex items-center gap-x-3">
49+
<span className="flex size-10 shrink-0 items-center justify-center rounded-lg border border-[#805c4e]">
50+
<img src={primeLogoSrc} alt="" className="h-5" />
51+
</span>
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">
53-
{marketRewards.map(({ token, rewardsCents, apyPercentage }) => (
54-
<MarketRewardRow
55-
key={token.address}
56-
token={token}
57-
rewardsCents={rewardsCents}
58-
totalRewardsCents={totalRewardsCents}
59-
>
60-
<div className="ml-2 flex items-center gap-x-1 text-green">
61-
<Icon name="sparkle" />
61+
{marketRewards.map(({ token, rewardsCents }) => {
62+
const asset = getPoolsData?.pools
63+
.flatMap(pool => pool.assets)
64+
.find(poolAsset =>
65+
areAddressesEqual(poolAsset.vToken.underlyingToken.address, token.address),
66+
);
6267

63-
<span className="text-b1s">{formatPercentageToReadableValue(apyPercentage)}</span>
64-
</div>
68+
return (
69+
<MarketRewardRow
70+
key={token.address}
71+
token={token}
72+
rewardsCents={rewardsCents}
73+
totalRewardsCents={totalRewardsCents}
74+
>
75+
{asset && <Apy asset={asset} type="supply" className="ml-2" />}
6576

66-
<MarketActions token={token} />
67-
</MarketRewardRow>
68-
))}
77+
<MarketActions token={token} />
78+
</MarketRewardRow>
79+
);
80+
})}
6981
</div>
7082
</div>
7183
);

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import { cn } from '@venusprotocol/ui';
2+
3+
import primeLogoSrc from 'assets/img/primeLogo.svg';
4+
import { Icon } from 'components';
5+
import { useTranslation } from 'libs/translations';
6+
17
import { UserRewardsCard } from '../UserRewardsCard';
28
import { useGetPrimeUserRewards } from '../useGetPrimeUserRewards';
39

@@ -6,12 +12,40 @@ export interface UserRewardsSectionProps {
612
}
713

814
export const UserRewardsSection: React.FC<UserRewardsSectionProps> = ({ className }) => {
9-
const { totalRewardsCents, marketRewards } = useGetPrimeUserRewards();
15+
const { t } = useTranslation();
16+
const { isPrime, totalRewardsCents, marketRewards } = useGetPrimeUserRewards();
17+
18+
const hasRewards = totalRewardsCents > 0;
19+
20+
let content: React.ReactNode;
21+
22+
if (!hasRewards) {
23+
content = (
24+
<div className="flex items-center gap-x-3">
25+
{isPrime ? (
26+
<span className="flex size-10 shrink-0 items-center justify-center rounded-lg border border-[#805c4e]">
27+
<img src={primeLogoSrc} alt="" className="h-5" />
28+
</span>
29+
) : (
30+
<span className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-dark-blue-hover">
31+
<Icon name="person" className="text-light-grey" />
32+
</span>
33+
)}
34+
35+
<p className={cn('flex-1 text-b1r', isPrime ? 'text-white' : 'text-yellow')}>
36+
{isPrime
37+
? t('primeLeaderboard.userRewards.eligibleMessage')
38+
: t('primeLeaderboard.userRewards.notEligibleMessage')}
39+
</p>
40+
</div>
41+
);
42+
}
1043

1144
return (
1245
<UserRewardsCard
1346
totalRewardsCents={totalRewardsCents}
1447
marketRewards={marketRewards}
48+
content={content}
1549
className={className}
1650
/>
1751
);

0 commit comments

Comments
 (0)