From 5de9d5f560fa907f5d06b74ff891db63ef8c1911 Mon Sep 17 00:00:00 2001 From: Karelian Pie Date: Sun, 3 Sep 2023 11:28:28 +0300 Subject: [PATCH 01/10] feat: Migrate web lib to 3.0 --- .eslintrc.js | 3 +- components/ImageTester.tsx | 3 +- components/PartnerEntity.tsx | 4 +- components/StatusLine.tsx | 6 +- components/StrategyEntity.tsx | 2 +- components/TokenEntity.tsx | 19 +- components/TranslationStatusLine.tsx | 2 +- components/VaultEntity.AnomaliesSection.tsx | 2 +- components/VaultEntity.tsx | 79 +- components/common/AddressWithActions.tsx | 58 + components/common/Card.tsx | 145 + components/common/Dropdown.tsx | 69 + components/common/Header.tsx | 24 +- components/common/KBarButton.tsx | 6 +- components/common/Kbar.tsx | 2 +- components/common/StandardFooter.tsx | 23 +- components/common/StatisticCard.tsx | 34 + components/modals/ModalFix.tsx | 3 +- contexts/useYearn.tsx | 7 +- package.json | 25 +- pages/_app.tsx | 33 +- pages/_document.tsx | 4 +- pages/index.tsx | 17 +- pages/settings.tsx | 11 +- public/sw.js | 2 +- ...orkbox-6a1bf588.js => workbox-e8535b98.js} | 2 +- style.css | 216 +- tailwind.config.js | 12 +- tsconfig.json | 9 - types/entities.tsx | 9 +- types/types.tsx | 11 +- yarn.lock | 6473 +++++++++++++---- 32 files changed, 5490 insertions(+), 1825 deletions(-) create mode 100644 components/common/AddressWithActions.tsx create mode 100644 components/common/Card.tsx create mode 100644 components/common/Dropdown.tsx create mode 100644 components/common/StatisticCard.tsx rename public/{workbox-6a1bf588.js => workbox-e8535b98.js} (98%) diff --git a/.eslintrc.js b/.eslintrc.js index 06ae2a5..da74a00 100755 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -114,6 +114,7 @@ module.exports = { 'comma-spacing': 'off', '@typescript-eslint/comma-spacing': ['error'], 'dot-notation': 'off', - '@typescript-eslint/dot-notation': ['error'] + '@typescript-eslint/dot-notation': ['error'], + '@typescript-eslint/no-explicit-any': 'off' } }; diff --git a/components/ImageTester.tsx b/components/ImageTester.tsx index e2ba9f2..95fcac8 100755 --- a/components/ImageTester.tsx +++ b/components/ImageTester.tsx @@ -2,8 +2,7 @@ import React, {ReactElement} from 'react'; import {useYearn} from 'contexts/useYearn'; import {TTokenData, TTokensData} from 'types/entities'; -import {useWeb3} from '@yearn-finance/web-lib/contexts'; -import {toAddress} from '@yearn-finance/web-lib/utils'; +import {toAddress, useWeb3} from '@yearn-finance/web-lib'; function VaultImageTester({vaults}: {vaults: any[]}): ReactElement { const {onUpdateIconStatus, onUpdateTokenIconStatus} = useYearn(); diff --git a/components/PartnerEntity.tsx b/components/PartnerEntity.tsx index 1b5c00e..72cd995 100755 --- a/components/PartnerEntity.tsx +++ b/components/PartnerEntity.tsx @@ -1,12 +1,12 @@ import React, {ReactElement} from 'react'; -import {Card} from '@yearn-finance/web-lib/components'; import type {TPartner, TSettings} from 'types/types'; import StatusLine from './StatusLine'; import {useWeb3} from '@yearn-finance/web-lib'; +import {Card} from 'components/common/Card'; type TPartnerEntityProps = {partner: string; status: TPartner[] ;settings: TSettings}; -const getSuffix = (src: string, chainID: string, hasAnomalies: boolean, partner: string): ReactElement | string => { +const getSuffix = (src: string, chainID: number, hasAnomalies: boolean, partner: string): ReactElement | string => { if (hasAnomalies && src === 'yDaemon') { return ( {'yDaemon (file '} diff --git a/components/StatusLine.tsx b/components/StatusLine.tsx index 40f2051..a4d2102 100755 --- a/components/StatusLine.tsx +++ b/components/StatusLine.tsx @@ -20,7 +20,7 @@ function StatusLine({ } return (
- +

{prefix} {' OK '} @@ -50,7 +50,7 @@ function StatusLine({ {suffix} {onClick ? : null} + className={'ml-2 mt-[2px] inline h-4 min-h-[16px] w-4 min-w-[16px] cursor-pointer text-neutral-500/40 transition-colors hover:text-neutral-500'} /> : null}

); @@ -64,7 +64,7 @@ function StatusLine({ {suffix} {onClick ? : null} + className={'ml-2 mt-[2px] inline h-4 min-h-[16px] w-4 min-w-[16px] cursor-pointer text-neutral-500/40 transition-colors hover:text-neutral-500'} /> : null}

); diff --git a/components/StrategyEntity.tsx b/components/StrategyEntity.tsx index 0e6a99b..4bf800b 100755 --- a/components/StrategyEntity.tsx +++ b/components/StrategyEntity.tsx @@ -1,8 +1,8 @@ import React, {ReactElement} from 'react'; -import {AddressWithActions} from '@yearn-finance/web-lib/components'; import type {TFile, TSettings} from 'types/types'; import {TStrategy} from 'types/entities'; import StatusLine from './StatusLine'; +import {AddressWithActions} from 'components/common/AddressWithActions'; type TProtocolStatus = { name: string; diff --git a/components/TokenEntity.tsx b/components/TokenEntity.tsx index 1fda3d1..e545a00 100755 --- a/components/TokenEntity.tsx +++ b/components/TokenEntity.tsx @@ -1,20 +1,17 @@ import React, {ReactElement} from 'react'; import Image from 'next/image'; -import {useSettings, useWeb3} from '@yearn-finance/web-lib/contexts'; -import {copyToClipboard} from '@yearn-finance/web-lib/utils'; -import {Copy, LinkOut} from '@yearn-finance/web-lib/icons'; -import {AddressWithActions} from '@yearn-finance/web-lib/components'; +import {IconCopy, IconLinkOut, copyToClipboard, getNetwork, useWeb3} from '@yearn-finance/web-lib'; import type {TSettings} from 'types/types'; import TranslationStatusLine from 'components/TranslationStatusLine'; import AnomaliesSection from 'components/VaultEntity.AnomaliesSection'; import {TTokenData} from 'types/entities'; +import {AddressWithActions} from 'components/common/AddressWithActions'; function TokenEntity({ tokenData, settings: statusSettings }: {tokenData: TTokenData, settings: TSettings}): ReactElement | null { const {chainID} = useWeb3(); - const {networks} = useSettings(); if (!tokenData) { return null; @@ -76,14 +73,14 @@ function TokenEntity({ suffix: ( {'for underlying token '} - + {tokenData.symbol || 'not_set'} - - - + + ) @@ -101,7 +98,7 @@ function TokenEntity({ suffix: ( {'for token '} - + {tokenData.name} {tokenData?.hasValidPrice ? ` (${tokenData.price}$)` : ''} diff --git a/components/TranslationStatusLine.tsx b/components/TranslationStatusLine.tsx index bf9d235..57af057 100755 --- a/components/TranslationStatusLine.tsx +++ b/components/TranslationStatusLine.tsx @@ -10,7 +10,7 @@ function TranslationStatusLine({ if (isValid) { return (
- +

{content}

diff --git a/components/VaultEntity.AnomaliesSection.tsx b/components/VaultEntity.AnomaliesSection.tsx index 0d24d80..29c9f73 100755 --- a/components/VaultEntity.AnomaliesSection.tsx +++ b/components/VaultEntity.AnomaliesSection.tsx @@ -1,5 +1,5 @@ import React, {ReactElement, ReactNode, useEffect, useState} from 'react'; -import {performBatchedUpdates} from '@yearn-finance/web-lib/utils'; +import {performBatchedUpdates} from '@yearn-finance/web-lib'; import StatusLine from 'components/StatusLine'; import type {TAnomalies, TAnomaliesSection} from 'types/types'; diff --git a/components/VaultEntity.tsx b/components/VaultEntity.tsx index 091f915..78e3a5d 100755 --- a/components/VaultEntity.tsx +++ b/components/VaultEntity.tsx @@ -1,15 +1,13 @@ import React, {ReactElement, ReactNode, useState} from 'react'; import Image from 'next/image'; -import {useSettings, useWeb3} from '@yearn-finance/web-lib/contexts'; -import {copyToClipboard, format, toAddress} from '@yearn-finance/web-lib/utils'; -import {AddressWithActions} from '@yearn-finance/web-lib/components'; +import {IconCopy, IconLinkOut, TAddress, copyToClipboard, formatAmount, getNetwork, toAddress, useWeb3} from '@yearn-finance/web-lib'; import {useYearn} from 'contexts/useYearn'; import AnomaliesSection from 'components/VaultEntity.AnomaliesSection'; import StatusLine from 'components/StatusLine'; import ModalFix from 'components/modals/ModalFix'; import Code from 'components/Code'; import type {TFixModalData, TSettings} from 'types/types'; -import {Copy, LinkOut} from '@yearn-finance/web-lib/icons'; +import {AddressWithActions} from 'components/common/AddressWithActions'; const defaultFixModalData: TFixModalData = { isOpen: false, @@ -28,7 +26,6 @@ function VaultEntity({ }: { vault: any, settings: TSettings, noStrategies?: boolean }): ReactElement | null { const {aggregatedData} = useYearn(); const {chainID} = useWeb3(); - const {networks} = useSettings(); const [fixModalData, set_fixModalData] = useState(defaultFixModalData); if (!vault) { @@ -79,20 +76,20 @@ function VaultEntity({ {'2. Append the following snippet at the end of the '} copyToClipboard('contracts')} - className={'cursor-copy rounded-md bg-neutral-200 py-1 px-2 text-sm'}> + className={'cursor-copy rounded-md bg-neutral-200 px-2 py-1 text-sm'}> {'contracts'} {' object in the '} copyToClipboard('b2c.json')} - className={'cursor-copy rounded-md bg-neutral-200 py-1 px-2 text-sm'}> + className={'cursor-copy rounded-md bg-neutral-200 px-2 py-1 text-sm'}> {'b2c.json'} {'file.'} ,
-
- + copyToClipboard(renderSnippetB2C())} className={'h-4 w-4 cursor-copy opacity-60 transition-colors hover:opacity-100'} />
@@ -108,13 +105,13 @@ function VaultEntity({ {'4. Clone and rename '} copyToClipboard('_vault_v0.4.3.json')} - className={'cursor-copy rounded-md bg-neutral-200 py-1 px-2 text-sm'}> + className={'cursor-copy rounded-md bg-neutral-200 px-2 py-1 text-sm'}> {'_vault_v0.4.3.json'} {' to '} copyToClipboard(`${vault.address}.json`)} - className={'cursor-copy rounded-md bg-neutral-200 py-1 px-2 text-sm'}> + className={'cursor-copy rounded-md bg-neutral-200 px-2 py-1 text-sm'}> {`${vault.address}.json`} , @@ -126,8 +123,8 @@ function VaultEntity({ ,
-
- + copyToClipboard(renderSnippetMain())} className={'h-4 w-4 cursor-copy opacity-60 transition-colors hover:opacity-100'} />
@@ -138,7 +135,7 @@ function VaultEntity({ }); } - function onTriggerModalForDescription(currentStrategy: { name: string, address: string }): void { + function onTriggerModalForDescription(currentStrategy: { name: string, address: TAddress }): void { set_fixModalData({ isOpen: true, fix: { @@ -156,7 +153,7 @@ function VaultEntity({ {'2. Select the file in which the strategy '} copyToClipboard(currentStrategy.name)} - className={'cursor-copy rounded-md bg-neutral-200 py-1 px-2 text-sm'}> + className={'cursor-copy rounded-md bg-neutral-200 px-2 py-1 text-sm'}> {currentStrategy.name} {' should belong to.'} @@ -165,7 +162,7 @@ function VaultEntity({ {'3a. If the file exists, append the address of the strategy to the file, under "addresses": '} copyToClipboard(currentStrategy.address)} - className={'cursor-copy rounded-md bg-neutral-200 py-1 px-2 text-sm'}> + className={'cursor-copy rounded-md bg-neutral-200 px-2 py-1 text-sm'}> {currentStrategy.address} , @@ -173,7 +170,7 @@ function VaultEntity({ {'3b. If the file does not exists, create a new one and append the address of the strategy to the file, under "addresses": '} copyToClipboard(currentStrategy.address)} - className={'cursor-copy rounded-md bg-neutral-200 py-1 px-2 text-sm'}> + className={'cursor-copy rounded-md bg-neutral-200 px-2 py-1 text-sm'}> {currentStrategy.address} @@ -200,7 +197,7 @@ function VaultEntity({ {'2. Add missing vault file with the filename '} copyToClipboard(`${vault.address}.json`)} - className={'cursor-copy rounded-md bg-neutral-200 py-1 px-2 text-sm'}> + className={'cursor-copy rounded-md bg-neutral-200 px-2 py-1 text-sm'}> {`${vault.address}.json`} @@ -217,7 +214,7 @@ function VaultEntity({ const hasStrategiesAnomaly = noStrategies; const hasRiskAnomaly = vaultData?.strategies.some((strategy): boolean => (strategy?.risk?.riskGroup || 'Others') === 'Others'); - const riskScores = (vaultData?.strategies ?? []).map((strategy): { strategy: { address: string; name: string; }; sum: number; isValid: boolean } => { + const riskScores = (vaultData?.strategies ?? []).map((strategy): { strategy: { address: TAddress; name: string; }; sum: number; isValid: boolean } => { const sum = ( (strategy?.risk?.riskDetails?.TVLImpact || 0) + (strategy?.risk?.riskDetails?.auditScore || 0) @@ -367,14 +364,14 @@ function VaultEntity({ suffix: ( {'for vault '} - + {vault.symbol || 'not_set'} - - - + + ) @@ -384,14 +381,14 @@ function VaultEntity({ suffix: ( {'for underlying token '} - + {vault.token.symbol || 'not_set'} - - - + + ) @@ -421,7 +418,7 @@ function VaultEntity({ suffix: ( {'for vault '} - + {vaultData?.name} @@ -454,7 +451,7 @@ function VaultEntity({ suffix={( {'for strategy '} - + {strategy.name} @@ -478,7 +475,7 @@ function VaultEntity({ suffix={( {'for strategy '} - + {riskScore.strategy.name} {` (${riskScore.sum})`} @@ -504,7 +501,7 @@ function VaultEntity({ suffix={( {'for strategy '} - + {description.strategy.name} @@ -521,13 +518,13 @@ function VaultEntity({ isValid: !vaultData?.hasErrorAPY, prefix: 'APY is set to ', errorMessage: `[ ERROR: ${vault?.apy?.error || 'unknown'} ]`, - suffix: `for vault - (Net APY: ${format.amount((vault?.apy?.net_apy || 0) * 100, 2, 4)}% | Gross APR: ${format.amount((vault?.apy?.gross_apr || 0) * 100, 2, 4)}%)` + suffix: `for vault - (Net APY: ${formatAmount((vault?.apy?.net_apy || 0) * 100, 2, 4)}% | Gross APR: ${formatAmount((vault?.apy?.gross_apr || 0) * 100, 2, 4)}%)` }, { isValid: !vaultData?.hasNewAPY, isWarning: true, prefix: 'APY is set to ', errorMessage: '[ NEW ]', - suffix: `for vault - (Net APY: ${format.amount((vault?.apy?.net_apy || 0) * 100, 2, 4)}% | Gross APR: ${format.amount((vault?.apy?.gross_apr || 0) * 100, 2, 4)}%)` + suffix: `for vault - (Net APY: ${formatAmount((vault?.apy?.net_apy || 0) * 100, 2, 4)}% | Gross APR: ${formatAmount((vault?.apy?.gross_apr || 0) * 100, 2, 4)}%)` }]} /> : null} {((vaultSettings.shouldShowMissingTranslations && !vaultSettings.shouldShowOnlyAnomalies) || (vaultSettings.shouldShowOnlyAnomalies && shouldRenderDueToMissingTranslations)) ? ( @@ -546,7 +543,7 @@ function VaultEntity({ suffix={( {'for '} - + {shortAddress} @@ -565,14 +562,14 @@ function VaultEntity({ suffix: ( {'for want token '} - + {vaultData?.token?.symbol || 'not_set'} - - - + + ) diff --git a/components/common/AddressWithActions.tsx b/components/common/AddressWithActions.tsx new file mode 100644 index 0000000..535c3ea --- /dev/null +++ b/components/common/AddressWithActions.tsx @@ -0,0 +1,58 @@ +import React, {ReactElement, useEffect, useState} from 'react'; +import {IconCopy, IconLinkOut, TAddress, copyToClipboard, getNetwork, toENS, useWeb3} from '@yearn-finance/web-lib'; + +type TAddressWithActions = { + address: TAddress; + explorer?: string; + truncate?: number; + wrapperClassName?: string; + className?: string; +}; + +export function AddressWithActions({ + address, + explorer = '', + truncate = 5, + wrapperClassName, + className = '' +}: TAddressWithActions): ReactElement { + const {chainID} = useWeb3(); + const [explorerURI, set_explorerURI] = useState(''); + + useEffect((): void => { + if (explorer !== '') { + set_explorerURI(explorer); + return; + } + + const network = getNetwork(chainID).defaultBlockExplorer; + if (network) { + set_explorerURI(network); + } + }, [chainID, explorer]); + + return ( + +

{toENS(address, truncate > 0, truncate)}

+ + +
+ ); +} diff --git a/components/common/Card.tsx b/components/common/Card.tsx new file mode 100644 index 0000000..36e70cd --- /dev/null +++ b/components/common/Card.tsx @@ -0,0 +1,145 @@ +import React, {ReactElement, Fragment} from 'react'; +import {Disclosure, Tab} from '@headlessui/react'; +import {AnimatePresence, motion} from 'framer-motion'; +import {IconChevron} from '@yearn-finance/web-lib'; + +export type TCard = { + className?: string; + variant?: 'surface' | 'background'; + padding?: 'none' | 'narrow' | 'regular'; + onClick?: React.MouseEventHandler; + children?: React.ReactNode; +} & React.ComponentPropsWithoutRef<'section'>; + +export type TCardDetailSummary = { + startChildren?: React.ReactNode; + endChildren?: React.ReactNode; + open?: boolean; +} & React.ComponentPropsWithoutRef<'div'>; + +export type TCardWithTabsOption = { + label: string; + children: ReactElement; +} + +export type TCardWithTabs = { + tabs: TCardWithTabsOption[]; +} + +export type TCardDetail = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + summary?: TCardDetailSummary | ReactElement | ((p: unknown) => ReactElement | TCardDetailSummary) | any; + variant?: 'surface' | 'background'; + isSticky?: boolean; + children?: React.ReactNode; +} & React.ComponentPropsWithoutRef<'div'>; + +function CardDetailsSummary({startChildren, endChildren, ...props}: TCardDetailSummary): ReactElement{ + return ( +
+
+ {startChildren} +
+
+ {endChildren} +
+ +
+
+
+ ); +} + +function CardDetails({summary, variant = 'surface', isSticky = true, children}: TCardDetail): ReactElement { + return ( + + {({open}): ReactElement => ( +
+ + {summary} + + + {open && ( + + + {children} + + + )} + +
+ )} +
+ ); +} + +function CardWithTabs({tabs}: TCardWithTabs): ReactElement { + return ( +
+ + + {tabs.map((option: TCardWithTabsOption): ReactElement => ( + `yearn--card-tab-item ${selected ? 'selected' : ''}`}> +

{option.label}

+
+ ))} +
+ + {tabs.map((option: TCardWithTabsOption): ReactElement => ( + + + {option.children} + + + ))} + +
+
+ ); +} + +function CardBase({ + children, + onClick, + padding = 'regular', + variant = 'surface', + className, + ...props +}: TCard): ReactElement { + return ( +
+ {children} +
+ ); +} + +export const Card = Object.assign(CardBase, { + Detail: Object.assign(CardDetails, {Summary: CardDetailsSummary}), + Tabs: CardWithTabs +}); diff --git a/components/common/Dropdown.tsx b/components/common/Dropdown.tsx new file mode 100644 index 0000000..b3a7cac --- /dev/null +++ b/components/common/Dropdown.tsx @@ -0,0 +1,69 @@ + +import React, {Fragment, ReactElement, cloneElement} from 'react'; +import {Menu, Transition} from '@headlessui/react'; +import {IconChevron} from '@yearn-finance/web-lib'; + +export type TDropdownOption = { + icon?: ReactElement; + value: string; + label: string; +}; + +export type TDropdownProps = { + options: TDropdownOption[]; + defaultOption: TDropdownOption; + selected: TDropdownOption; + onSelect: + | React.Dispatch> + | ((option: TDropdownOption) => void); +}; + +export function Dropdown({options, defaultOption, selected, onSelect}: TDropdownProps): ReactElement { + return ( +
+ + {({open}): ReactElement => { + console.log({open: `transition-transform${open ? '-rotate-90' : '-rotate-180'}`}); + return ( + <> + +
+ {selected?.icon ? cloneElement(selected.icon, {className: 'w-5 h-5 mr-2 min-w-[24px]'}) : null} +

{selected?.label || defaultOption.label}

+
+ +
+ + + {options.map((option): ReactElement => ( + + {({active}): ReactElement => ( +
onSelect(option)} + data-active={active} + className={'yearn--dropdown-menu-item'}> + {option.icon ? cloneElement(option.icon, {className: 'w-5 h-5 mr-2 min-w-[24px]'}) : null} +

{option.label}

+
+ )} +
+ ))} +
+
+ + ) + }} +
+
+ ); +} diff --git a/components/common/Header.tsx b/components/common/Header.tsx index 68c9379..c17f3e2 100755 --- a/components/common/Header.tsx +++ b/components/common/Header.tsx @@ -1,14 +1,13 @@ +import {IconHamburger, IconNetworkArbitrum, IconNetworkEthereum, IconNetworkFantom, IconNetworkOptimism, ModalMobileMenu, useWeb3} from '@yearn-finance/web-lib'; +import {Card} from 'components/common/Card'; +import {Dropdown} from 'components/common/Dropdown'; import React, {ReactElement, useEffect, useState} from 'react'; -import {useWeb3} from '@yearn-finance/web-lib/contexts'; -import {Card, Dropdown, ModalMobileMenu} from '@yearn-finance/web-lib/components'; -import {Hamburger, NetworkArbitrum, NetworkEthereum, - NetworkFantom, NetworkOptimism} from '@yearn-finance/web-lib/icons'; const options: any[] = [ - {icon: , label: 'Ethereum', value: 1}, - {icon: , label: 'Optimism', value: 10}, - {icon: , label: 'Fantom', value: 250}, - {icon: , label: 'Arbitrum', value: 42161} + {icon: , label: 'Ethereum', value: 1}, + {icon: , label: 'Optimism', value: 10}, + {icon: , label: 'Fantom', value: 250}, + {icon: , label: 'Arbitrum', value: 42161} ]; type THeader = { @@ -37,7 +36,7 @@ function Header({
@@ -47,7 +46,7 @@ function Header({ defaultOption={options[0]} options={options} selected={selectedOption} - onSelect={(option: any): void => onSwitchChain(option.value as number, true)} /> + onSelect={(option: any): void => onSwitchChain(option.value as number)} />
) : null} @@ -56,7 +55,10 @@ function Header({ set_hasMobileMenu(false)} /> + shouldUseNetworks={true} // TODO + onClose={(): void => set_hasMobileMenu(false)}> + + ); } diff --git a/components/common/KBarButton.tsx b/components/common/KBarButton.tsx index 8430c13..42d92cd 100755 --- a/components/common/KBarButton.tsx +++ b/components/common/KBarButton.tsx @@ -1,6 +1,6 @@ import React, {ReactElement} from 'react'; -import {Search} from '@yearn-finance/web-lib/icons'; import {useKBar} from 'kbar'; +import {IconSearch} from '@yearn-finance/web-lib'; function KBarButton(): ReactElement { const {query} = useKBar(); @@ -8,9 +8,9 @@ function KBarButton(): ReactElement {