+
{title}
-
+
+
@@ -664,108 +752,186 @@ const PublisherList = ({
{filteredSortedPublishers.length > 0 ? (
-
-
-
-
- Publisher
-
-
- {"Publisher's stake"}
-
-
- Pool
-
-
- Estimated next APY
-
- Historical APY
-
- Number of feeds
-
-
- Quality ranking
-
-
-
-
-
-
+ <>
+
{paginatedPublishers.map((publisher) => (
-
+ -
+
+
))}
-
-
+
+
+
+
+
+ Publisher
+
+
+ {"Publisher's stake"}
+
+
+ Pool
+
+
+ Estimated next APY
+
+ Historical APY
+
+ Number of feeds
+
+
+ Quality ranking
+
+
+
+
+
+
+ {paginatedPublishers.map((publisher) => (
+
+ ))}
+
+
+ >
) : (
No results match your query
)}
- {filteredSortedPublishers.length > PAGE_SIZE && (
-
- {range(Math.ceil(filteredSortedPublishers.length / PAGE_SIZE)).map(
- (page) =>
- page === currentPage ? (
-
- {page + 1}
-
- ) : (
-
- ),
- )}
-
+ {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 (
+
+ {currentPage > 1 && (
+ -
+
+
+ )}
+ {pages.map((page) =>
+ page === currentPage ? (
+ -
+ {page}
+
+ ) : (
+ -
+
+
+ ),
+ )}
+ {currentPage < numPages && (
+ -
+
+
+ )}
+
+ );
+};
+
+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 && (
+
+ )}
+
+
+ {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) && (
-
-
-
- Your Positions
-
-
- {warmup !== undefined && (
-
- | 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 !== undefined && (
-
- | 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) => (
+
+
+
+ Your Positions
+
+
+ {warmup !== undefined && (
+
+ | 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 !== undefined && (
+
+ | 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",
},
},
},