Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(staking): add guides & faqs & a ton of mobile fixes #1920

Merged
merged 1 commit into from
Sep 16, 2024
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
1 change: 1 addition & 0 deletions apps/staking/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@solana/web3.js": "^1.95.2",
"clsx": "^2.1.1",
"dnum": "^2.13.1",
"framer-motion": "^11.3.8",
"next": "^14.2.5",
"pino": "^9.3.2",
"proxycheck-ts": "^0.0.11",
Expand Down
150 changes: 132 additions & 18 deletions apps/staking/src/components/AccountSummary/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InformationCircleIcon } from "@heroicons/react/24/outline";
import Image from "next/image";
import { type ComponentProps, type ReactNode, useCallback } from "react";
import {
Expand Down Expand Up @@ -50,7 +51,7 @@ export const AccountSummary = ({
alt=""
className="absolute -right-40 hidden h-full object-cover object-right [mask-image:linear-gradient(to_right,_transparent,_black_50%)] md:block"
/>
<div className="relative flex flex-col items-start justify-between gap-8 sm:px-6 sm:py-10 md:gap-16 md:px-12 md:py-20 xl:flex-row xl:items-center">
<div className="relative flex flex-row items-center justify-between gap-8 sm:px-6 sm:py-10 md:gap-16 lg:px-12 lg:py-20">
<div>
<div className="mb-2 inline-block border border-neutral-600/50 bg-neutral-900 px-4 py-1 text-xs text-neutral-400 sm:mb-4">
Total Balance
Expand Down Expand Up @@ -114,24 +115,42 @@ export const AccountSummary = ({
transfer={api.deposit}
enableWithZeroMax
/>
<WithdrawButton
api={api}
max={availableToWithdraw}
className="xl:hidden"
/>
{api.type === ApiStateType.Loaded ? (
<DialogTrigger>
<Button variant="secondary" className="xl:hidden">
Claim
</Button>
{availableRewards > 0n ? (
<ClaimDialog
expiringRewards={expiringRewards}
availableRewards={availableRewards}
api={api}
/>
) : (
<ModalDialog title="No Rewards" closeButtonText="Ok">
<p>You have no rewards available to be claimed</p>
</ModalDialog>
)}
</DialogTrigger>
) : (
<Button variant="secondary" isDisabled={true} className="lg:hidden">
Claim
</Button>
)}
</div>
</div>
<div className="flex w-full flex-row items-stretch gap-4 xl:w-auto">
<div className="hidden w-auto items-stretch gap-4 xl:flex">
<BalanceCategory
name="Unlocked & Unstaked"
amount={availableToWithdraw}
description="The amount of unlocked tokens that are not staked in either program"
action={
<TransferButton
size="small"
variant="secondary"
actionDescription="Move funds from your account back to your wallet"
actionName="Withdraw"
max={availableToWithdraw}
{...(api.type === ApiStateType.Loaded && {
transfer: api.withdraw,
})}
/>
<WithdrawButton api={api} max={availableToWithdraw} size="small" />
}
/>
<BalanceCategory
Expand All @@ -140,7 +159,12 @@ export const AccountSummary = ({
description="Rewards you have earned from OIS"
action={
api.type === ApiStateType.Loaded ? (
<ClaimButton isDisabled={availableRewards === 0n} api={api} />
<ClaimButton
size="small"
variant="secondary"
isDisabled={availableRewards === 0n}
api={api}
/>
) : (
<Button size="small" variant="secondary" isDisabled={true}>
Claim
Expand All @@ -163,6 +187,33 @@ export const AccountSummary = ({
</section>
);

type WithdrawButtonProps = Omit<
ComponentProps<typeof TransferButton>,
"variant" | "actionDescription" | "actionName" | "transfer"
> & {
api: States[ApiStateType.Loaded] | States[ApiStateType.LoadedNoStakeAccount];
};

const WithdrawButton = ({ api, ...props }: WithdrawButtonProps) => (
<TransferButton
variant="secondary"
actionDescription="Move funds from your account back to your wallet"
actionName="Withdraw"
{...(api.type === ApiStateType.Loaded && {
transfer: api.withdraw,
})}
{...props}
>
<div className="mb-4 flex max-w-96 flex-row gap-2 border border-neutral-600/50 bg-pythpurple-400/20 p-4">
<InformationCircleIcon className="size-8 flex-none" />
<div className="text-sm">
You can only withdraw tokens that are unlocked and not staked in either
OIS or Pyth Governance
</div>
</div>
</TransferButton>
);

type BalanceCategoryProps = {
name: string;
amount: bigint;
Expand All @@ -178,23 +229,88 @@ const BalanceCategory = ({
action,
warning,
}: BalanceCategoryProps) => (
<div className="flex w-full flex-col justify-between border border-neutral-600/50 bg-pythpurple-800/60 p-6 backdrop-blur xl:w-96">
<div className="flex w-full flex-col justify-between border border-neutral-600/50 bg-pythpurple-800/60 p-4 backdrop-blur sm:p-6 xl:w-80 2xl:w-96">
<div>
<div className="mb-4 inline-block border border-neutral-600/50 bg-neutral-900 px-4 py-1 text-xs text-neutral-400">
{name}
</div>
<div>
<Tokens className="text-xl font-light">{amount}</Tokens>
</div>
<p className="mt-4 max-w-xs text-sm text-neutral-500">{description}</p>
<p className="mt-4 text-sm text-neutral-500">{description}</p>
</div>
<div className="mt-4 flex flex-row items-center gap-4">
{action}
{warning && <p className="max-w-xs text-xs text-red-600">{warning}</p>}
{warning && <p className="text-xs text-red-600">{warning}</p>}
</div>
</div>
);

type ClaimDialogProps = {
availableRewards: bigint;
expiringRewards: Date | undefined;
api: States[ApiStateType.Loaded];
};

const ClaimDialog = ({
api,
expiringRewards,
availableRewards,
}: ClaimDialogProps) => {
const { state, execute } = useAsync(api.claim);

const doClaim = useCallback(() => {
execute().catch(() => {
/* TODO figure out a better UI treatment for when claim fails */
});
}, [execute]);

return (
<ModalDialog title="Claim">
{({ close }) => (
<>
<p className="mb-4">
Claim your <Tokens>{availableRewards}</Tokens> rewards
</p>
{expiringRewards && (
<div className="mb-4 flex max-w-96 flex-row gap-2 border border-neutral-600/50 bg-pythpurple-400/20 p-4">
<InformationCircleIcon className="size-8 flex-none" />
<div className="text-sm">
Rewards expire one year from the epoch in which they were
earned. You have rewards expiring on{" "}
{expiringRewards.toLocaleDateString()}.
</div>
</div>
)}
{state.type === StateType.Error && (
<p className="mt-8 text-red-600">
Uh oh, an error occurred! Please try again
</p>
)}
<div className="mt-14 flex flex-col gap-8 sm:flex-row sm:justify-between">
<Button
variant="secondary"
className="w-full sm:w-auto"
size="noshrink"
onPress={close}
>
Cancel
</Button>
<Button
className="w-full sm:w-auto"
size="noshrink"
isLoading={state.type === StateType.Running}
onPress={doClaim}
>
Claim
</Button>
</div>
</>
)}
</ModalDialog>
);
};

type ClaimButtonProps = Omit<
ComponentProps<typeof Button>,
"onClick" | "disabled" | "loading"
Expand All @@ -213,8 +329,6 @@ const ClaimButton = ({ api, ...props }: ClaimButtonProps) => {

return (
<Button
size="small"
variant="secondary"
onPress={doClaim}
isDisabled={state.type !== StateType.Base}
isLoading={state.type === StateType.Running}
Expand Down
Loading
Loading