Skip to content
Open
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
3 changes: 3 additions & 0 deletions apps/evm/src/constants/prime.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import BigNumber from 'bignumber.js';
import { ChainId } from 'types';

// Maximum rank that qualifies for Prime during a cycle
export const PRIME_RANK_LIMIT = 500;
Comment on lines +4 to +5

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be fetched from the contract (it could change in the future through a VIP).


const bscTestnetPrimeMarketsAddresses = {
vBTCB: '0xb6e9322C49FD75a367Fcb17B0Fcd62C5070EbCBe',
vUSDT: '0xb7526572FFE56AB9D7489838Bf2E18e3323b441A',
Expand Down
9 changes: 9 additions & 0 deletions apps/evm/src/libs/translations/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,15 @@
"sec": "Sec",
"title": "End of cycle"
},
"lastCycleSummary": {
"primeScoreLabel": "Prime score",
"rankLabel": "Your rank",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned this to Zed yesterday, this should be "You ranked". Sorry for the last minute change.

"rankMissed": "Almost there! You weren't in last cycle's top 500. Boost your stake to qualify this cycle.",
"rankNoStake": "You didn't stake XVS last cycle. Stake now to qualify for Prime and earn extra rewards.",
"rankQualified": "Congrats! You're in the Top 500 during last cycle and qualified for Prime Rewards.",
Comment on lines +1267 to +1269

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These weren't proof read I think.

Suggested change
"rankMissed": "Almost there! You weren't in last cycle's top 500. Boost your stake to qualify this cycle.",
"rankNoStake": "You didn't stake XVS last cycle. Stake now to qualify for Prime and earn extra rewards.",
"rankQualified": "Congrats! You're in the Top 500 during last cycle and qualified for Prime Rewards.",
"rankMissed": "Almost there! You weren't in the top 500 last cycle. Increase your stake to qualify this cycle.",
"rankNoStake": "You didn't stake XVS during the last cycle. Stake now to qualify for Prime in the next cycle and earn extra rewards.",
"rankQualified": "Congrats! You were in the top 500 during the last cycle and qualified for Prime rewards.",

"title": "Last Cycle Prime Summary",
"userRewardsTitle": "Your Prime rewards last cycle"
},
"rankTable": {
"columns": {
"primeScore": "Prime score",
Expand Down
9 changes: 9 additions & 0 deletions apps/evm/src/libs/translations/translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,15 @@
"sec": "秒",
"title": "サイクル終了"
},
"lastCycleSummary": {
"primeScoreLabel": "Prime スコア",
"rankLabel": "あなたのランク",
"rankMissed": "あと一歩です!前サイクルでは上位 500 に入りませんでした。ステーク量を増やして今サイクルでの対象を目指しましょう。",
"rankNoStake": "前サイクルでは XVS をステークしていませんでした。今すぐステークして Prime の対象になり、追加報酬を獲得しましょう。",
"rankQualified": "おめでとうございます!前サイクルで上位 500 に入り、Prime 報酬の対象になりました。",
"title": "前サイクルの Prime サマリー",
"userRewardsTitle": "前サイクルのあなたの Prime 報酬"
},
"rankTable": {
"columns": {
"primeScore": "Prime スコア",
Expand Down
9 changes: 9 additions & 0 deletions apps/evm/src/libs/translations/translations/th.json
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,15 @@
"sec": "วินาที",
"title": "สิ้นสุดรอบ"
},
"lastCycleSummary": {
"primeScoreLabel": "คะแนน Prime",
"rankLabel": "อันดับของคุณ",
"rankMissed": "ใกล้แล้ว! คุณไม่ได้อยู่ใน Top 500 ของรอบที่แล้ว เพิ่ม stake ของคุณเพื่อให้มีสิทธิ์ในรอบนี้",
"rankNoStake": "คุณไม่ได้ stake XVS ในรอบที่แล้ว Stake ตอนนี้เพื่อมีสิทธิ์รับ Prime และรับรางวัลเพิ่มเติม",
"rankQualified": "ยินดีด้วย! คุณอยู่ใน Top 500 ในรอบที่แล้วและมีสิทธิ์รับรางวัล Prime",
"title": "สรุป Prime รอบที่แล้ว",
"userRewardsTitle": "รางวัล Prime ของคุณในรอบที่แล้ว"
},
"rankTable": {
"columns": {
"primeScore": "คะแนน Prime",
Expand Down
9 changes: 9 additions & 0 deletions apps/evm/src/libs/translations/translations/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,15 @@
"sec": "Sn.",
"title": "Döngü sonu"
},
"lastCycleSummary": {
"primeScoreLabel": "Prime puanı",
"rankLabel": "Sıralaman",
"rankMissed": "Az kaldı! Son döngüde ilk 500 içinde değildin. Bu döngüde uygun olmak için stake'ini artır.",
"rankNoStake": "Son döngüde XVS stake etmedin. Prime için uygun olmak ve ek ödül kazanmak için şimdi stake et.",
"rankQualified": "Tebrikler! Son döngüde ilk 500 içindeydin ve Prime ödülleri için uygun oldun.",
"title": "Son Döngü Prime Özeti",
"userRewardsTitle": "Son döngüdeki Prime ödülleriniz"
},
"rankTable": {
"columns": {
"primeScore": "Prime puanı",
Expand Down
9 changes: 9 additions & 0 deletions apps/evm/src/libs/translations/translations/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,15 @@
"sec": "Giây",
"title": "Kết thúc chu kỳ"
},
"lastCycleSummary": {
"primeScoreLabel": "Điểm Prime",
"rankLabel": "Hạng của bạn",
"rankMissed": "Sắp được rồi! Bạn không nằm trong Top 500 của chu kỳ trước. Hãy tăng mức stake để đủ điều kiện trong chu kỳ này.",
"rankNoStake": "Bạn chưa stake XVS ở chu kỳ trước. Hãy stake ngay để đủ điều kiện nhận Prime và kiếm thêm phần thưởng.",
"rankQualified": "Chúc mừng! Bạn nằm trong Top 500 ở chu kỳ trước và đủ điều kiện nhận phần thưởng Prime.",
"title": "Tóm tắt Prime chu kỳ trước",
"userRewardsTitle": "Phần thưởng Prime của bạn trong chu kỳ trước"
},
"rankTable": {
"columns": {
"primeScore": "Điểm Prime",
Expand Down
9 changes: 9 additions & 0 deletions apps/evm/src/libs/translations/translations/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,15 @@
"sec": "秒",
"title": "周期结束"
},
"lastCycleSummary": {
"primeScoreLabel": "Prime 分数",
"rankLabel": "你的排名",
"rankMissed": "就差一点!你未进入上一周期前 500。提升质押以在本周期获得资格。",
"rankNoStake": "你在上一周期未质押 XVS。立即质押以获得 Prime 资格并赚取额外奖励。",
"rankQualified": "恭喜!你在上一周期位列前 500,已获得 Prime 奖励资格。",
"title": "上一周期 Prime 汇总",
"userRewardsTitle": "你上一周期的 Prime 奖励"
},
"rankTable": {
"columns": {
"primeScore": "Prime 分数",
Expand Down
9 changes: 9 additions & 0 deletions apps/evm/src/libs/translations/translations/zh-Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,15 @@
"sec": "秒",
"title": "週期結束"
},
"lastCycleSummary": {
"primeScoreLabel": "Prime 分數",
"rankLabel": "你的排名",
"rankMissed": "就差一點!你未進入上一週期前 500。提升質押以在本週期獲得資格。",
"rankNoStake": "你在上一週期未質押 XVS。立即質押以獲得 Prime 資格並賺取額外獎勵。",
"rankQualified": "恭喜!你在上一週期位列前 500,已獲得 Prime 獎勵資格。",
"title": "上一週期 Prime 彙總",
"userRewardsTitle": "你上一週期的 Prime 獎勵"
},
"rankTable": {
"columns": {
"primeScore": "Prime 分數",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { screen } from '@testing-library/react';
import { fireEvent, screen } from '@testing-library/react';

import { renderComponent } from 'testUtils/render';
import { EndOfCycle } from '..';
Expand All @@ -13,6 +13,14 @@ describe('pages/PrimeLeaderboard/EndOfCycle', () => {
expect(screen.getByText("See last cycle's Prime summary")).toBeInTheDocument();
});

it('opens the last cycle summary modal from the helper link', async () => {
renderComponent(<EndOfCycle endDate={new Date(Date.now() + 5 * ONE_DAY_MS)} />);

fireEvent.click(screen.getByText("See last cycle's Prime summary"));

expect(await screen.findByText('Last Cycle Prime Summary')).toBeInTheDocument();
});

it('renders the ended state for a past end date', () => {
renderComponent(<EndOfCycle endDate={new Date(Date.now() - ONE_DAY_MS)} />);

Expand Down
20 changes: 17 additions & 3 deletions apps/evm/src/pages/PrimeLeaderboard/EndOfCycle/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Button, cn } from '@venusprotocol/ui';
import { useState } from 'react';
import ReactCountdown from 'react-countdown';

import { Card } from 'components';
import { useTranslation } from 'libs/translations';

import { LastCycleSummaryModal } from '../LastCycleSummaryModal';
import { Timer } from './Timer';

export interface EndOfCycleProps {
Expand All @@ -21,6 +23,7 @@ interface CountdownState {

export const EndOfCycle: React.FC<EndOfCycleProps> = ({ endDate, className }) => {
const { t, Trans } = useTranslation();
const [isSummaryModalOpen, setIsSummaryModalOpen] = useState(false);

const deadline = t('primeLeaderboard.endOfCycle.deadline', { date: endDate });

Expand Down Expand Up @@ -49,9 +52,12 @@ export const EndOfCycle: React.FC<EndOfCycleProps> = ({ endDate, className }) =>
values={{ deadline }}
components={{
bold: <span className="text-b1s text-white" />,
// TODO: open the last cycle summary modal once it's available
summaryLink: (
<Button variant="text" className="h-auto p-0 text-b1s text-blue underline" />
<Button
variant="text"
onClick={() => setIsSummaryModalOpen(true)}
className="h-auto p-0 text-b1s text-blue underline"
/>
),
}}
/>
Expand All @@ -60,5 +66,13 @@ export const EndOfCycle: React.FC<EndOfCycleProps> = ({ endDate, className }) =>
</Card>
);

return <ReactCountdown date={endDate} renderer={renderCard} />;
return (
<>
<ReactCountdown date={endDate} renderer={renderCard} />

{isSummaryModalOpen && (
<LastCycleSummaryModal isOpen handleClose={() => setIsSummaryModalOpen(false)} />
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { cn } from '@venusprotocol/ui';
import type BigNumber from 'bignumber.js';

import { PRIME_RANK_LIMIT } from 'constants/prime';
import { useTranslation } from 'libs/translations';
import { shortenValueWithSuffix } from 'utilities';

export interface UserRankCardProps {
rank?: number;
primeScore?: BigNumber;
className?: string;
}

export const UserRankCard: React.FC<UserRankCardProps> = ({ rank, primeScore, className }) => {
const { t } = useTranslation();

const isRanked = rank !== undefined;
const isInTopRank = isRanked && rank <= PRIME_RANK_LIMIT;
const rankLabel = isRanked ? `#${rank}` : '#-';
const primeScoreLabel =
isRanked && primeScore ? shortenValueWithSuffix({ value: primeScore }) : '-';

let message = t('primeLeaderboard.lastCycleSummary.rankNoStake');
let messageClassName = 'text-yellow';

if (isInTopRank) {
message = t('primeLeaderboard.lastCycleSummary.rankQualified');
messageClassName = 'text-white';
} else if (isRanked) {
message = t('primeLeaderboard.lastCycleSummary.rankMissed');
}

return (
<div className={cn('flex flex-col gap-4 rounded-lg bg-background-active p-4', className)}>
<div className="flex items-start justify-between">
<div className="flex flex-col">
<span className="text-b1r text-light-grey">
{t('primeLeaderboard.lastCycleSummary.rankLabel')}
</span>

<span className="text-h5 text-white">{rankLabel}</span>
</div>

<div className="flex flex-col items-end">
<span className="text-b1r text-light-grey">
{t('primeLeaderboard.lastCycleSummary.primeScoreLabel')}
</span>

<span className="text-h5 text-white">{primeScoreLabel}</span>
</div>
</div>

<p className={cn('text-b1r', messageClassName)}>{message}</p>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { screen } from '@testing-library/react';

import { renderComponent } from 'testUtils/render';

import { LastCycleSummaryModal } from '..';

describe('pages/PrimeLeaderboard/LastCycleSummaryModal', () => {
it('renders the last cycle user rank and rewards', async () => {
renderComponent(<LastCycleSummaryModal isOpen handleClose={() => {}} />);

expect(await screen.findByText('Last Cycle Prime Summary')).toBeInTheDocument();
expect(screen.getByText('Your rank')).toBeInTheDocument();
expect(screen.getByText('Your Prime rewards last cycle')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import BigNumber from 'bignumber.js';

import { Modal } from 'components';
import { useGetTokens } from 'libs/tokens';
import { useTranslation } from 'libs/translations';

import { UserRewardsCard } from '../UserRewardsCard';
import { UserRankCard } from './UserRankCard';

// TODO: replace these placeholder values with the data returned by the API
const placeholderRank = 1222;
const placeholderPrimeScore = new BigNumber(542_500_000);
const placeholderUserRewardsCents = 1_840_000;
const placeholderUserMarketRewardsCents = [1_140_000, 700_000];
const placeholderApyPercentage = 3.78;

export interface LastCycleSummaryModalProps {
isOpen: boolean;
handleClose: () => void;
}

export const LastCycleSummaryModal: React.FC<LastCycleSummaryModalProps> = ({
isOpen,
handleClose,
}) => {
const { t } = useTranslation();
const tokens = useGetTokens();

// TODO: replace these placeholder tokens with the real Prime markets returned by the API
const markets = tokens.slice(0, placeholderUserMarketRewardsCents.length);

const userMarketRewards = markets.map((token, index) => ({
token,
rewardsCents: placeholderUserMarketRewardsCents[index],
apyPercentage: placeholderApyPercentage,
}));

return (
<Modal
isOpen={isOpen}
handleClose={handleClose}
title={t('primeLeaderboard.lastCycleSummary.title')}
className="max-w-113"
>
<div className="flex flex-col gap-3">
<UserRankCard rank={placeholderRank} primeScore={placeholderPrimeScore} />

<UserRewardsCard
title={t('primeLeaderboard.lastCycleSummary.userRewardsTitle')}
totalRewardsCents={placeholderUserRewardsCents}
marketRewards={userMarketRewards}
showMarketActions={false}
/>
</div>
</Modal>
);
};
18 changes: 18 additions & 0 deletions apps/evm/src/pages/PrimeLeaderboard/PrimeRewardBadge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { cn } from '@venusprotocol/ui';

import primeLogoSrc from 'assets/img/primeLogo.svg';

export interface PrimeRewardBadgeProps {
className?: string;
}

export const PrimeRewardBadge: React.FC<PrimeRewardBadgeProps> = ({ className }) => (
<span
className={cn(
'flex size-10 shrink-0 items-center justify-center rounded-lg border border-[#805c4e]',
className,
)}
>
<img src={primeLogoSrc} alt="" className="h-5" />
</span>
);
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ export interface MarketReward {
export interface TotalRewardsCardProps {
totalRewardsCents: number;
marketRewards: MarketReward[];
title?: React.ReactNode;
className?: string;
}

export const TotalRewardsCard: React.FC<TotalRewardsCardProps> = ({
totalRewardsCents,
marketRewards,
title,
className,
}) => {
const { t } = useTranslation();
Expand All @@ -32,7 +34,9 @@ export const TotalRewardsCard: React.FC<TotalRewardsCardProps> = ({
)}
>
<div>
<p className="text-b1r text-light-grey">{t('primeLeaderboard.totalRewards.title')}</p>
<p className="text-b1r text-light-grey">
{title ?? t('primeLeaderboard.totalRewards.title')}
</p>

<p className="text-h5 text-white">
{formatCentsToReadableValue({ value: totalRewardsCents })}
Expand Down
Loading
Loading