diff --git a/apps/staking/package.json b/apps/staking/package.json index 0f32a19fd2..aaceb7fe64 100644 --- a/apps/staking/package.json +++ b/apps/staking/package.json @@ -24,7 +24,6 @@ "@amplitude/analytics-browser": "^2.9.3", "@amplitude/plugin-autocapture-browser": "^0.9.0", "@bonfida/spl-name-service": "^3.0.0", - "@headlessui/react": "^2.1.2", "@heroicons/react": "^2.1.4", "@next/third-parties": "^14.2.5", "@pythnetwork/staking-sdk": "workspace:*", @@ -42,8 +41,7 @@ "react-aria-components": "^1.3.3", "react-dom": "^18.3.1", "recharts": "^2.12.7", - "swr": "^2.2.5", - "zod": "^3.23.8" + "swr": "^2.2.5" }, "devDependencies": { "@axe-core/react": "^4.9.1", diff --git a/apps/staking/router.d.ts b/apps/staking/router.d.ts new file mode 100644 index 0000000000..d2f38cf82c --- /dev/null +++ b/apps/staking/router.d.ts @@ -0,0 +1,11 @@ +import "react-aria-components"; + +declare module "react-aria-components" { + import { useRouter } from "next/navigation"; + + export type RouterConfig = { + routerOptions: NonNullable< + Parameters["push"]>[1] + >; + }; +} diff --git a/apps/staking/src/components/AccountSummary/index.tsx b/apps/staking/src/components/AccountSummary/index.tsx index 6e3f7855d8..8a3a4bfc4c 100644 --- a/apps/staking/src/components/AccountSummary/index.tsx +++ b/apps/staking/src/components/AccountSummary/index.tsx @@ -1,11 +1,15 @@ import Image from "next/image"; -import type { ComponentProps, ReactNode } from "react"; +import { type ComponentProps, type ReactNode, useCallback } from "react"; +import { + DialogTrigger, + Button as ReactAriaButton, +} from "react-aria-components"; import background from "./background.png"; import { deposit, withdraw, claim } from "../../api"; import { StateType, useTransfer } from "../../hooks/use-transfer"; import { Button } from "../Button"; -import { Modal, ModalButton, ModalPanel } from "../Modal"; +import { ModalDialog } from "../ModalDialog"; import { Tokens } from "../Tokens"; import { TransferButton } from "../TransferButton"; @@ -71,14 +75,11 @@ export const AccountSummary = ({ {locked}
locked included
- - + + Show Unlock Schedule - - + @@ -104,8 +105,8 @@ export const AccountSummary = ({ - - + + )}
@@ -124,13 +125,13 @@ export const AccountSummary = ({ description="The amount of unlocked tokens that are not staked in either program" action={ } /> @@ -138,7 +139,7 @@ export const AccountSummary = ({ name="Available Rewards" amount={availableRewards} description="Rewards you have earned from OIS" - action={} + action={} {...(expiringRewards !== undefined && expiringRewards.amount > 0n && { warning: ( @@ -194,13 +195,19 @@ const ClaimButton = ( ) => { const { state, execute } = useTransfer(claim); + const doClaim = useCallback(() => { + execute().catch(() => { + /* TODO figure out a better UI treatment for when claim fails */ + }); + }, [execute]); + return (
); }; const DashboardTab = Styled( Tab, - "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", + "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", ); const DashboardTabPanel = Styled( @@ -187,3 +188,9 @@ const useIntegrityStakingSum = ( // eslint-disable-next-line unicorn/no-array-reduce const bigIntMin = (...args: bigint[]) => args.reduce((m, e) => (e < m ? e : m)); + +enum TabIds { + Overview, + Governance, + IntegrityStaking, +} diff --git a/apps/staking/src/components/Error/index.tsx b/apps/staking/src/components/Error/index.tsx index 7644dae7b2..5baed81814 100644 --- a/apps/staking/src/components/Error/index.tsx +++ b/apps/staking/src/components/Error/index.tsx @@ -27,7 +27,7 @@ export const Error = ({ error, reset }: Props) => { {error.digest ?? error.message} - {reset && } + {reset && } ); }; diff --git a/apps/staking/src/components/Footer/index.tsx b/apps/staking/src/components/Footer/index.tsx index 801ac56730..0c173f2c29 100644 --- a/apps/staking/src/components/Footer/index.tsx +++ b/apps/staking/src/components/Footer/index.tsx @@ -7,6 +7,7 @@ import LinkedIn from "./linkedin.svg"; import Telegram from "./telegram.svg"; import X from "./x.svg"; import Youtube from "./youtube.svg"; +import { Link } from "../Link"; import { MaxWidth } from "../MaxWidth"; const SOCIAL_LINKS = [ @@ -46,7 +47,7 @@ export const Footer = ({
© 2024 Pyth Data Association
{SOCIAL_LINKS.map(({ name, icon: Icon, href }) => ( - {name} - + ))}
diff --git a/apps/staking/src/components/Home/index.tsx b/apps/staking/src/components/Home/index.tsx index 3c9c456a08..33ef3fa80c 100644 --- a/apps/staking/src/components/Home/index.tsx +++ b/apps/staking/src/components/Home/index.tsx @@ -72,7 +72,7 @@ const NoWalletHome = () => { publishers.

- +
); diff --git a/apps/staking/src/components/Link/index.tsx b/apps/staking/src/components/Link/index.tsx new file mode 100644 index 0000000000..99ba164678 --- /dev/null +++ b/apps/staking/src/components/Link/index.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { Link } from "react-aria-components"; diff --git a/apps/staking/src/components/Modal/index.tsx b/apps/staking/src/components/Modal/index.tsx deleted file mode 100644 index 88af3fe513..0000000000 --- a/apps/staking/src/components/Modal/index.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { - Dialog, - DialogBackdrop, - DialogTitle, - Description, - DialogPanel, - CloseButton, - Transition, -} from "@headlessui/react"; -import { XMarkIcon } from "@heroicons/react/24/outline"; -import { - type ReactNode, - type ComponentProps, - type ElementType, - type Dispatch, - type SetStateAction, - useState, - useCallback, - useContext, - createContext, -} from "react"; - -import { Button } from "../Button"; - -const ModalContext = createContext< - [boolean, Dispatch>] | undefined ->(undefined); - -export const Modal = ( - props: Omit, "value">, -) => { - const state = useState(false); - return ; -}; - -const useModalContext = () => { - const ctx = useContext(ModalContext); - if (ctx === undefined) { - throw new ContextNotInitializedError(); - } - return ctx; -}; - -class ContextNotInitializedError extends Error { - constructor() { - super("You cannot use this component outside of a parent!"); - } -} - -type ModalButtonProps = Omit, "as"> & { - as?: T; -}; - -export const ModalButton = ({ - as, - ...props -}: ModalButtonProps) => { - const Component = as ?? Button; - const [, setState] = useModalContext(); - const toggle = useCallback(() => { - setState((cur) => !cur); - }, [setState]); - return ; -}; - -export const ModalPanel = ( - props: Omit, -) => { - const [state, setState] = useModalContext(); - const onClose = useCallback(() => { - setState(false); - }, [setState]); - - return ; -}; - -type RawModalProps = { - isOpen: boolean; - onClose: () => void; - closeDisabled?: boolean | undefined; - afterLeave?: (() => void) | undefined; - title: ReactNode | ReactNode[]; - description?: string; - children?: - | ((onClose: () => void) => ReactNode | ReactNode[]) - | ReactNode - | ReactNode[] - | undefined; -}; - -export const RawModal = ({ - isOpen, - onClose, - closeDisabled, - afterLeave, - children, - title, - description, -}: RawModalProps) => { - const handleClose = useCallback(() => { - if (!closeDisabled) { - onClose(); - } - }, [closeDisabled, onClose]); - - return ( - - - -
- - - {title} - - - - - {description && ( - - {description} - - )} - {typeof children === "function" ? children(handleClose) : children} - -
-
-
- ); -}; diff --git a/apps/staking/src/components/ModalDialog/index.tsx b/apps/staking/src/components/ModalDialog/index.tsx new file mode 100644 index 0000000000..bfa6250337 --- /dev/null +++ b/apps/staking/src/components/ModalDialog/index.tsx @@ -0,0 +1,63 @@ +import { XMarkIcon } from "@heroicons/react/24/outline"; +import type { ComponentProps, ReactNode } from "react"; +import { Dialog, Heading, Modal, ModalOverlay } from "react-aria-components"; + +import { Button } from "../Button"; + +// This type currently isn't exported by react-aria-components, so we reconstruct it here... +type DialogRenderProps = Parameters< + Exclude["children"], ReactNode> +>[0]; + +type ModalDialogProps = Omit< + ComponentProps, + "children" +> & { + closeDisabled?: boolean | undefined; + title: ReactNode | ReactNode[]; + description?: string; + children?: + | ((options: DialogRenderProps) => ReactNode | ReactNode[]) + | ReactNode + | ReactNode[] + | undefined; +}; + +export const ModalDialog = ({ + closeDisabled, + children, + title, + description, + ...props +}: ModalDialogProps) => ( + + + + {(options) => ( + <> + + + {title} + + {description && ( +

{description}

+ )} + {typeof children === "function" ? children(options) : children} + + )} +
+
+
+); diff --git a/apps/staking/src/components/NotFound/index.tsx b/apps/staking/src/components/NotFound/index.tsx index fca29b05ff..c7aa5012e6 100644 --- a/apps/staking/src/components/NotFound/index.tsx +++ b/apps/staking/src/components/NotFound/index.tsx @@ -1,17 +1,13 @@ -import Link from "next/link"; - -import { Button } from "../Button"; +import { LinkButton } from "../Button"; export const NotFound = () => (
-

+

Not Found

-

- {"The page you're looking for isn't here"} -

- +
); diff --git a/apps/staking/src/components/OracleIntegrityStaking/index.tsx b/apps/staking/src/components/OracleIntegrityStaking/index.tsx index df43fcf3f5..e4282045d1 100644 --- a/apps/staking/src/components/OracleIntegrityStaking/index.tsx +++ b/apps/staking/src/components/OracleIntegrityStaking/index.tsx @@ -19,6 +19,8 @@ import { SearchField, Input, Button as BaseButton, + Meter, + Label, } from "react-aria-components"; import { @@ -88,7 +90,7 @@ export const OracleIntegrityStaking = ({

- You ({self.name}) + You ({self.name ?? self.publicKey.toBase58()})

@@ -323,10 +325,10 @@ const PublisherList = ({ ) : (
0n && { actions: ( 0n && { actions: ( (
{name}
-
+
{children} diff --git a/apps/staking/src/components/Root/index.tsx b/apps/staking/src/components/Root/index.tsx index fa327abe95..a9badcc16f 100644 --- a/apps/staking/src/components/Root/index.tsx +++ b/apps/staking/src/components/Root/index.tsx @@ -19,6 +19,7 @@ import { Footer } from "../Footer"; import { Header } from "../Header"; import { MaxWidth } from "../MaxWidth"; import { ReportAccessibility } from "../ReportAccessibility"; +import { RouterProvider } from "../RouterProvider"; import { WalletProvider } from "../WalletProvider"; const redHatText = Red_Hat_Text({ @@ -36,32 +37,36 @@ type Props = { }; export const Root = ({ children }: Props) => ( - - - - - -
- {children} -