Skip to content

Commit c252a1c

Browse files
authored
Merge pull request #1868 from cprussin/staking-app-styling
Staking app styling
2 parents 14dc53f + 9b27ce2 commit c252a1c

File tree

47 files changed

+3419
-1624
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+3419
-1624
lines changed

apps/staking/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"next": "^14.2.5",
3737
"pino": "^9.3.2",
3838
"react": "^18.3.1",
39+
"react-aria": "^3.34.3",
3940
"react-dom": "^18.3.1",
4041
"recharts": "^2.12.7",
4142
"swr": "^2.2.5",

apps/staking/src/api.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,22 @@ export const calculateApy = (
290290
);
291291
};
292292

293+
export const getUpcomingEpoch = (): Date => {
294+
const d = new Date();
295+
d.setUTCDate(d.getUTCDate() + ((5 + 7 - d.getUTCDay()) % 7 || 7));
296+
d.setUTCHours(0);
297+
d.setUTCMinutes(0);
298+
d.setUTCSeconds(0);
299+
d.setUTCMilliseconds(0);
300+
return d;
301+
};
302+
303+
export const getNextFullEpoch = (): Date => {
304+
const d = getUpcomingEpoch();
305+
d.setUTCDate(d.getUTCDate() + 7);
306+
return d;
307+
};
308+
293309
const MOCK_DELAY = 500;
294310

