diff --git a/apps/tangle-dapp/app/blueprints/loading.tsx b/apps/tangle-dapp/app/blueprints/loading.tsx new file mode 100644 index 0000000000..0621a59da8 --- /dev/null +++ b/apps/tangle-dapp/app/blueprints/loading.tsx @@ -0,0 +1,24 @@ +import TopBanner from '@webb-tools/tangle-shared-ui/components/blueprints/TopBanner'; +import SkeletonLoader from '@webb-tools/webb-ui-components/components/SkeletonLoader'; +import type { FC } from 'react'; +import { twMerge } from 'tailwind-merge'; + +const LoadingPage: FC = () => { + return ( +
+ + +
+ {Array.from({ length: 6 }).map((_, idx) => ( + + ))} +
+
+ ); +}; + +export default LoadingPage; diff --git a/apps/tangle-dapp/app/bridge/loading.tsx b/apps/tangle-dapp/app/bridge/loading.tsx new file mode 100644 index 0000000000..df44dd05db --- /dev/null +++ b/apps/tangle-dapp/app/bridge/loading.tsx @@ -0,0 +1,64 @@ +import { ArrowRight } from '@webb-tools/icons/ArrowRight'; +import Button from '@webb-tools/webb-ui-components/components/buttons/Button'; +import { Label } from '@webb-tools/webb-ui-components/components/Label'; +import SkeletonLoader from '@webb-tools/webb-ui-components/components/SkeletonLoader'; +import { type FC } from 'react'; +import { twMerge } from 'tailwind-merge'; + +const Loading: FC = () => { + // TODO: Using the container style when PR https://github.com/tangle-network/dapp/pull/2664 is merged + return ( +
+
+
+ {/* Source Chain Selector */} +
+ + + +
+ + + + {/* Destination Chain Selector */} +
+ + + +
+
+ +
+ + + +
+ + +
+ +
+ ); +}; + +export default Loading; diff --git a/apps/tangle-dapp/app/liquid-staking/loading.tsx b/apps/tangle-dapp/app/liquid-staking/loading.tsx new file mode 100644 index 0000000000..8256c9a9d9 --- /dev/null +++ b/apps/tangle-dapp/app/liquid-staking/loading.tsx @@ -0,0 +1,14 @@ +import SkeletonLoader from '@webb-tools/webb-ui-components/components/SkeletonLoader'; +import type { FC } from 'react'; + +const LoadingPage: FC = () => { + return ( +
+ + + +
+ ); +}; + +export default LoadingPage; diff --git a/apps/tangle-dapp/app/restake/AnimatedPageWrapper.tsx b/apps/tangle-dapp/app/restake/AnimatedPageWrapper.tsx new file mode 100644 index 0000000000..eaa2ad1ba8 --- /dev/null +++ b/apps/tangle-dapp/app/restake/AnimatedPageWrapper.tsx @@ -0,0 +1,29 @@ +'use client'; + +import { HTMLMotionProps, motion } from 'framer-motion'; +import { forwardRef, PropsWithChildren } from 'react'; + +export type AnimatedPageWrapperProps = HTMLMotionProps<'div'>; + +const AnimatedPageWrapper = forwardRef< + HTMLDivElement, + PropsWithChildren +>(({ children, ...props }, ref) => { + return ( + + {children} + + ); +}); + +AnimatedPageWrapper.displayName = 'AnimatedPageWrapper'; + +export default AnimatedPageWrapper; diff --git a/apps/tangle-dapp/app/restake/AnimatedTable.tsx b/apps/tangle-dapp/app/restake/AnimatedTable.tsx new file mode 100644 index 0000000000..08eeeace7a --- /dev/null +++ b/apps/tangle-dapp/app/restake/AnimatedTable.tsx @@ -0,0 +1,34 @@ +import cx from 'classnames'; +import { AnimatePresence, motion } from 'framer-motion'; +import type { PropsWithChildren } from 'react'; + +export type AnimatedTableProps = PropsWithChildren & { + isTableOpen?: boolean; + isMediumScreen?: boolean; +}; + +export function AnimatedTable({ + children, + isMediumScreen, + isTableOpen, +}: AnimatedTableProps) { + return ( + + {(!isMediumScreen || isTableOpen) && ( + + {children} + + )} + + ); +} diff --git a/apps/tangle-dapp/app/restake/ExpandTableButton.tsx b/apps/tangle-dapp/app/restake/ExpandTableButton.tsx new file mode 100644 index 0000000000..7268d87aec --- /dev/null +++ b/apps/tangle-dapp/app/restake/ExpandTableButton.tsx @@ -0,0 +1,70 @@ +import { DoubleArrowRightIcon } from '@radix-ui/react-icons'; +import { TooltipTrigger } from '@radix-ui/react-tooltip'; +import IconButton from '@webb-tools/webb-ui-components/components/buttons/IconButton'; +import { + Tooltip, + TooltipBody, +} from '@webb-tools/webb-ui-components/components/Tooltip'; +import type { ComponentProps, ReactNode } from 'react'; +import { twMerge } from 'tailwind-merge'; + +export enum NotificationVariant { + PENDING = 'pending', + SUCCESS = 'success', +} + +export type ExpandTableButtonProps = ComponentProps<'button'> & { + notificationVariant?: NotificationVariant; + tooltipContent?: ReactNode; +}; + +const colorClasses = { + [NotificationVariant.PENDING]: { + back: twMerge('bg-amber-400'), + front: twMerge('bg-amber-500'), + }, + [NotificationVariant.SUCCESS]: { + back: twMerge('bg-green-400'), + front: twMerge('bg-green-500'), + }, +} as const satisfies Record< + NotificationVariant, + { back: string; front: string } +>; + +export function ExpandTableButton({ + notificationVariant, + tooltipContent, + ...props +}: ExpandTableButtonProps) { + return ( + + + + + + {notificationVariant && ( + <> + + + + + + )} + + + + {tooltipContent} + + ); +} diff --git a/apps/tangle-dapp/app/restake/LoadingPage.tsx b/apps/tangle-dapp/app/restake/LoadingPage.tsx new file mode 100644 index 0000000000..d48fa1f676 --- /dev/null +++ b/apps/tangle-dapp/app/restake/LoadingPage.tsx @@ -0,0 +1,39 @@ +import Button from '@webb-tools/webb-ui-components/components/buttons/Button'; +import { Card } from '@webb-tools/webb-ui-components/components/Card'; +import SkeletonLoader from '@webb-tools/webb-ui-components/components/SkeletonLoader'; +import { FC } from 'react'; + +import RestakeTabs from './RestakeTabs'; +import StyleContainer from './StyleContainer'; + +const LoadingPage: FC = () => { + return ( + + + + + + +
+
+ + + +
+ +
+ + + +
+
+ + +
+
+ ); +}; + +export default LoadingPage; diff --git a/apps/tangle-dapp/app/restake/StyleContainer.tsx b/apps/tangle-dapp/app/restake/StyleContainer.tsx new file mode 100644 index 0000000000..b0edef2561 --- /dev/null +++ b/apps/tangle-dapp/app/restake/StyleContainer.tsx @@ -0,0 +1,21 @@ +import { ComponentProps, forwardRef, PropsWithChildren } from 'react'; +import { twMerge } from 'tailwind-merge'; + +const StyleContainer = forwardRef< + HTMLDivElement, + PropsWithChildren> +>(({ children, className, ...props }, ref) => { + return ( +
+ {children} +
+ ); +}); + +StyleContainer.displayName = 'StyleContainer'; + +export default StyleContainer; diff --git a/apps/tangle-dapp/app/restake/deposit/layout.tsx b/apps/tangle-dapp/app/restake/deposit/layout.tsx deleted file mode 100644 index 64ad4ce10e..0000000000 --- a/apps/tangle-dapp/app/restake/deposit/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { PropsWithChildren } from 'react'; - -import RestakeTabs from '../RestakeTabs'; - -export const dynamic = 'force-static'; - -const layout = ({ children }: PropsWithChildren) => { - return ( -
- - - {children} -
- ); -}; - -export default layout; diff --git a/apps/tangle-dapp/app/restake/deposit/loading.tsx b/apps/tangle-dapp/app/restake/deposit/loading.tsx new file mode 100644 index 0000000000..0079e3b575 --- /dev/null +++ b/apps/tangle-dapp/app/restake/deposit/loading.tsx @@ -0,0 +1,7 @@ +import LoadingPage from '../LoadingPage'; + +const Page = () => { + return ; +}; + +export default Page; diff --git a/apps/tangle-dapp/app/restake/deposit/page.tsx b/apps/tangle-dapp/app/restake/deposit/page.tsx index c54915d55a..c710cfa6ff 100644 --- a/apps/tangle-dapp/app/restake/deposit/page.tsx +++ b/apps/tangle-dapp/app/restake/deposit/page.tsx @@ -1,7 +1,14 @@ +import RestakeTabs from '../RestakeTabs'; +import StyleContainer from '../StyleContainer'; import DepositForm from './DepositForm'; export const dynamic = 'force-static'; export default function DepositPage() { - return ; + return ( + + + + + ); } diff --git a/apps/tangle-dapp/app/restake/OperatorsTable.tsx b/apps/tangle-dapp/app/restake/overview/OperatorsTable.tsx similarity index 89% rename from apps/tangle-dapp/app/restake/OperatorsTable.tsx rename to apps/tangle-dapp/app/restake/overview/OperatorsTable.tsx index bed4dd47a7..bac7ffa607 100644 --- a/apps/tangle-dapp/app/restake/OperatorsTable.tsx +++ b/apps/tangle-dapp/app/restake/overview/OperatorsTable.tsx @@ -5,10 +5,10 @@ import type { OperatorMap } from '@webb-tools/tangle-shared-ui/types/restake'; import { Input } from '@webb-tools/webb-ui-components/components/Input'; import { type ComponentProps, type FC, useMemo, useState } from 'react'; -import OperatorsTableUI from '../../components/tables/Operators'; -import { useRestakeContext } from '../../context/RestakeContext'; -import useIdentities from '../../data/useIdentities'; -import { delegationsToVaultTokens } from './utils'; +import OperatorsTableUI from '../../../components/tables/Operators'; +import { useRestakeContext } from '../../../context/RestakeContext'; +import useIdentities from '../../../data/useIdentities'; +import { delegationsToVaultTokens } from '../utils'; type OperatorUI = NonNullable< ComponentProps['data'] diff --git a/apps/tangle-dapp/app/restake/TableTabs.tsx b/apps/tangle-dapp/app/restake/overview/TableTabs.tsx similarity index 92% rename from apps/tangle-dapp/app/restake/TableTabs.tsx rename to apps/tangle-dapp/app/restake/overview/TableTabs.tsx index 1ee8d605c3..bcee7038af 100644 --- a/apps/tangle-dapp/app/restake/TableTabs.tsx +++ b/apps/tangle-dapp/app/restake/overview/TableTabs.tsx @@ -6,11 +6,11 @@ import { TableAndChartTabs } from '@webb-tools/webb-ui-components/components/Tab import { TabContent } from '@webb-tools/webb-ui-components/components/Tabs/TabContent'; import { type ComponentProps, type FC, useMemo } from 'react'; -import VaultAssetsTable from '../../components/tables/VaultAssets'; -import VaultsTable from '../../components/tables/Vaults'; -import { useRestakeContext } from '../../context/RestakeContext'; -import useRestakeRewardConfig from '../../data/restake/useRestakeRewardConfig'; -import type { DelegatorInfo } from '../../types/restake'; +import VaultAssetsTable from '../../../components/tables/VaultAssets'; +import VaultsTable from '../../../components/tables/Vaults'; +import { useRestakeContext } from '../../../context/RestakeContext'; +import useRestakeRewardConfig from '../../../data/restake/useRestakeRewardConfig'; +import type { DelegatorInfo } from '../../../types/restake'; import OperatorsTable from './OperatorsTable'; const RESTAKE_VAULTS_TAB = 'Restake Vaults'; diff --git a/apps/tangle-dapp/app/restake/overview/loading.tsx b/apps/tangle-dapp/app/restake/overview/loading.tsx new file mode 100644 index 0000000000..6fad6a447d --- /dev/null +++ b/apps/tangle-dapp/app/restake/overview/loading.tsx @@ -0,0 +1,73 @@ +import Button from '@webb-tools/webb-ui-components/components/buttons/Button'; +import { + Card, + CardVariant, +} from '@webb-tools/webb-ui-components/components/Card'; +import SkeletonLoader from '@webb-tools/webb-ui-components/components/SkeletonLoader'; +import { TANGLE_DOCS_RESTAKING_URL } from '@webb-tools/webb-ui-components/constants'; +import { Typography } from '@webb-tools/webb-ui-components/typography/Typography'; +import type { FC } from 'react'; +import { twMerge } from 'tailwind-merge'; + +import { CONTENT } from './shared'; + +const LoadingPage: FC = () => { + return ( +
+
+ + + {CONTENT.OVERVIEW} + + +
+ + + +
+
+ + +
+ + How it works + + + {CONTENT.HOW_IT_WORKS} +
+ + +
+
+ + +
+ ); +}; + +export default LoadingPage; diff --git a/apps/tangle-dapp/app/restake/overview/page.tsx b/apps/tangle-dapp/app/restake/overview/page.tsx new file mode 100644 index 0000000000..2ed5deffe1 --- /dev/null +++ b/apps/tangle-dapp/app/restake/overview/page.tsx @@ -0,0 +1,106 @@ +'use client'; + +import useRestakeOperatorMap from '@webb-tools/tangle-shared-ui/data/restake/useRestakeOperatorMap'; +import Button from '@webb-tools/webb-ui-components/components/buttons/Button'; +import { + Card, + CardVariant, +} from '@webb-tools/webb-ui-components/components/Card'; +import { TANGLE_DOCS_RESTAKING_URL } from '@webb-tools/webb-ui-components/constants'; +import { Typography } from '@webb-tools/webb-ui-components/typography/Typography'; +import { twMerge } from 'tailwind-merge'; + +import StatItem from '../../../components/StatItem'; +import useRestakeDelegatorInfo from '../../../data/restake/useRestakeDelegatorInfo'; +import useRestakeTVL from '../../../data/restake/useRestakeTVL'; +import getTVLToDisplay from '../../../utils/getTVLToDisplay'; +import { CONTENT } from './shared'; +import TableTabs from './TableTabs'; + +export const dynamic = 'force-static'; + +export default function RestakePage() { + const { delegatorInfo } = useRestakeDelegatorInfo(); + const { operatorMap } = useRestakeOperatorMap(); + + const { + delegatorTVL, + operatorConcentration, + operatorTVL, + vaultTVL, + totalDelegatorTVL, + totalNetworkTVL, + } = useRestakeTVL(operatorMap, delegatorInfo); + + return ( + <> +
+
+ + + {CONTENT.OVERVIEW} + + +
+ + + +
+
+ + +
+ + How it works + + + {CONTENT.HOW_IT_WORKS} +
+ + +
+
+ + +
+ + ); +} diff --git a/apps/tangle-dapp/app/restake/overview/shared.tsx b/apps/tangle-dapp/app/restake/overview/shared.tsx new file mode 100644 index 0000000000..b320b831fa --- /dev/null +++ b/apps/tangle-dapp/app/restake/overview/shared.tsx @@ -0,0 +1,10 @@ +export const CONTENT = { + OVERVIEW: ( + <> + Operators on Tangle provide computation resources to power AVS Blueprints. +  Deposit and Delegate liquidity to earn yields. + + ), + HOW_IT_WORKS: + 'Tangle combines restaking with omnichain assets to provide a multi-asset crypto-economically secured compute infrastructure.', +} as const; diff --git a/apps/tangle-dapp/app/restake/page.tsx b/apps/tangle-dapp/app/restake/page.tsx index 9849306f87..b1a11cef4f 100644 --- a/apps/tangle-dapp/app/restake/page.tsx +++ b/apps/tangle-dapp/app/restake/page.tsx @@ -1,122 +1,9 @@ -'use client'; +import { redirect } from 'next/navigation'; -import useRestakeOperatorMap from '@webb-tools/tangle-shared-ui/data/restake/useRestakeOperatorMap'; -import { - Card, - CardVariant, - TANGLE_DOCS_RESTAKING_URL, -} from '@webb-tools/webb-ui-components'; -import Button from '@webb-tools/webb-ui-components/components/buttons/Button'; -import { Typography } from '@webb-tools/webb-ui-components/typography/Typography'; -import { twMerge } from 'tailwind-merge'; - -import StatItem from '../../components/StatItem'; -import useRestakeDelegatorInfo from '../../data/restake/useRestakeDelegatorInfo'; -import useRestakeTVL from '../../data/restake/useRestakeTVL'; -import getTVLToDisplay from '../../utils/getTVLToDisplay'; -import TableTabs from './TableTabs'; +import { PagePath } from '../../types'; export const dynamic = 'force-static'; -const CONTENT = { - OVERVIEW: ( - <> - Operators on Tangle provide computation resources to power AVS Blueprints. -  Deposit and Delegate liquidity to earn yields. - - ), - HOW_IT_WORKS: - 'Tangle combines restaking with omnichain assets to provide a multi-asset crypto-economically secured compute infrastructure.', -} as const; - -const minHeightClsx = 'min-h-[233px]'; - export default function RestakePage() { - const { delegatorInfo } = useRestakeDelegatorInfo(); - const { operatorMap } = useRestakeOperatorMap(); - - const { - delegatorTVL, - operatorConcentration, - operatorTVL, - vaultTVL, - totalDelegatorTVL, - totalNetworkTVL, - } = useRestakeTVL(operatorMap, delegatorInfo); - - return ( - <> -
-
- - - {CONTENT.OVERVIEW} - - -
- - - -
-
- - -
- - How it works - - - {CONTENT.HOW_IT_WORKS} -
- - -
-
- - -
- - ); + return redirect(PagePath.RESTAKE_OVERVIEW); } diff --git a/apps/tangle-dapp/app/restake/stake/Info.tsx b/apps/tangle-dapp/app/restake/stake/Info.tsx index dda7d15367..e1f626fb2f 100644 --- a/apps/tangle-dapp/app/restake/stake/Info.tsx +++ b/apps/tangle-dapp/app/restake/stake/Info.tsx @@ -1,34 +1,35 @@ import isDefined from '@webb-tools/dapp-types/utils/isDefined'; -import FeeDetails from '@webb-tools/webb-ui-components/components/FeeDetails'; import { memo } from 'react'; +import DetailsContainer from '../../../components/DetailsContainer'; +import DetailItem from '../../../components/LiquidStaking/stakeAndUnstake/DetailItem'; import useRestakeConsts from '../../../data/restake/useRestakeConsts'; const Info = memo(() => { const { leaveDelegatorsDelay, delegationBondLessDelay } = useRestakeConsts(); return ( - + + + + + ); }); diff --git a/apps/tangle-dapp/app/restake/stake/layout.tsx b/apps/tangle-dapp/app/restake/stake/layout.tsx deleted file mode 100644 index 64ad4ce10e..0000000000 --- a/apps/tangle-dapp/app/restake/stake/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { PropsWithChildren } from 'react'; - -import RestakeTabs from '../RestakeTabs'; - -export const dynamic = 'force-static'; - -const layout = ({ children }: PropsWithChildren) => { - return ( -
- - - {children} -
- ); -}; - -export default layout; diff --git a/apps/tangle-dapp/app/restake/stake/loading.tsx b/apps/tangle-dapp/app/restake/stake/loading.tsx new file mode 100644 index 0000000000..0079e3b575 --- /dev/null +++ b/apps/tangle-dapp/app/restake/stake/loading.tsx @@ -0,0 +1,7 @@ +import LoadingPage from '../LoadingPage'; + +const Page = () => { + return ; +}; + +export default Page; diff --git a/apps/tangle-dapp/app/restake/stake/page.tsx b/apps/tangle-dapp/app/restake/stake/page.tsx index 70a78f4403..58bf694c77 100644 --- a/apps/tangle-dapp/app/restake/stake/page.tsx +++ b/apps/tangle-dapp/app/restake/stake/page.tsx @@ -42,6 +42,8 @@ import type { DelegationFormFields } from '../../../types/restake'; import AssetList from '../AssetList'; import Form from '../Form'; import ModalContent from '../ModalContent'; +import RestakeTabs from '../RestakeTabs'; +import StyleContainer from '../StyleContainer'; import SupportedChainModal from '../SupportedChainModal'; import useSwitchChain from '../useSwitchChain'; import ActionButton from './ActionButton'; @@ -253,70 +255,74 @@ export default function Page() { ); return ( - -
-
- - -
- - - + + + + +
+ + +
+ + + +
-
- - - - - - - - + + + + + + + + + - - - - - - + + + + ); } diff --git a/apps/tangle-dapp/app/restake/unstake/UnstakeRequestTable.tsx b/apps/tangle-dapp/app/restake/unstake/UnstakeRequestTable.tsx index b295bbdc90..9f0eed8638 100644 --- a/apps/tangle-dapp/app/restake/unstake/UnstakeRequestTable.tsx +++ b/apps/tangle-dapp/app/restake/unstake/UnstakeRequestTable.tsx @@ -169,7 +169,7 @@ const UnstakeRequestTable = ({ <> -
+