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
28 changes: 28 additions & 0 deletions apps/evm/src/components/Icon/icons/dotShortcut.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { SVGProps } from 'react';

const SvgDotShortcut = (props: SVGProps<SVGSVGElement>) => (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<rect x="0.5" y="0.5" width="15" height="15" rx="7.5" stroke="currentColor" />
<path
d="M4 9C4.55228 9 5 8.55228 5 8C5 7.44772 4.55228 7 4 7C3.44772 7 3 7.44772 3 8C3 8.55228 3.44772 9 4 9Z"
fill="currentColor"
/>
<path
d="M8 9C8.55228 9 9 8.55228 9 8C9 7.44772 8.55228 7 8 7C7.44772 7 7 7.44772 7 8C7 8.55228 7.44772 9 8 9Z"
fill="currentColor"
/>
<path
d="M12 9C12.5523 9 13 8.55228 13 8C13 7.44772 12.5523 7 12 7C11.4477 7 11 7.44772 11 8C11 8.55228 11.4477 9 12 9Z"
fill="currentColor"
/>
</svg>
);

export default SvgDotShortcut;
2 changes: 2 additions & 0 deletions apps/evm/src/components/Icon/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export { default as checkInlineDotted } from './checkInlineDotted';
export { default as mark } from './mark';
export { default as arrowShaft } from './arrowShaft';
export { default as notice } from './notice';
export { default as dotShortcut } from './dotShortcut';
export { default as dots } from './dots';
export { default as exclamation } from './exclamation';
export { default as comment } from './comment';
Expand Down Expand Up @@ -66,6 +67,7 @@ export { default as shield2 } from './shield2';
export { default as lightning2 } from './lightning2';
export { default as graph } from './graph';
export { default as star } from './star';
export { default as sparkle } from './sparkle';

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 icon isn't used anywhere.

export { default as download } from './download';
export { default as arrowUpFull2 } from './arrowUpFull2';
export { default as transactionFile } from './transactionFile';
Expand Down
19 changes: 19 additions & 0 deletions apps/evm/src/components/Icon/icons/sparkle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { SVGProps } from 'react';

const SvgSparkle = (props: SVGProps<SVGSVGElement>) => (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M3.73138 8.51568C3.84319 8.29954 4.15265 8.29967 4.26459 8.51568L5.31732 10.5528C5.34582 10.6078 5.39121 10.6522 5.44623 10.6807L7.48236 11.7344C7.69864 11.8463 7.69864 12.1548 7.48236 12.2667L5.44623 13.3204C5.39121 13.3489 5.34582 13.3933 5.31732 13.4483L4.26459 15.4854C4.15265 15.7014 3.84319 15.7015 3.73138 15.4854L2.67865 13.4483C2.6501 13.3933 2.60481 13.3488 2.54974 13.3204L0.51361 12.2667C0.297547 12.1548 0.297547 11.8463 0.51361 11.7344L2.54974 10.6807C2.60481 10.6522 2.6501 10.6078 2.67865 10.5528L3.73138 8.51568ZM10.2236 1.62505C10.3287 1.38611 10.6673 1.38614 10.7724 1.62505L11.8652 4.10845C12.0667 4.56644 12.4325 4.93239 12.8906 5.13384L15.374 6.22564C15.6129 6.33074 15.6129 6.67034 15.374 6.77544L12.8906 7.86724C12.4325 8.06869 12.0667 8.43463 11.8652 8.89263L10.7724 11.376C10.6673 11.6149 10.3287 11.615 10.2236 11.376L9.13177 8.89263C8.93027 8.43455 8.5635 8.06872 8.10541 7.86724L5.62298 6.77544C5.38403 6.67034 5.38403 6.33074 5.62298 6.22564L8.10541 5.13384C8.5635 4.93236 8.93027 4.56653 9.13177 4.10845L10.2236 1.62505ZM3.90912 0.171926C3.94641 0.100177 4.04943 0.10031 4.08685 0.171926L4.66498 1.29107C4.67443 1.30923 4.68979 1.32358 4.70795 1.33306L5.82611 1.91216C5.8982 1.94944 5.8982 2.05164 5.82611 2.08892L4.70795 2.66802C4.68979 2.6775 4.67443 2.69184 4.66498 2.71001L4.08685 3.82915C4.04943 3.90077 3.94641 3.9009 3.90912 3.82915L3.33099 2.71001C3.32147 2.69182 3.30627 2.67746 3.28802 2.66802L2.16986 2.08892C2.09819 2.05156 2.09819 1.94952 2.16986 1.91216L3.28802 1.33306C3.30627 1.32362 3.32147 1.30926 3.33099 1.29107L3.90912 0.171926Z"
fill="currentColor"
/>
</svg>
);