295311
const MOCK_STAKE_ACCOUNTS: StakeAccount[] = [

apps/staking/src/app/robots.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { MetadataRoute } from "next";
22

3-
import { IS_PRODUCTION_SERVER } from "../server-config";
3+
import { IS_PRODUCTION_SERVER } from "../config/server";
44

55
const robots = (): MetadataRoute.Robots => ({
66
rules: {

apps/staking/src/components/AccountHistoryButton/index.tsx renamed to apps/staking/src/components/AccountHistory/index.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,15 @@ import {
77
StakeType,
88
loadAccountHistory,
99
} from "../../api";
10-
import { useApiContext } from "../../use-api-context";
10+
import { useApiContext } from "../../hooks/use-api-context";
1111
import { LoadingSpinner } from "../LoadingSpinner";
12-
import { ModalButton } from "../ModalButton";
1312
import { Tokens } from "../Tokens";
1413

1514
const ONE_SECOND_IN_MS = 1000;
1615
const ONE_MINUTE_IN_MS = 60 * ONE_SECOND_IN_MS;
1716
const REFRESH_INTERVAL = 1 * ONE_MINUTE_IN_MS;
1817

19-
export const AccountHistoryButton = () => (
20-
<ModalButton
21-
title="Account history"
22-
description="A history of events that have affected your account balances"
23-
>
24-
<ModalBody />
25-
</ModalButton>
26-
);
27-
28-
const ModalBody = () => {
18+
export const AccountHistory = () => {
2919
const history = useAccountHistoryData();
3020

3121
switch (history.type) {
1.39 MB
Loading
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import Image from "next/image";
2+
import type { ComponentProps, ReactNode } from "react";
3+
4+
import background from "./background.png";
5+
import { deposit, withdraw, claim } from "../../api";
6+
import { StateType, useTransfer } from "../../hooks/use-transfer";
7+
import { Button } from "../Button";
8+
import { Modal, ModalButton, ModalPanel } from "../Modal";
9+
import { Tokens } from "../Tokens";
10+
import { TransferButton } from "../TransferButton";
11+
12+
type Props = {
13+
total: bigint;
14+
locked: bigint;
15+
unlockSchedule: {
16+
amount: bigint;
17+
date: Date;
18+
}[];
19+
lastSlash:
20+
| {
21+
amount: bigint;
22+
date: Date;
23+
}
24+
| undefined;
25+
walletAmount: bigint;
26+
availableRewards: bigint;
27+
expiringRewards: {
28+
amount: bigint;
29+
expiry: Date;
30+
};
31+
availableToWithdraw: bigint;
32+
};
33+
34+
export const AccountSummary = ({
35+
locked,
36+
unlockSchedule,
37+
lastSlash,
38+
walletAmount,
39+
total,
40+
availableToWithdraw,
41+
availableRewards,
42+
expiringRewards,
43+
}: Props) => (
44+
<section className="relative w-full overflow-hidden border border-neutral-600/50 bg-pythpurple-800">
45+
<Image
46+
src={background}
47+
alt=""
48+
className="absolute -right-40 h-full object-right [mask-image:linear-gradient(to_right,_transparent,_black_50%)]"
49+
/>
50+
<div className="relative flex flex-col items-start justify-between gap-16 px-12 py-20 md:flex-row md:items-center">
51+
<div>
52+
<div className="mb-4 inline-block border border-neutral-600/50 bg-neutral-900 px-4 py-1 text-xs text-neutral-400">
53+
Total Balance
54+
</div>
55+
<div className="flex flex-row items-center gap-8">
56+
<span>
57+
<Tokens className="text-6xl font-light">{total}</Tokens>
58+
</span>
59+
{lastSlash && (
60+
<p className="max-w-48 text-sm text-red-600">
61+
<Tokens>{lastSlash.amount}</Tokens> were slashed on{" "}
62+
{lastSlash.date.toLocaleString()}
63+
</p>
64+
)}
65+
</div>
66+
<div className="mt-8 flex flex-row items-center gap-4">
67+
<TransferButton
68+
actionDescription="Add funds to your balance"
69+
actionName="Deposit"
70+
max={walletAmount}
71+
transfer={deposit}
72+
/>
73+
</div>
74+
{locked > 0n && (
75+
<>
76+
<div className="mt-6 flex flex-row items-center gap-1 text-xl text-pythpurple-100/50">
77+
<Tokens>{locked}</Tokens>
78+
<div>locked</div>
79+
</div>
80+
<Modal>
81+
<ModalButton
82+
as="button"
83+
className="mt-1 text-sm text-pythpurple-400 hover:underline"
84+
>
85+
Show Unlock Schedule
86+
</ModalButton>
87+
<ModalPanel
88+
title="Unlock Schedule"
89+
description="Your tokens will become available for withdrawal and for participation in Integrity Staking according to this schedule"
90+
>
91+
<div className="border border-neutral-600/50 bg-pythpurple-100/10 px-8 py-6">
92+
<table>
93+
<thead className="font-medium">
94+
<tr>
95+
<td className="pr-12 text-sm text-neutral-400">Date</td>
96+
<td className="text-sm text-neutral-400">Amount</td>
97+
</tr>
98+
</thead>
99+
<tbody>
100+
{unlockSchedule.map((unlock, i) => (
101+
<tr key={i}>
102+
<td className="pr-12 text-sm opacity-80">
103+
{unlock.date.toLocaleString()}
104+
</td>
105+
<td>
106+
<Tokens>{unlock.amount}</Tokens>
107+
</td>
108+
</tr>
109+
))}
110+
</tbody>
111+
</table>
112+
</div>
113+
</ModalPanel>
114+
</Modal>
115+
</>
116+
)}
117+
</div>
118+
<div className="flex flex-col items-stretch gap-4 xl:flex-row">
119+
<BalanceCategory
120+
name="Available for Withdrawal"
121+
amount={availableToWithdraw}
122+
description="The lesser of the amount you have available to stake in governance & integrity staking"
123+
action={
124+
<TransferButton
125+
small
126+
secondary
127+
actionDescription="Move funds from your account back to your wallet"
128+
actionName="Withdraw"
129+
max={availableToWithdraw}
130+
transfer={withdraw}
131+
disabled={availableToWithdraw === 0n}
132+
/>
133+
}
134+
/>
135+
<BalanceCategory
136+
name="Available Rewards"
137+
amount={availableRewards}
138+
description="Rewards you have earned but not yet claimed from the Integrity Staking program"
139+
action={<ClaimButton disabled={availableRewards === 0n} />}
140+
{...(expiringRewards.amount > 0n && {
141+
warning: (
142+
<>
143+
<Tokens>{expiringRewards.amount}</Tokens> will expire on{" "}
144+
{expiringRewards.expiry.toLocaleDateString()}
145+
</>
146+
),
147+
})}
148+
/>
149+
</div>
150+
</div>
151+
</section>
152+
);
153+
154+
type BalanceCategoryProps = {
155+
name: string;
156+
amount: bigint;
157+
description: string;
158+
action: ReactNode;
159+
warning?: ReactNode | undefined;
160+
};
161+
162+
const BalanceCategory = ({
163+
name,
164+
amount,
165+
description,
166+
action,
167+
warning,
168+
}: BalanceCategoryProps) => (
169+
<div className="flex flex-col justify-between border border-neutral-600/50 bg-pythpurple-800/60 p-6 backdrop-blur">
170+
<div>
171+
<div className="mb-4 inline-block border border-neutral-600/50 bg-neutral-900 px-4 py-1 text-xs text-neutral-400">
172+
{name}
173+
</div>
174+
<div>
175+
<Tokens className="text-xl font-light">{amount}</Tokens>
176+
</div>
177+
<p className="mt-4 max-w-xs text-sm text-neutral-500">{description}</p>
178+
</div>
179+
<div className="mt-4 flex flex-row items-center gap-4">
180+
{action}
181+
{warning && <p className="max-w-xs text-xs text-red-600">{warning}</p>}
182+
</div>
183+
</div>
184+
);
185+
186+
const ClaimButton = (
187+
props: Omit<
188+
ComponentProps<typeof Button>,
189+
"onClick" | "disabled" | "loading"
190+
>,
191+
) => {
192+
const { state, execute } = useTransfer(claim);
193+
194+
return (
195+
<Button
196+
small
197+
secondary
198+
onClick={execute}
199+
disabled={state.type !== StateType.Base}
200+
loading={state.type === StateType.Submitting}
201+
{...props}
202+
>
203+
Claim
204+
</Button>
205+
);
206+
};

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,30 @@ import { Styled } from "../Styled";
44

55
type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
66
loading?: boolean | undefined;
7+
secondary?: boolean | undefined;
8+
small?: boolean | undefined;
9+
nopad?: boolean | undefined;
710
};
811

9-
const ButtonBase = ({ loading, disabled, children, ...props }: Props) => (
12+
const ButtonBase = ({
13+
loading,
14+
secondary,
15+
small,
16+
nopad,
17+
disabled,
18+
...props
19+
}: Props) => (
1020
<button
1121
disabled={loading === true || disabled === true}
12-
{...(loading && { "data-loading": true })}
22+
{...(loading && { "data-loading": "" })}
23+
{...(secondary && { "data-secondary": "" })}
24+
{...(small && { "data-small": "" })}
25+
{...(nopad && { "data-nopad": "" })}
1326
{...props}
14-
>
15-
{children}
16-
</button>
27+
/>
1728
);
1829

1930
export const Button = Styled(
2031
ButtonBase,
21-
"border border-pythpurple-600 px-2 py-0.5 bg-black/10 disabled:cursor-not-allowed disabled:bg-black/20 disabled:border-black/40 disabled:text-neutral-700 disabled:data-[loading]:cursor-wait",
32+
"border border-pythpurple-600 bg-pythpurple-600/50 data-[small]:text-sm data-[small]:px-6 data-[small]:py-1 data-[secondary]:bg-pythpurple-600/20 px-8 py-2 data-[nopad]:px-0 data-[nopad]:py-0 disabled:cursor-not-allowed disabled:bg-neutral-50/10 disabled:border-neutral-50/10 disabled:text-white/60 disabled:data-[loading]:cursor-wait hover:bg-pythpurple-600/60 data-[secondary]:hover:bg-pythpurple-600/60 data-[secondary]:disabled:bg-neutral-50/10 focus-visible:ring-1 focus-visible:ring-pythpurple-400 focus:outline-none justify-center",
2233
);

0 commit comments

Comments
 (0)