From 3d057f7f64e49d1967d035a23d4d22a727d87a1a Mon Sep 17 00:00:00 2001 From: Pearce Date: Fri, 3 Oct 2025 17:19:12 +0200 Subject: [PATCH 01/22] feat: Legal page with tabs for terms, privacy, and disclaimers and subtabs for disclaimers --- .../curve-ui-kit/src/widgets/Legal/Page.tsx | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 packages/curve-ui-kit/src/widgets/Legal/Page.tsx diff --git a/packages/curve-ui-kit/src/widgets/Legal/Page.tsx b/packages/curve-ui-kit/src/widgets/Legal/Page.tsx new file mode 100644 index 000000000..32c130e9a --- /dev/null +++ b/packages/curve-ui-kit/src/widgets/Legal/Page.tsx @@ -0,0 +1,143 @@ +import { MouseEvent, useEffect, useMemo, useState } from 'react' +import type { INetworkName as CurveNetworkId } from '@curvefi/api/lib/interfaces' +import type { INetworkName as LlamaNetworkId } from '@curvefi/llamalend-api/lib/interfaces' +import Stack from '@mui/material/Stack' +import { usePathname, useSearchParams, useParams } from '@ui-kit/hooks/router' +import { t } from '@ui-kit/lib/i18n' +import type { AppName } from '@ui-kit/shared/routes' +import { TabsSwitcher, type TabOption } from '@ui-kit/shared/ui/TabsSwitcher' +import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' +import { pushSearchParams } from '@ui-kit/utils/urls' +import { LastUpdated } from '../Disclaimer/LastUpdated' +import { TabPanel } from '../Disclaimer/TabPanel' +import { Header } from '../Disclaimer/Section' +import { Footer } from '../Disclaimer/Footer' +import { Dex } from '../Disclaimer/Tabs/Dex' +import { LlamaLend } from '../Disclaimer/Tabs/LlamaLend' +import { CrvUsd } from '../Disclaimer/Tabs/CrvUsd' +import { SCrvUsd } from '../Disclaimer/Tabs/SCrvUsd' +import { Grid } from '@mui/material' + +const { MaxWidth, Spacing } = SizesAndSpaces + +type Tab = 'terms' | 'privacy' | 'disclaimers' +type DisclaimerTab = 'dex' | 'lend' | 'crvusd' | 'scrvusd' + +const TABS: TabOption[] = [ + { value: 'terms', label: t`Terms & Conditions` }, + { value: 'privacy', label: t`Privacy Policy` }, + { value: 'disclaimers', label: t`Risk Disclaimers` }, +] + +const DISCLAIMER_TABS: TabOption[] = [ + { value: 'dex', label: t`Dex` }, + { value: 'lend', label: t`LlamaLend` }, + { value: 'crvusd', label: t`crvUSD` }, + { value: 'scrvusd', label: t`Savings crvUSD` }, +] + +const defaultDisclaimerTab: Record = { + dao: 'dex', + crvusd: 'crvusd', + lend: 'lend', + llamalend: 'lend', + dex: 'dex', +} + +export type LegalPageProps = { + currentApp: AppName +} + +function useAfterHydration(result: string) { + const [value, setValue] = useState() + useEffect(() => setValue(result), [result]) // only after hydration, otherwise test may click too fast + return value +} + +export const LegalPage = ({ currentApp }: LegalPageProps) => { + const { network } = useParams() as { network: CurveNetworkId | LlamaNetworkId } + const pathname = usePathname() + const searchParams = useSearchParams() + const tab = searchParams?.get('tab') ?? 'terms' + const disclaimerTab = searchParams?.get('subtab') ?? defaultDisclaimerTab[currentApp] + + const tabs = useMemo( + () => [ + ...TABS.map(({ value, ...props }) => ({ + ...props, + value, + href: { query: { tab: value }, pathname }, + onClick: (e: MouseEvent) => pushSearchParams(e, { tab: value }), + })), + ], + [pathname], + ) + + const disclaimerTabs = useMemo( + () => [ + ...DISCLAIMER_TABS.map(({ value, ...props }) => ({ + ...props, + value, + href: { query: { tab: 'disclaimers', subtab: value }, pathname }, + onClick: (e: MouseEvent) => pushSearchParams(e, { subtab: value }), + })), + ], + [pathname], + ) + + return ( + + + + + + + + + + + {tab === 'disclaimers' ? ( + <> + t.design.Layer[1].Fill }} + > + + + + {disclaimerTab === 'dex' && } + {disclaimerTab === 'lend' && } + {disclaimerTab === 'crvusd' && } + {disclaimerTab === 'scrvusd' && } +