Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions apps/staking/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand All @@ -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",
Expand Down
11 changes: 11 additions & 0 deletions apps/staking/router.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import "react-aria-components";

declare module "react-aria-components" {
import { useRouter } from "next/navigation";

export type RouterConfig = {
routerOptions: NonNullable<
Parameters<ReturnType<typeof useRouter>["push"]>[1]
>;
};
}
47 changes: 27 additions & 20 deletions apps/staking/src/components/AccountSummary/index.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -71,14 +75,11 @@ export const AccountSummary = ({
<Tokens>{locked}</Tokens>
<div>locked included</div>
</div>
<Modal>
<ModalButton
as="button"
className="mt-1 text-sm text-pythpurple-400 hover:underline"
>
<DialogTrigger>
<ReactAriaButton className="mt-1 text-sm text-pythpurple-400 hover:underline focus:outline-none focus-visible:underline focus-visible:outline-none">
Show Unlock Schedule
</ModalButton>
<ModalPanel
</ReactAriaButton>
<ModalDialog
title="Unlock Schedule"
description="Your tokens will become available for withdrawal and for participation in Integrity Staking according to this schedule"
>
Expand All @@ -104,8 +105,8 @@ export const AccountSummary = ({
</tbody>
</table>
</div>
</ModalPanel>
</Modal>
</ModalDialog>
</DialogTrigger>
</>
)}
<div className="mt-3 flex flex-row items-center gap-4 sm:mt-8">
Expand All @@ -124,21 +125,21 @@ export const AccountSummary = ({
description="The amount of unlocked tokens that are not staked in either program"
action={
<TransferButton
small
secondary
size="small"
variant="secondary"
actionDescription="Move funds from your account back to your wallet"
actionName="Withdraw"
max={availableToWithdraw}
transfer={withdraw}
disabled={availableToWithdraw === 0n}
isDisabled={availableToWithdraw === 0n}
/>
}
/>
<BalanceCategory
name="Available Rewards"
amount={availableRewards}
description="Rewards you have earned from OIS"
action={<ClaimButton disabled={availableRewards === 0n} />}
action={<ClaimButton isDisabled={availableRewards === 0n} />}
{...(expiringRewards !== undefined &&
expiringRewards.amount > 0n && {
warning: (
Expand Down Expand Up @@ -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 (
<Button
small
secondary
onClick={execute}
disabled={state.type !== StateType.Base}
loading={state.type === StateType.Submitting}
size="small"
variant="secondary"
onPress={doClaim}
isDisabled={state.type !== StateType.Base}
isLoading={state.type === StateType.Submitting}
{...props}
>
Claim
Expand Down
96 changes: 73 additions & 23 deletions apps/staking/src/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,83 @@
import type { ButtonHTMLAttributes } from "react";
"use client";

import { Styled } from "../Styled";
import clsx from "clsx";
import type { ComponentProps } from "react";
import { Button as ReactAriaButton } from "react-aria-components";

type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
loading?: boolean | undefined;
secondary?: boolean | undefined;
small?: boolean | undefined;
nopad?: boolean | undefined;
import { Link } from "../Link";

type VariantProps = {
variant?: "secondary" | undefined;
size?: "small" | "nopad" | undefined;
};

const ButtonBase = ({
loading,
secondary,
small,
nopad,
disabled,
type ButtonProps = ComponentProps<typeof ReactAriaButton> &
VariantProps & {
isLoading?: boolean | undefined;
};

export const Button = ({
isLoading,
variant,
size,
isDisabled,
className,
...props
}: Props) => (
<button
disabled={loading === true || disabled === true}
{...(loading && { "data-loading": "" })}
{...(secondary && { "data-secondary": "" })}
{...(small && { "data-small": "" })}
{...(nopad && { "data-nopad": "" })}
}: ButtonProps) => (
<ReactAriaButton
isDisabled={isLoading === true || isDisabled === true}
className={clsx(
"disabled:border-neutral-50/10 disabled:bg-neutral-50/10 disabled:text-white/60",
isLoading ? "cursor-wait" : "disabled:cursor-not-allowed",
baseClassName({ variant, size }),
className,
)}
{...props}
/>
);

export const Button = Styled(
ButtonBase,
"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",
type LinkButtonProps = ComponentProps<typeof Link> & VariantProps;

export const LinkButton = ({
variant,
size,
className,
...props
}: LinkButtonProps) => (
<Link
className={clsx(baseClassName({ variant, size }), className)}
{...props}
/>
);

const baseClassName = (props: VariantProps) =>
clsx(
"border border-pythpurple-600 hover:bg-pythpurple-600/60 focus:outline-none focus-visible:ring-1 focus-visible:ring-pythpurple-400",
variantClassName(props.variant),
sizeClassName(props.size),
);

const variantClassName = (variant: VariantProps["variant"]) => {
switch (variant) {
case "secondary": {
return "bg-pythpurple-600/20";
}
case undefined: {
return "bg-pythpurple-600/50";
}
}
};

const sizeClassName = (size: VariantProps["size"]) => {
switch (size) {
case "small": {
return "text-sm px-2 sm:px-3 py-1";
}
case "nopad": {
return "px-0 py-0";
}
case undefined: {
return "px-2 sm:px-4 md:px-8 py-2";
}
}
};
81 changes: 44 additions & 37 deletions apps/staking/src/components/Dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
import { type ComponentProps, useMemo } from "react";
import { Tabs, TabList, Tab, TabPanel } from "react-aria-components";

import { AccountSummary } from "../AccountSummary";
import { Governance } from "../Governance";
Expand Down Expand Up @@ -117,54 +117,55 @@ export const Dashboard = ({
availableRewards={availableRewards}
expiringRewards={expiringRewards}
/>
<TabGroup as="section">
<TabList className="flex w-full flex-row text-sm font-medium sm:text-base">
<DashboardTab>Overview</DashboardTab>
<DashboardTab>Governance</DashboardTab>
<DashboardTab>
<Tabs>
<TabList
className="mb-8 flex w-full flex-row text-sm font-medium sm:text-base"
aria-label="Programs"
>
<DashboardTab id={TabIds.Overview}>Overview</DashboardTab>
<DashboardTab id={TabIds.Governance}>Governance</DashboardTab>
<DashboardTab id={TabIds.IntegrityStaking}>
<span className="sm:hidden">Integrity Staking</span>
<span className="hidden sm:inline">
Oracle Integrity Staking (OIS)
</span>
</DashboardTab>
</TabList>
<TabPanels className="mt-8">
<DashboardTabPanel>
<section className="py-20">
<p className="text-center">
This is an overview of the staking programs
</p>
</section>
</DashboardTabPanel>
<DashboardTabPanel>
<Governance
availableToStake={availableToStakeGovernance}
warmup={governance.warmup}
staked={governance.staked}
cooldown={governance.cooldown}
cooldown2={governance.cooldown2}
/>
</DashboardTabPanel>
<DashboardTabPanel>
<OracleIntegrityStaking
availableToStake={availableToStakeIntegrity}
locked={locked}
warmup={integrityStakingWarmup}
staked={integrityStakingStaked}
cooldown={integrityStakingCooldown}
cooldown2={integrityStakingCooldown2}
publishers={integrityStakingPublishers}
/>
</DashboardTabPanel>
</TabPanels>
</TabGroup>
<DashboardTabPanel id={TabIds.Overview}>
<section className="py-20">
<p className="text-center">
This is an overview of the staking programs
</p>
</section>
</DashboardTabPanel>
<DashboardTabPanel id={TabIds.Governance}>
<Governance
availableToStake={availableToStakeGovernance}
warmup={governance.warmup}
staked={governance.staked}
cooldown={governance.cooldown}
cooldown2={governance.cooldown2}
/>
</DashboardTabPanel>
<DashboardTabPanel id={TabIds.IntegrityStaking}>
<OracleIntegrityStaking
availableToStake={availableToStakeIntegrity}
locked={locked}
warmup={integrityStakingWarmup}
staked={integrityStakingStaked}
cooldown={integrityStakingCooldown}
cooldown2={integrityStakingCooldown2}
publishers={integrityStakingPublishers}
/>
</DashboardTabPanel>
</Tabs>
</div>
);
};

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(
Expand All @@ -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,
}
2 changes: 1 addition & 1 deletion apps/staking/src/components/Error/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const Error = ({ error, reset }: Props) => {
<strong className="mb-8 border border-pythpurple-400/20 bg-pythpurple-600/50 px-1 py-0.5 text-sm opacity-50">
{error.digest ?? error.message}
</strong>
{reset && <Button onClick={reset}>Reset</Button>}
{reset && <Button onPress={reset}>Reset</Button>}
</main>
);
};
5 changes: 3 additions & 2 deletions apps/staking/src/components/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -46,7 +47,7 @@ export const Footer = ({
<div>© 2024 Pyth Data Association</div>
<div className="relative -right-3 flex h-full items-center">
{SOCIAL_LINKS.map(({ name, icon: Icon, href }) => (
<a
<Link
target="_blank"
href={href}
key={name}
Expand All @@ -55,7 +56,7 @@ export const Footer = ({
>
<Icon className="size-4" />
<span className="sr-only">{name}</span>
</a>
</Link>
))}
</div>
</MaxWidth>
Expand Down
2 changes: 1 addition & 1 deletion apps/staking/src/components/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const NoWalletHome = () => {
publishers.
</p>
<div className="grid w-full place-content-center">
<Button onClick={showModal}>Connect your wallet to participate</Button>
<Button onPress={showModal}>Connect your wallet to participate</Button>
</div>
</main>
);
Expand Down
3 changes: 3 additions & 0 deletions apps/staking/src/components/Link/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use client";

export { Link } from "react-aria-components";
Loading
Loading