From 4dae4f3dd1796409dd5e446ae68f2d3eb03daa0a Mon Sep 17 00:00:00 2001 From: jz - synthetix <52280438+jmzwar@users.noreply.github.com> Date: Tue, 14 Nov 2023 22:54:29 +0100 Subject: [PATCH] Feature/loading design (#99) * wip loading on pools * wip * vault row loading * lint * move available collateral * available collateral * available collateral * add test * pool card test * available collateral test * move pool to snx/v3 * add exception test formatTimeToUnlock * fix deps * router-dom deps * mismatched fix * more deps * update home * jade feedback * revert available row --------- Co-authored-by: jmzwar --- liquidity/components/BorderBox/BorderBox.tsx | 2 +- .../Pools/AvailableCollateral.cy.tsx | 63 +++++ .../components/Pools/AvailableCollateral.tsx | 150 ++++++++++ .../Pools}/AvailableCollateralRow.tsx | 37 ++- liquidity/components/Pools/PoolCard.cy.tsx | 74 +++++ liquidity/components/Pools/PoolCard.tsx | 88 ++++++ liquidity/components/Pools/PoolsList.tsx | 37 +++ liquidity/components/Pools/PoolsLoading.tsx | 58 ++++ liquidity/components/Pools/Stats.tsx | 113 ++++++++ .../Home => components/Pools}/VaultRow.tsx | 24 +- .../components/Pools/VaultRowLoading.tsx | 52 ++++ liquidity/components/Pools/index.tsx | 8 + liquidity/components/Pools/package.json | 26 ++ liquidity/lib/formatters/date.test.ts | 61 ++++- liquidity/lib/formatters/date.ts | 23 ++ liquidity/lib/formatters/package.json | 5 +- .../useLiquidityPositions.ts | 1 + liquidity/ui/package.json | 2 +- .../src/components/Shared/Welcome/Welcome.tsx | 2 +- liquidity/ui/src/layouts/Default/Footer.tsx | 2 +- .../ui/src/pages/Home/AvailableCollateral.tsx | 140 ---------- liquidity/ui/src/pages/Home/Home.tsx | 259 ++---------------- liquidity/ui/src/pages/Home/Stats.tsx | 60 ---- liquidity/ui/src/pages/Pool/Pool.cy.tsx | 19 -- liquidity/ui/src/pages/Pool/Pool.tsx | 49 +--- yarn.lock | 29 +- 26 files changed, 872 insertions(+), 512 deletions(-) create mode 100644 liquidity/components/Pools/AvailableCollateral.cy.tsx create mode 100644 liquidity/components/Pools/AvailableCollateral.tsx rename liquidity/{ui/src/pages/Home => components/Pools}/AvailableCollateralRow.tsx (60%) create mode 100644 liquidity/components/Pools/PoolCard.cy.tsx create mode 100644 liquidity/components/Pools/PoolCard.tsx create mode 100644 liquidity/components/Pools/PoolsList.tsx create mode 100644 liquidity/components/Pools/PoolsLoading.tsx create mode 100644 liquidity/components/Pools/Stats.tsx rename liquidity/{ui/src/pages/Home => components/Pools}/VaultRow.tsx (86%) create mode 100644 liquidity/components/Pools/VaultRowLoading.tsx create mode 100644 liquidity/components/Pools/index.tsx create mode 100644 liquidity/components/Pools/package.json delete mode 100644 liquidity/ui/src/pages/Home/AvailableCollateral.tsx delete mode 100644 liquidity/ui/src/pages/Home/Stats.tsx delete mode 100644 liquidity/ui/src/pages/Pool/Pool.cy.tsx diff --git a/liquidity/components/BorderBox/BorderBox.tsx b/liquidity/components/BorderBox/BorderBox.tsx index fdb65a9de..6cd00ac5b 100644 --- a/liquidity/components/BorderBox/BorderBox.tsx +++ b/liquidity/components/BorderBox/BorderBox.tsx @@ -1,5 +1,5 @@ import { Flex, FlexProps } from '@chakra-ui/react'; export const BorderBox = (props: FlexProps) => ( - + ); diff --git a/liquidity/components/Pools/AvailableCollateral.cy.tsx b/liquidity/components/Pools/AvailableCollateral.cy.tsx new file mode 100644 index 000000000..d2272c500 --- /dev/null +++ b/liquidity/components/Pools/AvailableCollateral.cy.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { AvailableCollateralUi } from './AvailableCollateral'; +import { wei } from '@synthetixio/wei'; +import { AvailableCollateralRow } from './AvailableCollateralRow'; + +describe('AvailableCollateral Component', () => { + const accountCollaterals = [ + { + tokenAddress: 'snxAddress', + symbol: 'SNX', + availableCollateral: wei(100), + totalAssigned: wei(100), + totalDeposited: wei(50), + totalLocked: wei(40), + }, + ]; + + it('renders loading state initially', () => { + cy.mount( + + ); + cy.get('h2').should('contain', 'Loading Collateral...'); + }); + + it('renders available collateral when not in loading state', () => { + cy.mount( + + ); + + cy.get('h2').should('contain', 'Available Collateral'); + cy.get('[data-testid="available-collateral"]').should('exist').should('contain', '100 SNX'); + }); + + it('displays withdrawal information when not in loading state', () => { + const currentDate = new Date(); // Replace with a valid unlock date + const unlockDate = new Date(currentDate); + unlockDate.setDate(currentDate.getDate() + 3); + + cy.mount( + + ); + + // Check that the unlock date is displayed + cy.get('[data-testid="time-to-unlock"]').should('exist').should('contain', 'in about 3 days'); + // Ensure button is disabled pre lock date + cy.get('[data-testid="withdraw-button"]').should('be.disabled'); + }); +}); diff --git a/liquidity/components/Pools/AvailableCollateral.tsx b/liquidity/components/Pools/AvailableCollateral.tsx new file mode 100644 index 000000000..fd5357ece --- /dev/null +++ b/liquidity/components/Pools/AvailableCollateral.tsx @@ -0,0 +1,150 @@ +import { useEffect, useState } from 'react'; +import { + Alert, + AlertDescription, + AlertIcon, + AlertTitle, + Box, + Button, + Fade, + Flex, + Heading, + Skeleton, + SkeletonCircle, + Table, + Tbody, + Td, + Text, + Thead, + Tr, +} from '@chakra-ui/react'; +import { useParams } from '@snx-v3/useParams'; +import { AccountCollateralWithSymbol, useAccountCollateral } from '@snx-v3/useAccountCollateral'; +import { useAccountCollateralUnlockDate } from '@snx-v3/useAccountCollateralUnlockDate'; +import { AvailableCollateralRow } from './'; +import { BorderBox } from '@snx-v3/BorderBox'; +import { CollateralIcon } from '@snx-v3/icons'; +import { formatTimeToUnlock, unlockDateString } from '@snx-v3/formatters'; + +export function AvailableCollateralUi({ + accountCollaterals, + timeToUnlock, + unlockDateString, + unlockDate, + isLoading, +}: { + accountCollaterals: AccountCollateralWithSymbol[]; + timeToUnlock?: string; + unlockDateString?: string; + unlockDate?: Date; + isLoading: boolean; + AvailableCollateralRow: typeof AvailableCollateralRow; +}) { + return ( + + + {isLoading ? 'Loading Collateral...' : 'Available Collateral'} + + {!isLoading && ( + + + + This collateral can be deposited to pools. As a security precaution, this collateral + cannot be withdrawn until at least 1 day has elapsed since previous account activity. + + + + + Withdrawals available + {timeToUnlock && ( + + {timeToUnlock} + + )} + + + + + )} + + + + + + + + {isLoading ? ( + // Loading State + + + + + ) : ( + <> + {accountCollaterals?.map((accountCollateral) => ( + + ))} + + )} + +
+ + + + + + + + + + + +
+
+
+
+ ); +} + +export function AvailableCollateral() { + const { accountId } = useParams(); + const { data: accountCollateralsData, isLoading: isAccountCollateralsLoading } = + useAccountCollateral({ accountId, includeDelegationOff: true }); + + const { data: accountCollateralUnlockDate, isLoading: isAccountCollateralDateLoading } = + useAccountCollateralUnlockDate({ + accountId, + }); + + const [timeToUnlock, setTimeToUnlock] = useState(formatTimeToUnlock(accountCollateralUnlockDate)); + + useEffect(() => { + const interval = setInterval( + () => setTimeToUnlock(formatTimeToUnlock(accountCollateralUnlockDate)), + 1_000 + ); + return () => clearInterval(interval); + }, [accountCollateralUnlockDate]); + + const isLoading = isAccountCollateralsLoading || isAccountCollateralDateLoading; + + return ( + + ); +} diff --git a/liquidity/ui/src/pages/Home/AvailableCollateralRow.tsx b/liquidity/components/Pools/AvailableCollateralRow.tsx similarity index 60% rename from liquidity/ui/src/pages/Home/AvailableCollateralRow.tsx rename to liquidity/components/Pools/AvailableCollateralRow.tsx index c0c0fe221..5c0f6bcea 100644 --- a/liquidity/ui/src/pages/Home/AvailableCollateralRow.tsx +++ b/liquidity/components/Pools/AvailableCollateralRow.tsx @@ -1,11 +1,11 @@ +import { useState, lazy, Suspense } from 'react'; import { Amount } from '@snx-v3/Amount'; -import { Button, Flex, Td, Text, Tr } from '@chakra-ui/react'; +import { Button, Fade, Flex, Td, Text, Tr } from '@chakra-ui/react'; import { CollateralIcon } from '@snx-v3/icons'; import { AccountCollateralWithSymbol } from '@snx-v3/useAccountCollateral'; -import React from 'react'; import { safeImport } from '@synthetixio/safe-import'; -const WithdrawModal = React.lazy(() => safeImport(() => import('@snx-v3/WithdrawModal'))); +const WithdrawModal = lazy(() => safeImport(() => import('@snx-v3/WithdrawModal'))); function AvailableCollateralRowUi({ accountCollateral, @@ -14,25 +14,35 @@ function AvailableCollateralRowUi({ accountCollateral: AccountCollateralWithSymbol; isDisabled: boolean; }) { - const [isOpen, setIsOpen] = React.useState(false); + const [isOpen, setIsOpen] = useState(false); return ( - + + + - - {accountCollateral.symbol} - + + + {accountCollateral.symbol} + + - - + + + + {isOpen ? ( ) : null} - + ); @@ -50,6 +60,7 @@ export type AvailableCollateralRowProps = { accountCollateralUnlockDate?: Date; accountCollateral: AccountCollateralWithSymbol; }; + export function AvailableCollateralRow({ accountCollateral, accountCollateralUnlockDate, diff --git a/liquidity/components/Pools/PoolCard.cy.tsx b/liquidity/components/Pools/PoolCard.cy.tsx new file mode 100644 index 000000000..ddece395a --- /dev/null +++ b/liquidity/components/Pools/PoolCard.cy.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { PoolCard } from './PoolCard'; // Import your PoolCard component +import { wei } from '@synthetixio/wei'; + +describe('PoolCard Component', () => { + const pool = { + name: 'Spartan Council Pool', + id: '1', + isPreferred: true, + }; + + const collateralTypes = [ + { + tokenAddress: 'address1', + symbol: 'SNX', + depositingEnabled: true, + issuanceRatioD18: wei(400, 2, true), + liquidationRatioD18: wei(150, 2, true), + liquidationRewardD18: wei(5, 2, true), + oracleNodeId: '1', + minDelegationD18: wei(1, 2, true), + displaySymbol: 'SNX', + }, + ]; + + it('renders pool name and "Pool Info" button', () => { + cy.mount(); + + cy.get('h2').should('contain', 'Spartan Council Pool'); + cy.get('a').should('contain', 'Pool Info'); + }); + + it('renders a table with collateral information', () => { + cy.mount(); + + cy.get('table').should('exist'); + + cy.get('[data-testid="liquidation-ratio"]').should('exist').should('contain', '150%'); + cy.get('[data-testid="issuance-ratio"]').should('exist').should('contain', '400%'); + }); + + it('renders a table with collateral and liquidity information', () => { + const liquidityPositionsById = { + '1-SNX': { + id: `1-SNX` as `${string}-${string}`, + poolId: '1', + collateralType: collateralTypes[0], + accountId: '12344', + collateralAmount: wei(100), + collateralValue: wei(100), + debt: wei(100), + poolName: 'Spartan Council Pool', + cRatio: wei(300), + collateralPrice: wei(1), + }, + }; + + cy.mount( + + ); + + cy.get('table').should('exist'); + cy.get('[data-testid="liquidation-ratio"]').should('exist').should('contain', '150%'); + cy.get('[data-testid="issuance-ratio"]').should('exist').should('contain', '400%'); + cy.get('[data-testid="collateral-amount"]').should('exist').should('contain', '100 SNX'); + cy.get('[data-testid="collateral-value"]').should('exist').should('contain', '$100'); + cy.get('[data-testid="debt"]').should('exist').should('contain', '$100'); + cy.get('[data-testid="c-ratio"]').should('exist').should('contain', '100%'); + }); +}); diff --git a/liquidity/components/Pools/PoolCard.tsx b/liquidity/components/Pools/PoolCard.tsx new file mode 100644 index 000000000..56671ba1c --- /dev/null +++ b/liquidity/components/Pools/PoolCard.tsx @@ -0,0 +1,88 @@ +import { Flex, Fade, Heading, Button, Table, Thead, Tr, Th, Tbody, Box } from '@chakra-ui/react'; +import { BorderBox } from '@snx-v3/BorderBox'; +import { CollateralType } from '@snx-v3/useCollateralTypes'; +import { LiquidityPositionsById } from '@snx-v3/useLiquidityPositions'; +import { Link, generatePath } from 'react-router-dom'; +import { VaultRow } from './'; +import { PoolType } from '@snx-v3/usePools'; + +interface PoolcardProps { + pool: PoolType; + collateralTypes?: CollateralType[]; + liquidityPositionsById?: LiquidityPositionsById; +} + +export const PoolCard = ({ pool, collateralTypes, liquidityPositionsById }: PoolcardProps) => { + return ( + + + + + {pool.name} + + + {pool.id && ( + + + + )} + + + + + + + + + + + + + + + <> + {collateralTypes && + collateralTypes.map((c) => ( + + ))} + + +
+ Collateral + + Debt + + C-Ratio + + Issuance Ratio + + Liquidation Ratio +
+
+
+ ); +}; diff --git a/liquidity/components/Pools/PoolsList.tsx b/liquidity/components/Pools/PoolsList.tsx new file mode 100644 index 000000000..415646a1d --- /dev/null +++ b/liquidity/components/Pools/PoolsList.tsx @@ -0,0 +1,37 @@ +import { CollateralType } from '@snx-v3/useCollateralTypes'; +import { LiquidityPositionsById } from '@snx-v3/useLiquidityPositions'; +import { PoolType } from '@snx-v3/usePools'; +import { PoolCard, PoolsLoading } from '.'; + +interface PoolsListProps { + pools?: PoolType[]; + collateralTypes?: CollateralType[]; + liquidityPositionsById?: LiquidityPositionsById; + isLoading: boolean; +} + +export const PoolsList = ({ + pools, + collateralTypes, + liquidityPositionsById, + isLoading, +}: PoolsListProps) => { + return ( + <> + {isLoading ? ( + + ) : ( + <> + {pools?.map((pool) => ( + + ))} + + )} + + ); +}; diff --git a/liquidity/components/Pools/PoolsLoading.tsx b/liquidity/components/Pools/PoolsLoading.tsx new file mode 100644 index 000000000..57648a4a7 --- /dev/null +++ b/liquidity/components/Pools/PoolsLoading.tsx @@ -0,0 +1,58 @@ +import { Box, Fade, Flex, Heading, Table, Tbody, Th, Thead, Tr } from '@chakra-ui/react'; +import { BorderBox } from '@snx-v3/BorderBox'; +import { VaultRowLoading } from './'; + +export const PoolsLoading = () => { + return ( + + + + + Loading Pools... + + + + + + + + + + + + + + + + + {Array.from({ length: 2 }).map((_, i) => ( + + ))} + +
+ Collateral + + Debt + + C-Ratio + + Issuance Ratio + + Liquidation Ratio +
+
+
+ ); +}; diff --git a/liquidity/components/Pools/Stats.tsx b/liquidity/components/Pools/Stats.tsx new file mode 100644 index 000000000..5768a4fb4 --- /dev/null +++ b/liquidity/components/Pools/Stats.tsx @@ -0,0 +1,113 @@ +import { Fade, Flex, Skeleton, Text } from '@chakra-ui/react'; +import { formatNumberToUsd } from '@snx-v3/formatters'; +import { BorderBox } from '@snx-v3/BorderBox'; + +export interface StatsProps { + totalDebt?: number; + totalCollateral?: number; + isLoading: boolean; +} + +export const Stats = ({ totalDebt, totalCollateral, isLoading }: StatsProps) => { + return ( + + + + Total Collateral + + + {isLoading ? ( + + + 100,000 + + + ) : ( + + + {totalCollateral ? formatNumberToUsd(totalCollateral) : '—'} + + + )} + + + + + Total debt + + + + {isLoading ? ( + + + 100,000 + + + ) : ( + + + {totalDebt ? formatNumberToUsd(totalDebt) : '—'} + + + )} + + + + + Total Earnings Lifetime + + + {isLoading ? ( + + + 100,000 + + + ) : ( + + + — + + + )} + + + + ); +}; diff --git a/liquidity/ui/src/pages/Home/VaultRow.tsx b/liquidity/components/Pools/VaultRow.tsx similarity index 86% rename from liquidity/ui/src/pages/Home/VaultRow.tsx rename to liquidity/components/Pools/VaultRow.tsx index 99bdbadfc..886024094 100644 --- a/liquidity/ui/src/pages/Home/VaultRow.tsx +++ b/liquidity/components/Pools/VaultRow.tsx @@ -1,7 +1,7 @@ +import { FC } from 'react'; import { Amount } from '@snx-v3/Amount'; import { Button, Flex, Td, Text, Tr } from '@chakra-ui/react'; import { generatePath, Link, useLocation } from 'react-router-dom'; -import { FC } from 'react'; import { CollateralType } from '@snx-v3/useCollateralTypes'; import { onboard, useIsConnected } from '@snx-v3/useBlockchain'; import { CollateralIcon } from '@snx-v3/icons'; @@ -35,14 +35,14 @@ function VaultRowUi({ - + {liquidityPosition?.collateralValue.gt(0) ? ( ) : ( '-' )} - + {liquidityPosition?.collateralAmount.gt(0) && ( )}{' '} @@ -51,15 +51,25 @@ function VaultRowUi({ - + {liquidityPosition?.debt.gt(0) ? : '-'} - {cRatio.gt(0) ? : '-'} + + {cRatio.gt(0) ? : '-'} + - + - + {isConnected && hasLiquidity ? ( diff --git a/liquidity/components/Pools/VaultRowLoading.tsx b/liquidity/components/Pools/VaultRowLoading.tsx new file mode 100644 index 000000000..d1fd73848 --- /dev/null +++ b/liquidity/components/Pools/VaultRowLoading.tsx @@ -0,0 +1,52 @@ +import { Amount } from '@snx-v3/Amount'; +import { Button, Flex, Td, Tr, SkeletonCircle, Skeleton } from '@chakra-ui/react'; +import { Link } from 'react-router-dom'; +import { CollateralIcon } from '@snx-v3/icons'; +import { wei } from '@synthetixio/wei'; + +export const VaultRowLoading = ({ + startColor, + endColor, +}: { + startColor: string; + endColor: string; +}) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/liquidity/components/Pools/index.tsx b/liquidity/components/Pools/index.tsx new file mode 100644 index 000000000..e68d6181d --- /dev/null +++ b/liquidity/components/Pools/index.tsx @@ -0,0 +1,8 @@ +export * from './Stats'; +export * from './PoolCard'; +export * from './PoolsList'; +export * from './VaultRow'; +export * from './PoolsLoading'; +export * from './VaultRowLoading'; +export * from './AvailableCollateral'; +export * from './AvailableCollateralRow'; diff --git a/liquidity/components/Pools/package.json b/liquidity/components/Pools/package.json new file mode 100644 index 000000000..b368243dd --- /dev/null +++ b/liquidity/components/Pools/package.json @@ -0,0 +1,26 @@ +{ + "name": "@snx-v3/Pools", + "private": true, + "main": "index.ts", + "version": "0.0.1", + "dependencies": { + "@chakra-ui/react": "^2.8.1", + "@snx-v3/Amount": "workspace:*", + "@snx-v3/BorderBox": "workspace:*", + "@snx-v3/WithdrawModal": "workspace:*", + "@snx-v3/calculations": "workspace:*", + "@snx-v3/formatters": "workspace:*", + "@snx-v3/icons": "workspace:*", + "@snx-v3/useAccountCollateral": "workspace:*", + "@snx-v3/useAccountCollateralUnlockDate": "workspace:*", + "@snx-v3/useBlockchain": "workspace:*", + "@snx-v3/useCollateralTypes": "workspace:*", + "@snx-v3/useLiquidityPositions": "workspace:*", + "@snx-v3/useParams": "workspace:*", + "@snx-v3/usePools": "workspace:*", + "@synthetixio/safe-import": "workspace:*", + "@synthetixio/wei": "^2.74.4", + "react": "^18.2.0", + "react-router-dom": "^6.17.0" + } +} diff --git a/liquidity/lib/formatters/date.test.ts b/liquidity/lib/formatters/date.test.ts index 5284a3203..e8604b759 100644 --- a/liquidity/lib/formatters/date.test.ts +++ b/liquidity/lib/formatters/date.test.ts @@ -1,4 +1,4 @@ -import { convertSecondsToDisplayString } from './date'; +import { convertSecondsToDisplayString, unlockDateString, formatTimeToUnlock } from './date'; describe('convertSecondsToDisplayString', () => { it('should return null when seconds is 0', () => { @@ -41,3 +41,62 @@ describe('convertSecondsToDisplayString', () => { expect(convertSecondsToDisplayString(13320)).toBe('every 3.7 hours'); }); }); + +describe('unlockDateString function', () => { + // Test case 1: accountCollateralUnlockDate is undefined + it('should return undefined when accountCollateralUnlockDate is undefined', () => { + const result = unlockDateString(undefined); + expect(result).toBeUndefined(); + }); + + // Test case 2: accountCollateralUnlockDate is in the past + it('should return undefined when accountCollateralUnlockDate is in the past', () => { + const pastDate = new Date('2022-01-01T00:00:00'); + const result = unlockDateString(pastDate); + expect(result).toBeUndefined(); + }); + + // Test case 3: accountCollateralUnlockDate is in the future + it('should return a formatted date string when accountCollateralUnlockDate is in the future', () => { + // Create a future date (e.g., one day from now) + const futureDate = new Date(Date.now() + 24 * 60 * 60 * 1000); + const result = unlockDateString(futureDate); + + // Define the expected format (you may adjust this based on your intlFormat implementation) + const expectedFormat = /[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{4}, [0-9]{1,2}:[0-9]{2}/; + + expect(result).toMatch(expectedFormat); + }); +}); + +describe('formatTimeToUnlock function', () => { + // Test case 1: accountCollateralUnlockDate is undefined + it('should return undefined when accountCollateralUnlockDate is undefined', () => { + const result = formatTimeToUnlock(undefined); + expect(result).toBeUndefined(); + }); + + // Test case 2: accountCollateralUnlockDate is in the past + it('should return undefined when accountCollateralUnlockDate is in the past', () => { + const pastDate = new Date('2022-01-01T00:00:00'); + const result = formatTimeToUnlock(pastDate); + expect(result).toBeUndefined(); + }); + + // Test case 3: accountCollateralUnlockDate is in the future + it('should return a formatted time string when accountCollateralUnlockDate is in the future', () => { + // Create a future date (e.g., one day from now) + const futureDate = new Date(Date.now() + 24 * 60 * 60 * 1000); + const result = formatTimeToUnlock(futureDate); + + // Assert that the result is a non-empty string + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + }); + + // Test case 4: Exception on a invalid date + it('should throw an exception when the date is invalid', () => { + const invalidDate = new Date('invalid'); + expect(() => formatTimeToUnlock(invalidDate)).toThrow(); + }); +}); diff --git a/liquidity/lib/formatters/date.ts b/liquidity/lib/formatters/date.ts index c1cad7e22..c5b1a9f97 100644 --- a/liquidity/lib/formatters/date.ts +++ b/liquidity/lib/formatters/date.ts @@ -1,3 +1,5 @@ +import { formatDistanceToNow, intlFormat } from 'date-fns'; + export function convertSecondsToDisplayString(seconds: number) { const secondsInHour = 3600; const secondsInDay = 86400; @@ -23,3 +25,24 @@ export function convertSecondsToDisplayString(seconds: number) { return `every ${hours.toFixed(1)} hours`; } } + +export const formatTimeToUnlock = (accountCollateralUnlockDate: Date | undefined) => { + if (!accountCollateralUnlockDate || accountCollateralUnlockDate.getTime() <= Date.now()) { + return undefined; + } + return formatDistanceToNow(accountCollateralUnlockDate, { addSuffix: true }); +}; + +export const unlockDateString = (accountCollateralUnlockDate: Date | undefined) => { + if (!accountCollateralUnlockDate || accountCollateralUnlockDate.getTime() <= Date.now()) { + return undefined; + } + + return intlFormat(accountCollateralUnlockDate, { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + }); +}; diff --git a/liquidity/lib/formatters/package.json b/liquidity/lib/formatters/package.json index 530e4f3a5..43ecd61b0 100644 --- a/liquidity/lib/formatters/package.json +++ b/liquidity/lib/formatters/package.json @@ -2,5 +2,8 @@ "name": "@snx-v3/formatters", "private": true, "main": "index.ts", - "version": "0.0.0" + "version": "0.0.0", + "dependencies": { + "date-fns": "^2.30.0" + } } diff --git a/liquidity/lib/useLiquidityPositions/useLiquidityPositions.ts b/liquidity/lib/useLiquidityPositions/useLiquidityPositions.ts index 5beab555e..63e2c072f 100644 --- a/liquidity/lib/useLiquidityPositions/useLiquidityPositions.ts +++ b/liquidity/lib/useLiquidityPositions/useLiquidityPositions.ts @@ -24,6 +24,7 @@ export type LiquidityPositionType = { cRatio: Wei; debt: Wei; }; + export type LiquidityPositionsById = { [poolIdDashSymbol: `${string}-${string}`]: LiquidityPositionType; }; diff --git a/liquidity/ui/package.json b/liquidity/ui/package.json index a55f28161..723cb186d 100644 --- a/liquidity/ui/package.json +++ b/liquidity/ui/package.json @@ -26,12 +26,12 @@ "@snx-v3/NumberInput": "workspace:*", "@snx-v3/PercentBadges": "workspace:*", "@snx-v3/PoolBox": "workspace:*", + "@snx-v3/Pools": "workspace:*", "@snx-v3/RepayModal": "workspace:*", "@snx-v3/TermsModal": "workspace:*", "@snx-v3/TrendText": "workspace:*", "@snx-v3/UndelegateModal": "workspace:*", "@snx-v3/WithdrawIncrease": "workspace:*", - "@snx-v3/WithdrawModal": "workspace:*", "@snx-v3/calculations": "workspace:*", "@snx-v3/constants": "workspace:*", "@snx-v3/etherscanLink": "workspace:*", diff --git a/liquidity/ui/src/components/Shared/Welcome/Welcome.tsx b/liquidity/ui/src/components/Shared/Welcome/Welcome.tsx index 1a3ed30fa..6b1d2dcf2 100644 --- a/liquidity/ui/src/components/Shared/Welcome/Welcome.tsx +++ b/liquidity/ui/src/components/Shared/Welcome/Welcome.tsx @@ -1,7 +1,7 @@ import { Flex, Box, Alert, AlertIcon, Text, Link } from '@chakra-ui/react'; export const Welcome = () => ( - + diff --git a/liquidity/ui/src/layouts/Default/Footer.tsx b/liquidity/ui/src/layouts/Default/Footer.tsx index a9fe07832..704221207 100644 --- a/liquidity/ui/src/layouts/Default/Footer.tsx +++ b/liquidity/ui/src/layouts/Default/Footer.tsx @@ -2,7 +2,7 @@ import { Box } from '@chakra-ui/react'; export default function Footer() { return ( - + This is an experimental prototype for Synthetix V3. Provide feedback in{' '} #synthetix-v3 diff --git a/liquidity/ui/src/pages/Home/AvailableCollateral.tsx b/liquidity/ui/src/pages/Home/AvailableCollateral.tsx deleted file mode 100644 index 31926593d..000000000 --- a/liquidity/ui/src/pages/Home/AvailableCollateral.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React, { FC } from 'react'; -import { - Alert, - AlertDescription, - AlertIcon, - AlertTitle, - Box, - Flex, - Heading, - Table, - Tbody, - Text, - Thead, - Tr, -} from '@chakra-ui/react'; -import { useParams } from '@snx-v3/useParams'; -import { AccountCollateralWithSymbol, useAccountCollateral } from '@snx-v3/useAccountCollateral'; -import { useAccountCollateralUnlockDate } from '@snx-v3/useAccountCollateralUnlockDate'; -import { AvailableCollateralRow, AvailableCollateralRowProps } from './AvailableCollateralRow'; -import formatDistanceToNow from 'date-fns/formatDistanceToNow'; -import intlFormat from 'date-fns/intlFormat'; -import { BorderBox } from '@snx-v3/BorderBox'; - -export function AvailableCollateralUi({ - accountCollaterals, - timeToUnlock, - unlockDateString, - unlockDate, - AvailableCollateralRow, -}: { - accountCollaterals: AccountCollateralWithSymbol[]; - timeToUnlock?: string; - unlockDateString?: string; - unlockDate?: Date; - AvailableCollateralRow: FC; -}) { - if (accountCollaterals.length === 0) { - return null; - } - - return ( - - {/* only render if there's any 'available collateral' */} - - Available Collateral - - - - This collateral can be deposited to pools. As a security precaution, this collateral - cannot be withdrawn until at least 1 day has elapsed since previous account activity. - - - - - Withdrawals available - {timeToUnlock ? ( - {timeToUnlock} - ) : null} - - - - - - - - - - {accountCollaterals.map((accountCollateral) => ( - - ))} - -
-
-
- ); -} - -export function AvailableCollateral() { - const { accountId } = useParams(); - const accountCollaterals = useAccountCollateral({ accountId, includeDelegationOff: true }); - - const accountCollateralUnlockDate = useAccountCollateralUnlockDate({ accountId }); - - const formatTimeToUnlock = React.useCallback(() => { - if (accountCollateralUnlockDate.isLoading) { - return '—'; - } - if ( - !accountCollateralUnlockDate.data || - accountCollateralUnlockDate.data.getTime() <= Date.now() - ) { - return undefined; - } - return formatDistanceToNow(accountCollateralUnlockDate.data, { addSuffix: true }); - }, [accountCollateralUnlockDate.data, accountCollateralUnlockDate.isLoading]); - - const [timeToUnlock, setTimeToUnlock] = React.useState(formatTimeToUnlock()); - React.useEffect(() => { - const interval = setInterval(() => setTimeToUnlock(formatTimeToUnlock()), 1_000); - return () => clearInterval(interval); - }, [formatTimeToUnlock]); - - const unlockDateString = React.useMemo(() => { - if (accountCollateralUnlockDate.isLoading) { - return '—'; - } - if ( - !accountCollateralUnlockDate.data || - accountCollateralUnlockDate.data.getTime() <= Date.now() - ) { - return undefined; - } - return intlFormat(accountCollateralUnlockDate.data, { - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - }); - }, [accountCollateralUnlockDate.data, accountCollateralUnlockDate.isLoading]); - - return ( - - ); -} diff --git a/liquidity/ui/src/pages/Home/Home.tsx b/liquidity/ui/src/pages/Home/Home.tsx index d2ff36fd2..acb6a6241 100644 --- a/liquidity/ui/src/pages/Home/Home.tsx +++ b/liquidity/ui/src/pages/Home/Home.tsx @@ -1,70 +1,24 @@ +import React from 'react'; import { Helmet } from 'react-helmet'; -import { - Box, - Button, - Fade, - Flex, - Heading, - Skeleton, - Table, - Tbody, - Td, - Th, - Thead, - Tr, -} from '@chakra-ui/react'; -import React, { FC } from 'react'; -import { generatePath, Link, useLocation } from 'react-router-dom'; -import { CollateralType, useCollateralTypes } from '@snx-v3/useCollateralTypes'; -import { VaultRow, VaultRowProps } from './VaultRow'; -import { PoolsType, usePools } from '@snx-v3/usePools'; +import { Flex } from '@chakra-ui/react'; +import { useCollateralTypes } from '@snx-v3/useCollateralTypes'; +import { usePools } from '@snx-v3/usePools'; import { useParams } from '@snx-v3/useParams'; -import { BorderBox } from '@snx-v3/BorderBox'; -import { LiquidityPositionsById, useLiquidityPositions } from '@snx-v3/useLiquidityPositions'; +import { useLiquidityPositions } from '@snx-v3/useLiquidityPositions'; import { Welcome } from '../../components/Shared/Welcome'; -import { Stats, StatsProps } from './Stats'; -import { AvailableCollateral } from './AvailableCollateral'; +import { PoolsList, Stats, AvailableCollateral } from '@snx-v3/Pools'; -const LoadingRow = () => ( - - - - - - - - - - - - - - - - - - - - -); +export function Home() { + const { accountId } = useParams(); + + const { data: collateralTypes = [], isLoading: collateralTypesLoading } = useCollateralTypes(); + const { data: pools, isLoading: isPoolsLoading } = usePools(); + + const { data: liquidityPositionsById, isLoading: liquidityPositionLoading } = + useLiquidityPositions({ accountId }); + + const isLoading = collateralTypesLoading || isPoolsLoading || liquidityPositionLoading; -export function HomeUi({ - collateralTypes, - pools, - liquidityPositionsById, - isLoading, - VaultRow, - Stats, - AvailableCollateral, -}: { - collateralTypes?: CollateralType[]; - pools: PoolsType; - liquidityPositionsById?: LiquidityPositionsById; - isLoading: boolean; - VaultRow: FC; - Stats: FC; - AvailableCollateral: FC; -}) { const { totalCollateral, totalDebt } = Object.values(liquidityPositionsById || []).reduce( (acc, val) => { @@ -74,167 +28,6 @@ export function HomeUi({ }, { totalCollateral: 0, totalDebt: 0 } ) || {}; - const location = useLocation(); - - return ( - - - - - - {isLoading ? ( - - - - - - - - ) : ( - <> - {pools.map((pool) => ( - - - - - {pool.name} - - - {pool.id && ( - - - - )} - - - - - - - - - - - - - - - {collateralTypes ? ( - collateralTypes.map((c) => ( - - )) - ) : ( - <> - - - - )} - -
- Collateral - - Debt - - C-Ratio - - Issuance Ratio - - Liquidation Ratio -
-
-
- ))} - - )} - -
- ); -} - -export function Home() { - const { accountId } = useParams(); - - const { data: collateralTypes = [], isLoading: collateralTypesLoading } = useCollateralTypes(); - const { data: pools, isLoading: isPoolsLoading } = usePools(); - - const { - data: liquidityPositionsById, - isLoading: liquidityPositionLoading, - isInitialLoading: liquidityInitialLoading, - } = useLiquidityPositions({ accountId }); - - const isLoading = - collateralTypesLoading || - isPoolsLoading || - (liquidityPositionLoading && liquidityInitialLoading); return ( <> @@ -242,15 +35,17 @@ export function Home() { Synthetix V3 - + + + + + + ); } diff --git a/liquidity/ui/src/pages/Home/Stats.tsx b/liquidity/ui/src/pages/Home/Stats.tsx deleted file mode 100644 index de1420b4a..000000000 --- a/liquidity/ui/src/pages/Home/Stats.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Flex, Text } from '@chakra-ui/react'; -import { formatNumberToUsd } from '@snx-v3/formatters'; -import { BorderBox } from '@snx-v3/BorderBox'; - -export interface StatsProps { - totalDebt?: number; - totalCollateral?: number; -} - -export const Stats = ({ totalDebt, totalCollateral }: StatsProps) => { - return ( - - - - Total Collateral - - - {totalCollateral ? formatNumberToUsd(totalCollateral) : '—'} - - - - - Total debt - - - {totalDebt ? formatNumberToUsd(totalDebt) : '—'} - - - - - Total Earnings Lifetime - - - — - - - - ); -}; diff --git a/liquidity/ui/src/pages/Pool/Pool.cy.tsx b/liquidity/ui/src/pages/Pool/Pool.cy.tsx deleted file mode 100644 index 8e7e47462..000000000 --- a/liquidity/ui/src/pages/Pool/Pool.cy.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { PoolUi } from './Pool'; - -describe('Pool Page', () => { - it('should render Pool Page layout', () => { - cy.viewport(800, 500); - cy.mount( -
PoolHeader
} - CollateralSection={() =>
CollateralSection
} - MarketSection={() =>
MarketSection
} - isLoading={false} - /> - ); - cy.contains('a', 'Home').should('exist'); - cy.contains('div', 'PoolHeader').should('exist'); - cy.contains('div', 'CollateralSection').should('exist'); - cy.contains('div', 'MarketSection').should('exist'); - }); -}); diff --git a/liquidity/ui/src/pages/Pool/Pool.tsx b/liquidity/ui/src/pages/Pool/Pool.tsx index b09f6cc67..ebf33e648 100644 --- a/liquidity/ui/src/pages/Pool/Pool.tsx +++ b/liquidity/ui/src/pages/Pool/Pool.tsx @@ -1,6 +1,5 @@ import { Box, Divider, Flex } from '@chakra-ui/react'; import { Helmet } from 'react-helmet'; -import { FC } from 'react'; import { PoolHeader } from './PoolHeader'; import { MarketSection } from './MarketSection'; import { CollateralSection } from './CollateralSection'; @@ -8,35 +7,10 @@ import { useParams } from '@snx-v3/useParams'; import { HomeLink } from '@snx-v3/HomeLink'; import { usePool } from '@snx-v3/usePools'; import { Rewards } from '../../components/Rewards'; -import { RewardsType, useRewards } from '@snx-v3/useRewards'; +import { useRewards } from '@snx-v3/useRewards'; import { usePoolData } from '@snx-v3/usePoolData'; import { useCollateralType } from '@snx-v3/useCollateralTypes'; -export const PoolUi: FC<{ - PoolHeader: FC; - CollateralSection: FC; - MarketSection: FC; - isLoading: boolean; - rewards?: RewardsType; -}> = ({ PoolHeader, CollateralSection, MarketSection, isLoading, rewards }) => { - return ( - <> - - - - - - - - - - - - - - ); -}; - export const Pool = () => { const params = useParams(); const { data: pool } = usePool(params.poolId); @@ -65,13 +39,20 @@ export const Pool = () => { {title} - + <> + + + + + + + + + + + + + ); }; diff --git a/yarn.lock b/yarn.lock index 8f364c3bf..108758d7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5202,6 +5202,31 @@ __metadata: languageName: unknown linkType: soft +"@snx-v3/Pools@workspace:*, @snx-v3/Pools@workspace:liquidity/components/Pools": + version: 0.0.0-use.local + resolution: "@snx-v3/Pools@workspace:liquidity/components/Pools" + dependencies: + "@chakra-ui/react": ^2.8.1 + "@snx-v3/Amount": "workspace:*" + "@snx-v3/BorderBox": "workspace:*" + "@snx-v3/WithdrawModal": "workspace:*" + "@snx-v3/calculations": "workspace:*" + "@snx-v3/formatters": "workspace:*" + "@snx-v3/icons": "workspace:*" + "@snx-v3/useAccountCollateral": "workspace:*" + "@snx-v3/useAccountCollateralUnlockDate": "workspace:*" + "@snx-v3/useBlockchain": "workspace:*" + "@snx-v3/useCollateralTypes": "workspace:*" + "@snx-v3/useLiquidityPositions": "workspace:*" + "@snx-v3/useParams": "workspace:*" + "@snx-v3/usePools": "workspace:*" + "@synthetixio/safe-import": "workspace:*" + "@synthetixio/wei": ^2.74.4 + react: ^18.2.0 + react-router-dom: ^6.17.0 + languageName: unknown + linkType: soft + "@snx-v3/RepayModal@workspace:*, @snx-v3/RepayModal@workspace:liquidity/components/RepayModal": version: 0.0.0-use.local resolution: "@snx-v3/RepayModal@workspace:liquidity/components/RepayModal" @@ -5367,6 +5392,8 @@ __metadata: "@snx-v3/formatters@workspace:*, @snx-v3/formatters@workspace:liquidity/lib/formatters": version: 0.0.0-use.local resolution: "@snx-v3/formatters@workspace:liquidity/lib/formatters" + dependencies: + date-fns: ^2.30.0 languageName: unknown linkType: soft @@ -5474,12 +5501,12 @@ __metadata: "@snx-v3/NumberInput": "workspace:*" "@snx-v3/PercentBadges": "workspace:*" "@snx-v3/PoolBox": "workspace:*" + "@snx-v3/Pools": "workspace:*" "@snx-v3/RepayModal": "workspace:*" "@snx-v3/TermsModal": "workspace:*" "@snx-v3/TrendText": "workspace:*" "@snx-v3/UndelegateModal": "workspace:*" "@snx-v3/WithdrawIncrease": "workspace:*" - "@snx-v3/WithdrawModal": "workspace:*" "@snx-v3/calculations": "workspace:*" "@snx-v3/constants": "workspace:*" "@snx-v3/etherscanLink": "workspace:*"