Skip to content

Commit 74fa075

Browse files
authored
Merge pull request #1877 from cprussin/switch-to-react-aria
chore(staking): migrate to react-aria
2 parents d855f3f + 24017be commit 74fa075

File tree

20 files changed

+680
-666
lines changed

20 files changed

+680
-666
lines changed

apps/staking/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
"@amplitude/analytics-browser": "^2.9.3",
2525
"@amplitude/plugin-autocapture-browser": "^0.9.0",
2626
"@bonfida/spl-name-service": "^3.0.0",
27-
"@headlessui/react": "^2.1.2",
2827
"@heroicons/react": "^2.1.4",
2928
"@next/third-parties": "^14.2.5",
3029
"@pythnetwork/staking-sdk": "workspace:*",
@@ -42,8 +41,7 @@
4241
"react-aria-components": "^1.3.3",
4342
"react-dom": "^18.3.1",
4443
"recharts": "^2.12.7",
45-
"swr": "^2.2.5",
46-
"zod": "^3.23.8"
44+
"swr": "^2.2.5"
4745
},
4846
"devDependencies": {
4947
"@axe-core/react": "^4.9.1",

apps/staking/router.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import "react-aria-components";
2+
3+
declare module "react-aria-components" {
4+
import { useRouter } from "next/navigation";
5+
6+
export type RouterConfig = {
7+
routerOptions: NonNullable<
8+
Parameters<ReturnType<typeof useRouter>["push"]>[1]
9+
>;
10+
};
11+
}

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

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import Image from "next/image";
2-
import type { ComponentProps, ReactNode } from "react";
2+
import { type ComponentProps, type ReactNode, useCallback } from "react";
3+
import {
4+
DialogTrigger,
5+
Button as ReactAriaButton,
6+
} from "react-aria-components";
37

48
import background from "./background.png";
59
import { deposit, withdraw, claim } from "../../api";
610
import { StateType, useTransfer } from "../../hooks/use-transfer";
711
import { Button } from "../Button";
8-
import { Modal, ModalButton, ModalPanel } from "../Modal";
12+
import { ModalDialog } from "../ModalDialog";
913
import { Tokens } from "../Tokens";
1014
import { TransferButton } from "../TransferButton";
1115

@@ -71,14 +75,11 @@ export const AccountSummary = ({
7175
<Tokens>{locked}</Tokens>
7276
<div>locked included</div>
7377
</div>
74-
<Modal>
75-
<ModalButton
76-
as="button"
77-
className="mt-1 text-sm text-pythpurple-400 hover:underline"
78-
>
78+
<DialogTrigger>
79+
<ReactAriaButton className="mt-1 text-sm text-pythpurple-400 hover:underline focus:outline-none focus-visible:underline focus-visible:outline-none">
7980
Show Unlock Schedule
80-
</ModalButton>
81-
<ModalPanel
81+
</ReactAriaButton>
82+
<ModalDialog
8283
title="Unlock Schedule"
8384
description="Your tokens will become available for withdrawal and for participation in Integrity Staking according to this schedule"
8485
>
@@ -104,8 +105,8 @@ export const AccountSummary = ({
104105
</tbody>
105106
</table>
106107
</div>
107-
</ModalPanel>
108-
</Modal>
108+
</ModalDialog>
109+
</DialogTrigger>
109110
</>
110111
)}
111112
<div className="mt-3 flex flex-row items-center gap-4 sm:mt-8">
@@ -124,21 +125,21 @@ export const AccountSummary = ({
124125
description="The amount of unlocked tokens that are not staked in either program"
125126
action={
126127
<TransferButton
127-
small
128-
secondary
128+
size="small"
129+
variant="secondary"
129130
actionDescription="Move funds from your account back to your wallet"
130131
actionName="Withdraw"
131132
max={availableToWithdraw}
132133
transfer={withdraw}
133-
disabled={availableToWithdraw === 0n}
134+
isDisabled={availableToWithdraw === 0n}
134135
/>
135136
}
136137
/>
137138
<BalanceCategory
138139
name="Available Rewards"
139140
amount={availableRewards}
140141
description="Rewards you have earned from OIS"
141-
action={<ClaimButton disabled={availableRewards === 0n} />}
142+
action={<ClaimButton isDisabled={availableRewards === 0n} />}
142143
{...(expiringRewards !== undefined &&
143144
expiringRewards.amount > 0n && {
144145
warning: (
@@ -194,13 +195,19 @@ const ClaimButton = (
194195
) => {
195196
const { state, execute } = useTransfer(claim);
196197

198+
const doClaim = useCallback(() => {
199+
execute().catch(() => {
200+
/* TODO figure out a better UI treatment for when claim fails */
201+
});
202+
}, [execute]);
203+
197204
return (
198205
<Button
199-
small
200-
secondary
201-
onClick={execute}
202-
disabled={state.type !== StateType.Base}
203-
loading={state.type === StateType.Submitting}
206+
size="small"
207+
variant="secondary"
208+
onPress={doClaim}
209+
isDisabled={state.type !== StateType.Base}
210+
isLoading={state.type === StateType.Submitting}
204211
{...props}
205212
>
206213
Claim
Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,83 @@
1-
import type { ButtonHTMLAttributes } from "react";
1+
"use client";
22

3-
import { Styled } from "../Styled";
3+
import clsx from "clsx";
4+
import type { ComponentProps } from "react";
5+
import { Button as ReactAriaButton } from "react-aria-components";
46

5-
type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
6-
loading?: boolean | undefined;
7-
secondary?: boolean | undefined;
8-
small?: boolean | undefined;
9-
nopad?: boolean | undefined;
7+
import { Link } from "../Link";
8+
9+
type VariantProps = {
10+
variant?: "secondary" | undefined;
11+
size?: "small" | "nopad" | undefined;
1012
};
1113

12-
const ButtonBase = ({
13-
loading,
14-
secondary,
15-
small,
16-
nopad,
17-
disabled,
14+
type ButtonProps = ComponentProps<typeof ReactAriaButton> &
15+
VariantProps & {
16+
isLoading?: boolean | undefined;
17+
};
18+
19+
export const Button = ({
20+
isLoading,
21+
variant,
22+
size,
23+
isDisabled,
24+
className,
1825
...props
19-
}: Props) => (
20-
<button
21-
disabled={loading === true || disabled === true}
22-
{...(loading && { "data-loading": "" })}
23-
{...(secondary && { "data-secondary": "" })}
24-
{...(small && { "data-small": "" })}
25-
{...(nopad && { "data-nopad": "" })}
26+
}: ButtonProps) => (
27+
<ReactAriaButton
28+
isDisabled={isLoading === true || isDisabled === true}
29+
className={clsx(
30+
"disabled:border-neutral-50/10 disabled:bg-neutral-50/10 disabled:text-white/60",
31+
isLoading ? "cursor-wait" : "disabled:cursor-not-allowed",
32+
baseClassName({ variant, size }),
33+
className,
34+
)}
2635
{...props}
2736
/>
2837
);
2938

30-
export const Button = Styled(
31-
ButtonBase,
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-2 sm:px-4 md: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",
39+
type LinkButtonProps = ComponentProps<typeof Link> & VariantProps;
40+
41+
export const LinkButton = ({
42+
variant,
43+
size,
44+
className,
45+
...props
46+
}: LinkButtonProps) => (
47+
<Link
48+
className={clsx(baseClassName({ variant, size }), className)}
49+
{...props}
50+
/>
3351
);
52+
53+
const baseClassName = (props: VariantProps) =>
54+
clsx(
55+
"border border-pythpurple-600 hover:bg-pythpurple-600/60 focus:outline-none focus-visible:ring-1 focus-visible:ring-pythpurple-400",
56+
variantClassName(props.variant),
57+
sizeClassName(props.size),
58+
);
59+
60+
const variantClassName = (variant: VariantProps["variant"]) => {
61+
switch (variant) {
62+
case "secondary": {
63+
return "bg-pythpurple-600/20";
64+
}
65+
case undefined: {
66+
return "bg-pythpurple-600/50";
67+
}
68+
}
69+
};
70+
71+
const sizeClassName = (size: VariantProps["size"]) => {
72+
switch (size) {
73+
case "small": {
74+
return "text-sm px-2 sm:px-3 py-1";
75+
}
76+
case "nopad": {
77+
return "px-0 py-0";
78+
}
79+
case undefined: {
80+
return "px-2 sm:px-4 md:px-8 py-2";
81+
}
82+
}
83+
};

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

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
21
import { type ComponentProps, useMemo } from "react";
2+
import { Tabs, TabList, Tab, TabPanel } from "react-aria-components";
33

44
import { AccountSummary } from "../AccountSummary";
55
import { Governance } from "../Governance";
@@ -117,54 +117,55 @@ export const Dashboard = ({
117117
availableRewards={availableRewards}
118118
expiringRewards={expiringRewards}
119119
/>
120-
<TabGroup as="section">
121-
<TabList className="flex w-full flex-row text-sm font-medium sm:text-base">
122-
<DashboardTab>Overview</DashboardTab>
123-
<DashboardTab>Governance</DashboardTab>
124-
<DashboardTab>
120+
<Tabs>
121+
<TabList
122+
className="mb-8 flex w-full flex-row text-sm font-medium sm:text-base"
123+
aria-label="Programs"
124+
>
125+
<DashboardTab id={TabIds.Overview}>Overview</DashboardTab>
126+
<DashboardTab id={TabIds.Governance}>Governance</DashboardTab>
127+
<DashboardTab id={TabIds.IntegrityStaking}>
125128
<span className="sm:hidden">Integrity Staking</span>
126129
<span className="hidden sm:inline">
127130
Oracle Integrity Staking (OIS)
128131
</span>
129132
</DashboardTab>
130133
</TabList>
131-
<TabPanels className="mt-8">
132-
<DashboardTabPanel>
133-
<section className="py-20">
134-
<p className="text-center">
135-
This is an overview of the staking programs
136-
</p>
137-
</section>
138-
</DashboardTabPanel>
139-
<DashboardTabPanel>
140-
<Governance
141-
availableToStake={availableToStakeGovernance}
142-
warmup={governance.warmup}
143-
staked={governance.staked}
144-
cooldown={governance.cooldown}
145-
cooldown2={governance.cooldown2}
146-
/>
147-
</DashboardTabPanel>
148-
<DashboardTabPanel>
149-
<OracleIntegrityStaking
150-
availableToStake={availableToStakeIntegrity}
151-
locked={locked}
152-
warmup={integrityStakingWarmup}
153-
staked={integrityStakingStaked}
154-
cooldown={integrityStakingCooldown}
155-
cooldown2={integrityStakingCooldown2}
156-
publishers={integrityStakingPublishers}
157-
/>
158-
</DashboardTabPanel>
159-
</TabPanels>
160-
</TabGroup>
134+
<DashboardTabPanel id={TabIds.Overview}>
135+
<section className="py-20">
136+
<p className="text-center">
137+
This is an overview of the staking programs
138+
</p>
139+
</section>
140+
</DashboardTabPanel>
141+
<DashboardTabPanel id={TabIds.Governance}>
142+
<Governance
143+
availableToStake={availableToStakeGovernance}
144+
warmup={governance.warmup}
145+
staked={governance.staked}
146+
cooldown={governance.cooldown}
147+
cooldown2={governance.cooldown2}
148+
/>
149+
</DashboardTabPanel>
150+
<DashboardTabPanel id={TabIds.IntegrityStaking}>
151+
<OracleIntegrityStaking
152+
availableToStake={availableToStakeIntegrity}
153+
locked={locked}
154+
warmup={integrityStakingWarmup}
155+
staked={integrityStakingStaked}
156+
cooldown={integrityStakingCooldown}
157+
cooldown2={integrityStakingCooldown2}
158+
publishers={integrityStakingPublishers}
159+
/>
160+
</DashboardTabPanel>
161+
</Tabs>
161162
</div>
162163
);
163164
};
164165

165166
const DashboardTab = Styled(
166167
Tab,
167-
"grow basis-0 border-b border-neutral-600/50 px-4 py-2 focus-visible:outline-none data-[selected]:cursor-default data-[selected]:border-pythpurple-400 data-[selected]:data-[hover]:bg-transparent data-[hover]:text-pythpurple-400 data-[selected]:text-pythpurple-400 data-[focus]:outline-none data-[focus]:ring-1 data-[focus]:ring-pythpurple-400",
168+
"grow basis-0 border-b border-neutral-600/50 px-4 py-2 focus-visible:outline-none selected:cursor-default selected:border-pythpurple-400 selected:hover:bg-transparent hover:text-pythpurple-400 selected:text-pythpurple-400 focus:outline-none focus-visible:ring-1 focus-visible:ring-pythpurple-400 cursor-pointer text-center",
168169
);
169170

170171
const DashboardTabPanel = Styled(
@@ -187,3 +188,9 @@ const useIntegrityStakingSum = (
187188

188189
// eslint-disable-next-line unicorn/no-array-reduce
189190
const bigIntMin = (...args: bigint[]) => args.reduce((m, e) => (e < m ? e : m));
191+
192+
enum TabIds {
193+
Overview,
194+
Governance,
195+
IntegrityStaking,
196+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const Error = ({ error, reset }: Props) => {
2727
<strong className="mb-8 border border-pythpurple-400/20 bg-pythpurple-600/50 px-1 py-0.5 text-sm opacity-50">
2828
{error.digest ?? error.message}
2929
</strong>
30-
{reset && <Button onClick={reset}>Reset</Button>}
30+
{reset && <Button onPress={reset}>Reset</Button>}
3131
</main>
3232
);
3333
};

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import LinkedIn from "./linkedin.svg";
77
import Telegram from "./telegram.svg";
88
import X from "./x.svg";
99
import Youtube from "./youtube.svg";
10+
import { Link } from "../Link";
1011
import { MaxWidth } from "../MaxWidth";
1112

1213
const SOCIAL_LINKS = [
@@ -46,7 +47,7 @@ export const Footer = ({
4647
<div>© 2024 Pyth Data Association</div>
4748
<div className="relative -right-3 flex h-full items-center">
4849
{SOCIAL_LINKS.map(({ name, icon: Icon, href }) => (
49-
<a
50+
<Link
5051
target="_blank"
5152
href={href}
5253
key={name}
@@ -55,7 +56,7 @@ export const Footer = ({
5556
>
5657
<Icon className="size-4" />
5758
<span className="sr-only">{name}</span>
58-
</a>
59+
</Link>
5960
))}
6061
</div>
6162
</MaxWidth>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const NoWalletHome = () => {
7272
publishers.
7373
</p>
7474
<div className="grid w-full place-content-center">
75-
<Button onClick={showModal}>Connect your wallet to participate</Button>
75+
<Button onPress={showModal}>Connect your wallet to participate</Button>
7676
</div>
7777
</main>
7878
);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"use client";
2+
3+
export { Link } from "react-aria-components";

0 commit comments

Comments
 (0)