diff --git a/apps/staking/src/components/Dashboard/index.tsx b/apps/staking/src/components/Dashboard/index.tsx index 3f58ea4fb3..b505daecff 100644 --- a/apps/staking/src/components/Dashboard/index.tsx +++ b/apps/staking/src/components/Dashboard/index.tsx @@ -241,7 +241,7 @@ const Journey = ({ )} {...props} > -
+
{longText} diff --git a/apps/staking/src/components/Header/help-menu.tsx b/apps/staking/src/components/Header/help-menu.tsx index 3a1f513d8e..6beefc5718 100644 --- a/apps/staking/src/components/Header/help-menu.tsx +++ b/apps/staking/src/components/Header/help-menu.tsx @@ -39,7 +39,7 @@ export const HelpMenu = () => { diff --git a/apps/staking/src/components/ModalDialog/index.tsx b/apps/staking/src/components/ModalDialog/index.tsx index ca57fc5f91..5b21ae90f6 100644 --- a/apps/staking/src/components/ModalDialog/index.tsx +++ b/apps/staking/src/components/ModalDialog/index.tsx @@ -40,7 +40,7 @@ export const ModalDialog = ({ {...props} > - + {(options) => ( <> - ), - )} -
+ {numPages > 1 && ( + )}
); }; +type PaginatorProps = { + currentPage: number; + numPages: number; + onPageChange: (newPage: number) => void; +}; + +const Paginator = ({ currentPage, numPages, onPageChange }: PaginatorProps) => { + const { first, count } = getPageRange(currentPage, numPages); + const pages = Array.from({ length: count }) + .fill(undefined) + .map((_, i) => i + first); + + return ( + + ); +}; + +const getPageRange = ( + page: number, + numPages: number, +): { first: number; count: number } => { + const first = + page <= 3 || numPages <= 5 + ? 1 + : page - 2 - Math.max(2 - (numPages - page), 0); + return { first, count: Math.min(numPages - first + 1, 5) }; +}; + const doSort = ( a: PublisherProps["publisher"], b: PublisherProps["publisher"], @@ -823,8 +989,6 @@ const doSort = ( } }; -const range = (length: number) => [...Array.from({ length }).keys()]; - type SortablePublisherTableHeaderProps = Omit< ComponentProps, "children" @@ -875,7 +1039,7 @@ const SortablePublisherTableHeader = ({ const PublisherTableHeader = Styled( "th", - "py-2 font-normal px-5 whitespace-nowrap", + "py-2 font-normal px-5 h-full whitespace-nowrap", ); type PublisherProps = { @@ -906,6 +1070,7 @@ type PublisherProps = { | undefined; }; yieldRate: bigint; + compact?: boolean | undefined; }; const Publisher = ({ @@ -916,6 +1081,7 @@ const Publisher = ({ totalStaked, isSelf, yieldRate, + compact, }: PublisherProps) => { const warmup = useMemo( () => @@ -944,29 +1110,115 @@ const Publisher = ({ api.type === ApiStateType.Loaded ? api.unstakeIntegrityStaking : undefined, publisher.publicKey, ); - const utilizationPercent = useMemo( + + const estimatedNextApy = useMemo( () => - publisher.poolCapacity > 0n - ? Number( - (100n * - (publisher.poolUtilization + publisher.poolUtilizationDelta)) / - publisher.poolCapacity, - ) - : Number.NaN, + calculateApy({ + isSelf: isSelf ?? false, + selfStake: publisher.selfStake + publisher.selfStakeDelta, + poolCapacity: publisher.poolCapacity, + poolUtilization: + publisher.poolUtilization + publisher.poolUtilizationDelta, + yieldRate, + }).toFixed(2), [ + isSelf, + publisher.selfStake, + publisher.selfStakeDelta, + publisher.poolCapacity, publisher.poolUtilization, publisher.poolUtilizationDelta, - publisher.poolCapacity, + yieldRate, ], ); - return ( + return compact ? ( +
+ {!isSelf && ( +
+ + {publisher} + + +
+ )} +
+
+ {isSelf && ( + + )} + +
+
+ {!isSelf && ( +
+
{"Publisher's Stake:"}
+
+ {publisher.selfStake} +
+
+ )} +
+
Estimated Next APY:
+
{estimatedNextApy}%
+
+
+
Number of feeds:
+
{publisher.numFeeds}
+
+
+
Quality ranking:
+
+ {publisher.qualityRanking === 0 ? "-" : publisher.qualityRanking} +
+
+
+
+ {(warmup !== undefined || staked !== undefined) && ( + + )} +
+ ) : ( <> {!isSelf && ( <> - {publisher} + + {publisher} + {publisher.selfStake} @@ -974,57 +1226,10 @@ const Publisher = ({ )} - - {({ percentage }) => ( - <> -
-
-
- {Number.isNaN(utilizationPercent) - ? "Empty Pool" - : `${utilizationPercent.toString()}%`} -
-
- - - )} - + -
- {calculateApy({ - isSelf: isSelf ?? false, - selfStake: publisher.selfStake + publisher.selfStakeDelta, - poolCapacity: publisher.poolCapacity, - poolUtilization: - publisher.poolUtilization + publisher.poolUtilizationDelta, - yieldRate, - }).toFixed(2)} - % -
+ {estimatedNextApy}%
@@ -1058,88 +1263,15 @@ const Publisher = ({ {(warmup !== undefined || staked !== undefined) && ( -
- - - - {warmup !== undefined && ( - - - - - - )} - {staked !== undefined && ( - - - - - - )} - -
- Your Positions -
Warmup - {warmup} - - - - Cancel tokens that are in warmup for staking to - - - {publisher} - - - } - actionName="Cancel" - submitButtonText="Cancel Warmup" - title="Cancel Warmup" - max={warmup} - transfer={cancelWarmup} - /> -
Staked -
- {staked} -
- ({Number((100n * staked) / totalStaked)}% of your - staked tokens) -
-
-
- - - Unstake tokens from - - - {publisher} - - - } - actionName="Unstake" - max={staked} - transfer={unstake} - > - - -
-
+ )} @@ -1147,6 +1279,168 @@ const Publisher = ({ ); }; +type UtilizationMeterProps = Omit, "children"> & { + publisher: PublisherProps["publisher"]; +}; + +const UtilizationMeter = ({ publisher, ...props }: UtilizationMeterProps) => { + const utilizationPercent = useMemo( + () => + publisher.poolCapacity > 0n + ? Number( + (100n * + (publisher.poolUtilization + publisher.poolUtilizationDelta)) / + publisher.poolCapacity, + ) + : Number.NaN, + [ + publisher.poolUtilization, + publisher.poolUtilizationDelta, + publisher.poolCapacity, + ], + ); + + return ( + + {({ percentage }) => ( + <> +
+
+
+ {Number.isNaN(utilizationPercent) + ? "Empty Pool" + : `${utilizationPercent.toString()}%`} +
+
+ + + )} + + ); +}; + +type YourPositionsTableProps = { + publisher: PublisherProps["publisher"]; + warmup: bigint | undefined; + cancelWarmup: ((amount: bigint) => Promise) | undefined; + staked: bigint | undefined; + totalStaked: bigint; + unstake: ((amount: bigint) => Promise) | undefined; + currentEpoch: bigint; +}; + +const YourPositionsTable = ({ + warmup, + cancelWarmup, + staked, + totalStaked, + unstake, + currentEpoch, + publisher, +}: YourPositionsTableProps) => ( +
+ + + + {warmup !== undefined && ( + + + + + + )} + {staked !== undefined && ( + + + + + + )} + +
+ Your Positions +
Warmup + {warmup} + + + + Cancel tokens that are in warmup for staking to + + + {publisher} + + + } + actionName="Cancel" + submitButtonText="Cancel Warmup" + title="Cancel Warmup" + max={warmup} + transfer={cancelWarmup} + /> +
Staked +
+ {staked} +
+ ({Number((100n * staked) / totalStaked)}% of your staked + tokens) +
+
+
+ + + Unstake tokens from + + + {publisher} + + + } + actionName="Unstake" + max={staked} + transfer={unstake} + > + + +
+
+); + const PublisherTableCell = Styled("td", "py-4 px-5 whitespace-nowrap"); type StakeToPublisherButtonProps = { @@ -1259,10 +1553,16 @@ const NewApy = ({ type PublisherNameProps = { className?: string | undefined; children: PublisherProps["publisher"]; - fullKey?: boolean | undefined; + fullClassName?: string; + truncatedClassName?: string; }; -const PublisherName = ({ children, fullKey, className }: PublisherNameProps) => +const PublisherName = ({ + children, + fullClassName, + truncatedClassName, + className, +}: PublisherNameProps) => children.name ? ( {children.name} ) : ( @@ -1270,12 +1570,10 @@ const PublisherName = ({ children, fullKey, className }: PublisherNameProps) => text={children.publicKey.toBase58()} {...(className && { className })} > - {fullKey === true && ( - - {children.publicKey.toBase58()} - + {fullClassName && ( + {children.publicKey.toBase58()} )} - + {children.publicKey} @@ -1311,6 +1609,15 @@ enum SortField { QualityRanking, } +const SORT_FIELD_TO_NAME: Record = { + [SortField.PublisherName]: "Publisher Name", + [SortField.PoolUtilization]: "Pool Utilization", + [SortField.APY]: "Estimated Next APY", + [SortField.SelfStake]: "Publisher's Stake", + [SortField.NumberOfFeeds]: "Number of Feeds", + [SortField.QualityRanking]: "Quality Ranking", +} as const; + class InvalidKeyError extends Error { constructor() { super("Invalid public key"); diff --git a/apps/staking/src/components/StakingTimeline/index.tsx b/apps/staking/src/components/StakingTimeline/index.tsx index 932313ec47..f7a4d86e6b 100644 --- a/apps/staking/src/components/StakingTimeline/index.tsx +++ b/apps/staking/src/components/StakingTimeline/index.tsx @@ -9,7 +9,7 @@ type Props = { export const StakingTimeline = ({ cooldownOnly, currentEpoch }: Props) => (
Timeline
-
+
{!cooldownOnly && ( <>
diff --git a/apps/staking/tailwind.config.ts b/apps/staking/tailwind.config.ts index 12cdd5c81f..1fc1e2460c 100644 --- a/apps/staking/tailwind.config.ts +++ b/apps/staking/tailwind.config.ts @@ -43,6 +43,7 @@ const tailwindConfig = { }, screens: { xs: "412px", + "3xl": "2560px", }, }, },