Skip to content

Commit e32ec93

Browse files
authored
Merge pull request #1940 from cprussin/add-legal-disclosure
feat(staking): add legal disclaimer on initial deposit
2 parents 816a33a + 053e13f commit e32ec93

File tree

4 files changed

+234
-33
lines changed

4 files changed

+234
-33
lines changed

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

Lines changed: 158 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
import { InformationCircleIcon } from "@heroicons/react/24/outline";
2+
import { useLocalStorageValue } from "@react-hookz/web";
23
import Image from "next/image";
3-
import { type ComponentProps, type ReactNode, useCallback } from "react";
4+
import {
5+
type ComponentProps,
6+
type FormEvent,
7+
type ReactNode,
8+
useCallback,
9+
useState,
10+
} from "react";
411
import {
512
DialogTrigger,
13+
Form,
614
Button as ReactAriaButton,
715
} from "react-aria-components";
816

917
import background from "./background.png";
1018
import { type States, StateType as ApiStateType } from "../../hooks/use-api";
1119
import { StateType, useAsync } from "../../hooks/use-async";
12-
import { Button } from "../Button";
20+
import { Button, LinkButton } from "../Button";
21+
import { Checkbox } from "../Checkbox";
22+
import { Link } from "../Link";
1323
import { ModalDialog } from "../ModalDialog";
1424
import { Tokens } from "../Tokens";
15-
import { TransferButton } from "../TransferButton";
25+
import { TransferButton, TransferDialog } from "../TransferButton";
1626