export default SvgSparkle;
11 changes: 10 additions & 1 deletion apps/evm/src/libs/translations/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,16 @@
}
},
"tablesRefreshNote": "Refreshed hourly · Last refresh: {{date, distanceToNow}} ago",
"title": "Prime leaderboard"
"title": "Prime leaderboard",
"totalRewards": {
"title": "Total Prime rewards distributed this cycle"
},
"userRewards": {
"eligibleMessage": "You are currently eligible for sharing the Prime rewards during this cycle. Supply assets below to share the rewards.",
"marketActions": "Open market actions",
"notEligibleMessage": "You are currently NOT eligible for sharing the Prime rewards during this cycle. Stake XVS to compete for Prime for next cycle.",

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 content wasn't proofread it seems:

Suggested change
"notEligibleMessage": "You are currently NOT eligible for sharing the Prime rewards during this cycle. Stake XVS to compete for Prime for next cycle.",
"notEligibleMessage": "You are currently NOT eligible for sharing the Prime rewards during this cycle. Stake XVS to compete for Prime in the next cycle.",

"title": "Your Prime rewards this cycle"
}
},
"primeStatusBanner": {
"becomePrimeTitle": "You can now become a Prime user",
Expand Down
11 changes: 10 additions & 1 deletion apps/evm/src/libs/translations/translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,16 @@
}
},
"tablesRefreshNote": "1時間ごとに更新 · 最終更新:{{date, distanceToNow}}前",
"title": "Prime リーダーボード"
"title": "Prime リーダーボード",
"totalRewards": {
"title": "今サイクルに分配された Prime 報酬の総額"
},
"userRewards": {
"eligibleMessage": "今サイクルで Prime 報酬を分け合う対象になっています。下記の資産を供給して報酬を獲得しましょう。",
"marketActions": "マーケット操作を開く",
"notEligibleMessage": "今サイクルでは Prime 報酬を分け合う対象ではありません。XVS をステークして次のサイクルで Prime を目指しましょう。",
"title": "今サイクルのあなたの Prime 報酬"
}
},
"primeStatusBanner": {
"becomePrimeTitle": "Primeユーザーになれるようになりました",
Expand Down
11 changes: 10 additions & 1 deletion apps/evm/src/libs/translations/translations/th.json
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,16 @@
}
},
"tablesRefreshNote": "รีเฟรชทุกชั่วโมง · รีเฟรชล่าสุด: {{date, distanceToNow}} ที่แล้ว",
"title": "กระดานผู้นำ Prime"
"title": "กระดานผู้นำ Prime",
"totalRewards": {
"title": "รางวัล Prime ทั้งหมดที่แจกในรอบนี้"
},
"userRewards": {
"eligibleMessage": "คุณมีสิทธิ์ร่วมรับรางวัล Prime ในรอบนี้ จัดหาสินทรัพย์ด้านล่างเพื่อรับรางวัล",
"marketActions": "เปิดการดำเนินการของตลาด",
"notEligibleMessage": "คุณยังไม่มีสิทธิ์ร่วมรับรางวัล Prime ในรอบนี้ Stake XVS เพื่อแข่งขันรับ Prime ในรอบถัดไป",
"title": "รางวัล Prime ของคุณในรอบนี้"
}
},
"primeStatusBanner": {
"becomePrimeTitle": "ตอนนี้คุณสามารถเป็นผู้ใช้ Prime ได้แล้ว",
Expand Down
11 changes: 10 additions & 1 deletion apps/evm/src/libs/translations/translations/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,16 @@
}
},
"tablesRefreshNote": "Saatlik yenilenir · Son yenileme: {{date, distanceToNow}} önce",
"title": "Prime lider tablosu"
"title": "Prime lider tablosu",
"totalRewards": {
"title": "Bu döngüde dağıtılan toplam Prime ödülü"
},
"userRewards": {
"eligibleMessage": "Bu döngüde Prime ödüllerini paylaşmaya uygunsun. Ödülleri paylaşmak için aşağıdan varlık sağla.",
"marketActions": "Piyasa işlemlerini aç",
"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.",
"title": "Bu döngüdeki Prime ödülleriniz"
}
},
"primeStatusBanner": {
"becomePrimeTitle": "Artık Prime kullanıcısı olabilirsiniz",
Expand Down
11 changes: 10 additions & 1 deletion apps/evm/src/libs/translations/translations/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,16 @@
}
},
"tablesRefreshNote": "Làm mới mỗi giờ · Lần làm mới cuối: {{date, distanceToNow}} trước",
"title": "Bảng xếp hạng Prime"
"title": "Bảng xếp hạng Prime",
"totalRewards": {
"title": "Tổng thưởng Prime đã phân phối trong chu kỳ này"
},
"userRewards": {
"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.",
"marketActions": "Mở thao tác thị trường",
"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.",
"title": "Phần thưởng Prime của bạn trong chu kỳ này"
}
},
"primeStatusBanner": {
"becomePrimeTitle": "Bạn đã có thể trở thành người dùng Prime",
Expand Down
11 changes: 10 additions & 1 deletion apps/evm/src/libs/translations/translations/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,16 @@
}
},
"tablesRefreshNote": "每小时刷新 · 上次刷新:{{date, distanceToNow}}前",
"title": "Prime 排行榜"
"title": "Prime 排行榜",
"totalRewards": {
"title": "本周期已分配的 Prime 总奖励"
},
"userRewards": {
"eligibleMessage": "你在本周期已具备分享 Prime 奖励的资格。在下方供给资产以分享奖励。",
"marketActions": "打开市场操作",
"notEligibleMessage": "你在本周期暂不具备分享 Prime 奖励的资格。质押 XVS 以在下一周期竞争 Prime。",
"title": "你本周期的 Prime 奖励"
}
},
"primeStatusBanner": {
"becomePrimeTitle": "你现在可以成为 Prime 用户",
Expand Down
11 changes: 10 additions & 1 deletion apps/evm/src/libs/translations/translations/zh-Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,16 @@
}
},
"tablesRefreshNote": "每小時刷新 · 上次刷新:{{date, distanceToNow}}前",
"title": "Prime 排行榜"
"title": "Prime 排行榜",
"totalRewards": {
"title": "本週期已分配的 Prime 總獎勵"
},
"userRewards": {
"eligibleMessage": "你在本週期已具備分享 Prime 獎勵的資格。在下方供給資產以分享獎勵。",
"marketActions": "開啟市場操作",
"notEligibleMessage": "你在本週期暫不具備分享 Prime 獎勵的資格。質押 XVS 以在下一週期競爭 Prime。",
"title": "你本週期的 Prime 獎勵"
}
},
"primeStatusBanner": {
"becomePrimeTitle": "你現在可以成為 Prime 用戶",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { fireEvent, screen } from '@testing-library/react';

import { xvs } from '__mocks__/models/tokens';
import { renderComponent } from 'testUtils/render';

import { MarketActions } from '..';

vi.mock('pages/Market/OperationForm', () => ({
OperationForm: () => <div data-testid="operation-form" />,
}));

describe('pages/PrimeLeaderboard/MarketActions', () => {
it('opens the market operation modal when clicked', async () => {
renderComponent(<MarketActions token={xvs} />);

fireEvent.click(screen.getByLabelText('Open market actions'));

expect(await screen.findByTestId('operation-form')).toBeInTheDocument();
});
});
57 changes: 57 additions & 0 deletions apps/evm/src/pages/PrimeLeaderboard/MarketActions/index.tsx

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.

What do you think about moving this component to the containers directory and reusing it in the MarketTable component (https://github.com/VenusProtocol/venus-protocol-interface/blob/088de2b0451f8a4ef5364cbaa7e3d2133b4d9bc8/apps/evm/src/containers/MarketTable/index.tsx) which renders the same UI using the renderRowControl property?

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useState } from 'react';

import { useGetPools } from 'clients/api';
import { Icon, Modal } from 'components';
import { useTranslation } from 'libs/translations';
import { useAccountAddress } from 'libs/wallet';
import { OperationForm } from 'pages/Market/OperationForm';

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.

OperationForm should be moved to the containers directory, since it's now shared by two pages.

import type { Token } from 'types';
import { areAddressesEqual } from 'utilities';

export interface MarketActionsProps {
token: Token;
}

export const MarketActions: React.FC<MarketActionsProps> = ({ token }) => {

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'd recommend using a more explicit name for this. Maybe MarketActionsButton or something that makes it obvious we're rendering a CTA (otherwise the plural name could give the impression we're rendering a list of elements).

const { t } = useTranslation();
const { accountAddress } = useAccountAddress();
const { data: getPoolsData } = useGetPools({ accountAddress });
const [isModalOpen, setIsModalOpen] = useState(false);

const market = getPoolsData?.pools
.flatMap(pool =>
pool.assets.map(asset => ({ asset, poolComptrollerAddress: pool.comptrollerAddress })),
)
.find(({ asset }) => areAddressesEqual(asset.vToken.underlyingToken.address, token.address));

if (!market) {
return undefined;
}
Comment on lines +18 to +29

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.

All you need from the market is poolComptrollerAddress and vToken. Since you already have this information from the parent that renders the modal, you could just pass them through props instead of having to go through all pools and assets.


return (
<>
<button
type="button"
aria-label={t('primeLeaderboard.userRewards.marketActions')}
className="ml-2 shrink-0"

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.

To make it a bit more lively:

Suggested change
className="ml-2 shrink-0"
className="ml-2 shrink-0 transition-colors cursor-pointer hover:text-white"

onClick={() => setIsModalOpen(true)}
>
<Icon name="dotShortcut" className="text-light-grey" />
</button>

{isModalOpen && (
<Modal
isOpen
title={market.asset.vToken.underlyingToken.symbol}
handleClose={() => setIsModalOpen(false)}
>
<OperationForm
vToken={market.asset.vToken}
poolComptrollerAddress={market.poolComptrollerAddress}
onSubmitSuccess={() => setIsModalOpen(false)}
/>
</Modal>
)}
Comment on lines +42 to +54

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 recommend creating a MarketFormModal component (you can name it whatever you want) inside the containers directory to render this DOM, so that it can be shared with the MarketTable component (https://github.com/VenusProtocol/venus-protocol-interface/blob/088de2b0451f8a4ef5364cbaa7e3d2133b4d9bc8/apps/evm/src/containers/MarketTable/index.tsx).

This will allow keeping one source of truth and avoid issues such as one we have here which is that if the market has protection mode enabled, it won't be displayed in the title (unlike the modal used inside the MarketTable component):
Image

</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { screen } from '@testing-library/react';

import { usdc } from '__mocks__/models/tokens';
import { renderComponent } from 'testUtils/render';
import { MarketRewardRow } from '..';

describe('pages/PrimeLeaderboard/MarketRewardRow', () => {
it('renders the token, reward amount and trailing content', () => {
renderComponent(
<MarketRewardRow token={usdc} rewardsCents={28_040_000} totalRewardsCents={46_230_000}>
<span>3.78%</span>
</MarketRewardRow>,
);

expect(screen.getByText(usdc.symbol)).toBeInTheDocument();
expect(screen.getByText('$280.4K')).toBeInTheDocument();
expect(screen.getByText('3.78%')).toBeInTheDocument();
});
});
36 changes: 36 additions & 0 deletions apps/evm/src/pages/PrimeLeaderboard/MarketRewardRow/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { TokenIconWithSymbol } from 'components';
import type { Token } from 'types';
import { formatCentsToReadableValue } from 'utilities';

export interface MarketRewardRowProps {
token: Token;
rewardsCents: number;
totalRewardsCents: number;
children?: React.ReactNode;
}

export const MarketRewardRow: React.FC<MarketRewardRowProps> = ({
token,
rewardsCents,
totalRewardsCents,
children,
}) => {
const progressPercentage =
totalRewardsCents > 0 ? Math.min(100, (rewardsCents / totalRewardsCents) * 100) : 0;

return (
<div className="flex items-center">
<TokenIconWithSymbol token={token} className="shrink-0 text-b1s text-white" />

<span className="ml-auto text-p3r text-white">
{formatCentsToReadableValue({ value: rewardsCents })}
</span>

<div className="ml-1 h-1.5 w-1/4 shrink-0 overflow-hidden rounded-full bg-lightGrey">
<div className="h-full rounded-full bg-green" style={{ width: `${progressPercentage}%` }} />
</div>
Comment on lines +29 to +31

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.


{children}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { screen } from '@testing-library/react';

import { usdc, xvs } from '__mocks__/models/tokens';
import { renderComponent } from 'testUtils/render';
import { TotalRewardsCard } from '..';

describe('pages/PrimeLeaderboard/TotalRewardsCard', () => {
it('renders the total and per-market rewards', () => {
renderComponent(
<TotalRewardsCard
totalRewardsCents={46_230_000}
marketRewards={[
{ token: usdc, rewardsCents: 28_040_000 },
{ token: xvs, rewardsCents: 17_190_000 },
]}
/>,
);

expect(screen.getByText('Total Prime rewards distributed this cycle')).toBeInTheDocument();
expect(screen.getByText('$462.3K')).toBeInTheDocument();
expect(screen.getByText('$280.4K')).toBeInTheDocument();
expect(screen.getByText('$171.9K')).toBeInTheDocument();
expect(screen.getByText(usdc.symbol)).toBeInTheDocument();
expect(screen.getByText(xvs.symbol)).toBeInTheDocument();
});
});
Loading
Loading