Skip to content

Commit 92efb88

Browse files
committed
feat(staking): add stats to navbar
1 parent 7254d7b commit 92efb88

File tree

7 files changed

+117
-23
lines changed

7 files changed

+117
-23
lines changed

apps/staking/src/components/Header/current-stake-account.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const CurrentStakeAccount = ({
2020
return api.type === ApiStateType.Loaded && !isBlocked ? (
2121
<div
2222
className={clsx(
23-
"hidden flex-col items-end justify-center text-xs xs:flex md:flex-row md:items-center md:text-sm",
23+
"hidden flex-col items-end justify-center text-xs xs:flex xl:flex-row xl:items-center xl:text-sm",
2424
className,
2525
)}
2626
{...props}

apps/staking/src/components/Header/help-menu.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import { useState, useCallback } from "react";
88
import { MenuTrigger, Button } from "react-aria-components";
99

10-
import { ProgramParameters } from "./program-pramaeters";
10+
import { ProgramParameters } from "./program-parameters";
1111
import { StateType, useApi } from "../../hooks/use-api";
1212
import { GeneralFaq } from "../GeneralFaq";
1313
import { GovernanceGuide } from "../GovernanceGuide";
@@ -45,9 +45,9 @@ export const HelpMenu = () => {
4545
return (
4646
<>
4747
<MenuTrigger>
48-
<Button className="group -mx-2 flex flex-row items-center gap-2 rounded-sm p-2 transition hover:bg-white/10 focus:outline-none focus-visible:ring-1 focus-visible:ring-pythpurple-400 pressed:bg-white/10 sm:-mx-4 sm:px-4">
48+
<Button className="group -mx-2 flex flex-row items-center gap-2 rounded-sm p-2 transition hover:bg-white/10 focus:outline-none focus-visible:ring-1 focus-visible:ring-pythpurple-400 pressed:bg-white/10 md:-mx-4 md:px-4">
4949
<QuestionMarkCircleIcon className="size-6 flex-none" />
50-
<span className="sr-only xs:not-sr-only">Help</span>
50+
<span className="sr-only md:not-sr-only">Help</span>
5151
<ChevronDownIcon className="size-4 flex-none opacity-60 transition duration-300 group-data-[pressed]:-rotate-180" />
5252
</Button>
5353
<Menu placement="bottom end">

apps/staking/src/components/Header/index.tsx

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { CurrentStakeAccount } from "./current-stake-account";
55
import { HelpMenu } from "./help-menu";
66
import Logo from "./logo.svg";
77
import Logomark from "./logomark.svg";
8+
import { Stats } from "./stats";
89
import { Link } from "../Link";
910
import { MaxWidth } from "../MaxWidth";
1011
import { WalletButton } from "../WalletButton";
@@ -13,22 +14,31 @@ export const Header = ({
1314
className,
1415
...props
1516
}: Omit<HTMLAttributes<HTMLElement>, "children">) => (
16-
<header className={clsx("sticky top-0 w-full lg:px-4", className)} {...props}>
17-
<div className="border-b border-neutral-600/50 bg-pythpurple-800 lg:border-x">
18-
<MaxWidth className="flex h-header items-center justify-between gap-2 lg:-mx-4">
19-
<Link
20-
href="/"
21-
className="-mx-2 h-[calc(var(--header-height)_-_0.5rem)] rounded-sm p-2 text-pythpurple-100 focus:outline-none focus-visible:ring-1 focus-visible:ring-pythpurple-400"
22-
>
23-
<Logo className="hidden h-full sm:block" />
24-
<Logomark className="h-full sm:hidden" />
25-
</Link>
26-
<div className="flex flex-none flex-row items-stretch gap-4 sm:gap-8">
27-
<CurrentStakeAccount />
28-
<WalletButton className="flex-none" />
29-
<HelpMenu />
30-
</div>
31-
</MaxWidth>
32-
</div>
33-
</header>
17+
<>
18+
<header
19+
className={clsx("sticky top-0 w-full lg:px-4", className)}
20+
{...props}
21+
>
22+
<div className="border-b border-neutral-600/50 bg-pythpurple-800 lg:border-x">
23+
<MaxWidth className="flex h-header items-center justify-between gap-2 lg:-mx-4">
24+
<div className="flex flex-row items-center gap-8 lg:gap-12">
25+
<Link
26+
href="/"
27+
className="-mx-2 h-[calc(var(--header-height)_-_0.5rem)] rounded-sm p-2 text-pythpurple-100 focus:outline-none focus-visible:ring-1 focus-visible:ring-pythpurple-400"
28+
>
29+
<Logo className="hidden h-full lg:block" />
30+
<Logomark className="h-full lg:hidden" />
31+
</Link>
32+
<Stats className="hidden gap-4 sm:flex lg:gap-6" />
33+
</div>
34+
<div className="flex flex-none flex-row items-stretch gap-4 md:gap-8">
35+
<CurrentStakeAccount />
36+
<WalletButton className="flex-none" />
37+
<HelpMenu />
38+
</div>
39+
</MaxWidth>
40+
</div>
41+
</header>
42+
<Stats className="border-b border-neutral-600/50 py-4 text-center sm:hidden" />
43+
</>
3444
);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"use client";
2+
3+
import { PythStakingClient } from "@pythnetwork/staking-sdk";
4+
import { useConnection } from "@solana/wallet-adapter-react";
5+
import { Connection } from "@solana/web3.js";
6+
import clsx from "clsx";
7+
import type { HTMLProps } from "react";
8+
9+
import { StateType, useData } from "../../hooks/use-data";
10+
import { Tokens } from "../Tokens";
11+
12+
const ONE_SECOND_IN_MS = 1000;
13+
const ONE_MINUTE_IN_MS = 60 * ONE_SECOND_IN_MS;
14+
const REFRESH_INTERVAL = 1 * ONE_MINUTE_IN_MS;
15+
const INITIAL_REWARD_POOL_SIZE = 60_000_000_000_000n;
16+
17+
export const Stats = ({ className, ...props }: HTMLProps<HTMLDivElement>) => {
18+
const { connection } = useConnection();
19+
const state = useData("poolStats", () => fetchStats(connection), {
20+
refreshInterval: REFRESH_INTERVAL,
21+
});
22+
23+
return (
24+
<div className={clsx("flex flex-row items-stretch", className)} {...props}>
25+
<div className="flex-1 sm:flex-none">
26+
{state.type === StateType.Loaded ? (
27+
<Tokens className="mb-1 text-xl font-semibold leading-none">
28+
{state.data.totalStaked}
29+
</Tokens>
30+
) : (
31+
<Loading />
32+
)}
33+
<div className="text-xs leading-none text-pythpurple-400">
34+
OIS Total Staked
35+
</div>
36+
</div>
37+
<div className="border-l border-neutral-600/50" />
38+
<div className="flex-1 sm:flex-none">
39+
{state.type === StateType.Loaded ? (
40+
<Tokens className="mb-1 text-xl font-semibold leading-none">
41+
{state.data.rewardsDistributed}
42+
</Tokens>
43+
) : (
44+
<Loading />
45+
)}
46+
<div className="text-xs leading-none text-pythpurple-400">
47+
OIS Rewards Distributed
48+
</div>
49+
</div>
50+
</div>
51+
);
52+
};
53+
54+
const Loading = () => (
55+
<div className="mb-1 h-5 w-10 animate-pulse rounded-md bg-white/30" />
56+
);
57+
58+
const fetchStats = async (connection: Connection) => {
59+
const client = new PythStakingClient({ connection });
60+
const poolData = await client.getPoolDataAccount();
61+
const totalDelegated = sum(
62+
poolData.delState.map(
63+
({ totalDelegation, deltaDelegation }) =>
64+
totalDelegation + deltaDelegation,
65+
),
66+
);
67+
const totalSelfStaked = sum(
68+
poolData.selfDelState.map(
69+
({ totalDelegation, deltaDelegation }) =>
70+
totalDelegation + deltaDelegation,
71+
),
72+
);
73+
74+
return {
75+
totalStaked: totalDelegated + totalSelfStaked,
76+
rewardsDistributed: poolData.claimableRewards + INITIAL_REWARD_POOL_SIZE,
77+
};
78+
};
79+
80+
const sum = (values: bigint[]): bigint =>
81+
values.reduce((acc, value) => acc + value, 0n);

apps/staking/src/components/Tokens/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import clsx from "clsx";
24
import * as dnum from "dnum";
35
import { type ComponentProps, useMemo } from "react";

apps/staking/src/components/WalletButton/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,8 @@ const ButtonComponent = ({
295295
...props
296296
}: ButtonComponentProps) => (
297297
<Button
298-
className={clsx("w-36 text-sm sm:w-52 sm:text-base", className)}
298+
className={clsx("w-36 text-sm lg:w-52 lg:text-base", className)}
299+
size="nopad"
299300
{...props}
300301
>
301302
<WalletIcon className="size-4 flex-none opacity-60" />

0 commit comments

Comments
 (0)