1727
type Props = {
1828
api: States[ApiStateType.Loaded] | States[ApiStateType.LoadedNoStakeAccount];
@@ -108,13 +118,7 @@ export const AccountSummary = ({
108118
</>
109119
)}
110120
<div className="mt-3 flex flex-row items-center gap-4 sm:mt-8">
111-
<TransferButton
112-
actionDescription="Add funds to your balance"
113-
actionName="Add Tokens"
114-
max={walletAmount}
115-
transfer={api.deposit}
116-
enableWithZeroMax
117-
/>
121+
<AddTokensButton walletAmount={walletAmount} api={api} />
118122
{availableToWithdraw === 0n ? (
119123
<DialogTrigger>
120124
<Button variant="secondary" className="xl:hidden">
@@ -354,3 +358,147 @@ const ClaimButton = ({ api, ...props }: ClaimButtonProps) => {
354358
</Button>
355359
);
356360
};
361+
362+
type AddTokensButtonProps = {
363+
walletAmount: bigint;
364+
api: States[ApiStateType.Loaded] | States[ApiStateType.LoadedNoStakeAccount];
365+
};
366+
367+
const AddTokensButton = ({ walletAmount, api }: AddTokensButtonProps) => {
368+
const hasAcknowledgedLegal = useLocalStorageValue("has-acknowledged-legal");
369+
const [transferOpen, setTransferOpen] = useState(false);
370+
const openTransfer = useCallback(() => {
371+
setTransferOpen(true);
372+
}, [setTransferOpen]);
373+
const acknowledgeLegal = useCallback(() => {
374+
hasAcknowledgedLegal.set("true");
375+
openTransfer();
376+
}, [hasAcknowledgedLegal, openTransfer]);
377+
378+
return (
379+
<>
380+
{hasAcknowledgedLegal.value ? (
381+
<Button onPress={openTransfer}>Add Tokens</Button>
382+
) : (
383+
<DisclosureButton onAcknowledge={acknowledgeLegal} />
384+
)}
385+
<TransferDialog
386+
title="Add tokens"
387+
description="Add funds to your balance"
388+
max={walletAmount}
389+
transfer={api.deposit}
390+
submitButtonText="Add tokens"
391+
isOpen={transferOpen}
392+
onOpenChange={setTransferOpen}
393+
/>
394+
</>
395+
);
396+
};
397+
398+
type DisclosureButtonProps = {
399+
onAcknowledge: () => void;
400+
};
401+
402+
const DisclosureButton = ({ onAcknowledge }: DisclosureButtonProps) => {
403+
const [understood, setUnderstood] = useState(false);
404+
const [agreed, setAgreed] = useState(false);
405+
const [open, setOpen] = useState(false);
406+
const acknowledge = useCallback(
407+
(e: FormEvent<HTMLFormElement>) => {
408+
e.preventDefault();
409+
setOpen(false);
410+
setTimeout(onAcknowledge, 400);
411+
},
412+
[setOpen, onAcknowledge],
413+
);
414+
415+
return (
416+
<>
417+
<DialogTrigger isOpen={open} onOpenChange={setOpen}>
418+
<Button>Add Tokens</Button>
419+
<ModalDialog title="Disclosure">
420+
<Form onSubmit={acknowledge}>
421+
<p className="max-w-prose text-sm opacity-60">
422+
THE SERVICES WERE NOT DEVELOPED FOR, AND ARE NOT AVAILABLE TO
423+
PERSONS OR ENTITIES WHO RESIDE IN, ARE LOCATED IN, ARE
424+
INCORPORATED IN, OR HAVE A REGISTERED OFFICE OR PRINCIPAL PLACE OF
425+
BUSINESS IN THE UNITED STATES OF AMERICA, THE UNITED KINGDOM OR
426+
CANADA (COLLECTIVELY, “BLOCKED PERSONS”). MOREOVER, THE SERVICES
427+
ARE NOT OFFERED TO PERSONS OR ENTITIES WHO RESIDE IN, ARE CITIZENS
428+
OF, ARE LOCATED IN, ARE INCORPORATED IN, OR HAVE A REGISTERED
429+
OFFICE OR PRINCIPAL PLACE OF BUSINESS IN ANY RESTRICTED
430+
JURISDICTION OR COUNTRY SUBJECT TO ANY SANCTIONS OR RESTRICTIONS
431+
PURSUANT TO ANY APPLICABLE LAW, INCLUDING THE CRIMEA REGION, CUBA,
432+
IRAN, NORTH KOREA, SYRIA, MYANMAR (BURMA, DONETSK, LUHANSK, OR ANY
433+
OTHER COUNTRY TO WHICH THE UNITED STATES, THE UNITED KINGDOM, THE
434+
EUROPEAN UNION OR ANY OTHER JURISDICTIONS EMBARGOES GOODS OR
435+
IMPOSES SIMILAR SANCTIONS, INCLUDING THOSE LISTED ON OUR SERVICES
436+
(COLLECTIVELY, THE “RESTRICTED JURISDICTIONS” AND EACH A
437+
“RESTRICTED JURISDICTION”) OR ANY PERSON OWNED, CONTROLLED,
438+
LOCATED IN OR ORGANIZED UNDER THE LAWS OF ANY JURISDICTION UNDER
439+
EMBARGO OR CONNECTED OR AFFILIATED WITH ANY SUCH PERSON
440+
(COLLECTIVELY, “RESTRICTED PERSONS”). THE WEBSITE WAS NOT
441+
SPECIFICALLY DEVELOPED FOR, AND IS NOT AIMED AT OR BEING ACTIVELY
442+
MARKETED TO, PERSONS OR ENTITIES WHO RESIDE IN, ARE LOCATED IN,
443+
ARE INCORPORATED IN, OR HAVE A REGISTERED OFFICE OR PRINCIPAL
444+
PLACE OF BUSINESS IN THE EUROPEAN UNION. THERE ARE NO EXCEPTIONS.
445+
IF YOU ARE A BLOCKED PERSON OR A RESTRICTED PERSON, THEN DO NOT
446+
USE OR ATTEMPT TO USE THE SERVICES. USE OF ANY TECHNOLOGY OR
447+
MECHANISM, SUCH AS A VIRTUAL PRIVATE NETWORK (“VPN”) TO CIRCUMVENT
448+
THE RESTRICTIONS SET FORTH HEREIN IS PROHIBITED.
449+
</p>
450+
<Checkbox
451+
className="my-4 block max-w-prose"
452+
isSelected={understood}
453+
onChange={setUnderstood}
454+
>
455+
I understand
456+
</Checkbox>
457+
<Checkbox
458+
className="my-4 block max-w-prose"
459+
isSelected={agreed}
460+
onChange={setAgreed}
461+
>
462+
By checking the box and access the Services, you acknowledge and
463+
agree to the terms and conditions of our{" "}
464+
<Link
465+
href="https://www.pyth.network/terms-of-use"
466+
target="_blank"
467+
className="underline"
468+
>
469+
Terms of Use
470+
</Link>{" "}
471+
and{" "}
472+
<Link
473+
href="https://www.pyth.network/privacy-policy"
474+
target="_blank"
475+
className="underline"
476+
>
477+
Privacy Policy
478+
</Link>
479+
.
480+
</Checkbox>
481+
<div className="mt-14 flex flex-col gap-8 sm:flex-row sm:justify-between">
482+
<LinkButton
483+
className="w-full sm:w-auto"
484+
href="https://pyth.network/"
485+
variant="secondary"
486+
size="noshrink"
487+
>
488+
Exit
489+
</LinkButton>
490+
<Button
491+
className="w-full sm:w-auto"
492+
size="noshrink"
493+
type="submit"
494+
isDisabled={!understood || !agreed}
495+
>
496+
Confirm
497+
</Button>
498+
</div>
499+
</Form>
500+
</ModalDialog>
501+
</DialogTrigger>
502+
</>
503+
);
504+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { CheckIcon } from "@heroicons/react/24/outline";
2+
import clsx from "clsx";
3+
import type { ComponentProps, ReactNode } from "react";
4+
import { Checkbox as BaseCheckbox } from "react-aria-components";
5+
6+
type Props = Omit<ComponentProps<typeof BaseCheckbox>, "children"> & {
7+
children: ReactNode;
8+
};
9+
10+
export const Checkbox = ({ className, children, ...props }: Props) => (
11+
<BaseCheckbox
12+
className={clsx("group flex cursor-pointer flex-row gap-2", className)}
13+
{...props}
14+
>
15+
<div className="relative top-1 size-4 flex-none rounded border border-pythpurple-400 transition duration-100 group-data-[selected]:bg-pythpurple-600">
16+
<CheckIcon className="absolute inset-0 stroke-2 opacity-0 transition duration-100 group-data-[selected]:opacity-100" />
17+
</div>
18+
<div>{children}</div>
19+
</BaseCheckbox>
20+
);

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,11 @@ export const OracleIntegrityStaking = ({
7979
}: Props) => {
8080
const self = useMemo(
8181
() =>
82-
api.type === ApiStateType.Loaded &&
83-
publishers.find((publisher) =>
84-
publisher.stakeAccount?.equals(api.account),
85-
),
82+
api.type === ApiStateType.Loaded
83+
? publishers.find((publisher) =>
84+
publisher.stakeAccount?.equals(api.account),
85+
)
86+
: undefined,
8687
[publishers, api],
8788
);
8889

@@ -130,7 +131,7 @@ export const OracleIntegrityStaking = ({
130131
<div
131132
className={clsx(
132133
"relative -mx-4 overflow-hidden border-t border-neutral-600/50 pt-6 sm:-mx-8 lg:mt-10",
133-
{ "mt-6": self === undefined },
134+
{ "mt-6 sm:mt-12": self === undefined },
134135
)}
135136
>
136137
<PublisherList
@@ -622,8 +623,10 @@ const PublisherList = ({
622623

623624
return (
624625
<div className="relative w-full overflow-x-auto">
625-
<div className="sticky left-0 mb-4 flex flex-col gap-4 px-4 sm:px-10 sm:pb-4 sm:pt-6 md:flex-row md:items-center md:justify-between md:gap-12">
626-
<h3 className="flex-none text-2xl font-light">{title}</h3>
626+
<div className="sticky left-0 mb-4 flex flex-col gap-4 px-4 sm:px-10 sm:pb-4 sm:pt-6 md:flex-row md:justify-between md:gap-12 lg:items-center">
627+
<h3 className="flex-none text-2xl font-light md:mt-1 lg:mt-0">
628+
{title}
629+
</h3>
627630

628631
<div className="flex flex-none grow flex-col items-end gap-2 lg:flex-row-reverse lg:items-center lg:justify-start lg:gap-10 xl:gap-16">
629632
<SearchField

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

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ export const TransferButton = ({
4949
isDisabled,
5050
...props
5151
}: Props) => {
52-
const [closeDisabled, setCloseDisabled] = useState(false);
53-
5452
return transfer === undefined ||
5553
isDisabled === true ||
5654
(max === 0n && !enableWithZeroMax) ? (
@@ -60,27 +58,59 @@ export const TransferButton = ({
6058
) : (
6159
<DialogTrigger>
6260
<Button {...props}>{actionName}</Button>
63-
<ModalDialog
61+
<TransferDialog
6462
title={title ?? actionName}
65-
closeDisabled={closeDisabled}
6663
description={actionDescription}
64+
max={max}
65+
transfer={transfer}
66+
submitButtonText={submitButtonText ?? actionName}
6767
>
68-
{({ close }) => (
69-
<DialogContents
70-
max={max}
71-
transfer={transfer}
72-
setCloseDisabled={setCloseDisabled}
73-
submitButtonText={submitButtonText ?? actionName}
74-
close={close}
75-
>
76-
{children}
77-
</DialogContents>
78-
)}
79-
</ModalDialog>
68+
{children}
69+
</TransferDialog>
8070
</DialogTrigger>
8171
);
8272
};
8373

74+
type TransferDialogProps = Omit<
75+
ComponentProps<typeof ModalDialog>,
76+
"children"
77+
> & {
78+
max: bigint;
79+
transfer: (amount: bigint) => Promise<void>;
80+
submitButtonText: ReactNode;
81+
children?:
82+
| ((amount: Amount) => ReactNode | ReactNode[])
83+
| ReactNode
84+
| ReactNode[]
85+
| undefined;
86+
};
87+
88+
export const TransferDialog = ({
89+
max,
90+
transfer,
91+
submitButtonText,
92+
children,
93+
...props
94+
}: TransferDialogProps) => {
95+
const [closeDisabled, setCloseDisabled] = useState(false);
96+
97+
return (
98+
<ModalDialog closeDisabled={closeDisabled} {...props}>
99+
{({ close }) => (
100+
<DialogContents
101+
max={max}
102+
transfer={transfer}
103+
setCloseDisabled={setCloseDisabled}
104+
submitButtonText={submitButtonText}
105+
close={close}
106+
>
107+
{children}
108+
</DialogContents>
109+
)}
110+
</ModalDialog>
111+
);
112+
};
113+
84114
type DialogContentsProps = {
85115
max: bigint;
86116
children: Props["children"];

0 commit comments

Comments
 (0)