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/.github/workflows/build.yml b/.github/workflows/build.yml index 39ff0f8..3473668 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,4 +25,6 @@ jobs: run: yarn --prefer-offline - name: Build project - run: yarn build \ No newline at end of file + run: yarn build + env: + WALLETCONNECT_PROJECT_ID: ${{ secrets.WALLETCONNECT_PROJECT_ID }} diff --git a/components/ImageTester.tsx b/components/ImageTester.tsx index e2ba9f2..84acc23 100755 --- a/components/ImageTester.tsx +++ b/components/ImageTester.tsx @@ -2,8 +2,8 @@ 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 {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3'; +import {toAddress} from '@yearn-finance/web-lib/utils/address'; function VaultImageTester({vaults}: {vaults: any[]}): ReactElement { const {onUpdateIconStatus, onUpdateTokenIconStatus} = useYearn(); diff --git a/components/PartnerEntity.tsx b/components/PartnerEntity.tsx index 1b5c00e..7b98bfd 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'; +import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3'; 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..8bb5d82 100755 --- a/components/StatusLine.tsx +++ b/components/StatusLine.tsx @@ -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..fcfe6b7 100755 --- a/components/TokenEntity.tsx +++ b/components/TokenEntity.tsx @@ -1,20 +1,21 @@ 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 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'; +import {getNetwork} from '@yearn-finance/web-lib/utils/wagmi/utils'; +import {copyToClipboard} from '@yearn-finance/web-lib/utils/helpers'; +import {IconCopy} from '@yearn-finance/web-lib/icons/IconCopy'; +import {IconLinkOut} from '@yearn-finance/web-lib/icons/IconLinkOut'; +import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3'; function TokenEntity({ tokenData, settings: statusSettings }: {tokenData: TTokenData, settings: TSettings}): ReactElement | null { const {chainID} = useWeb3(); - const {networks} = useSettings(); if (!tokenData) { return null; @@ -76,14 +77,14 @@ function TokenEntity({ suffix: ( {'for underlying token '} - + {tokenData.symbol || 'not_set'} - - - + + ) @@ -101,7 +102,7 @@ function TokenEntity({ suffix: ( {'for token '} - + {tokenData.name} {tokenData?.hasValidPrice ? ` (${tokenData.price}$)` : ''} diff --git a/components/VaultEntity.AnomaliesSection.tsx b/components/VaultEntity.AnomaliesSection.tsx index 0d24d80..883ac80 100755 --- a/components/VaultEntity.AnomaliesSection.tsx +++ b/components/VaultEntity.AnomaliesSection.tsx @@ -1,7 +1,7 @@ import React, {ReactElement, ReactNode, useEffect, useState} from 'react'; -import {performBatchedUpdates} from '@yearn-finance/web-lib/utils'; import StatusLine from 'components/StatusLine'; import type {TAnomalies, TAnomaliesSection} from 'types/types'; +import {performBatchedUpdates} from '@yearn-finance/web-lib/utils/performBatchedUpdates'; function AnomaliesSection({ label, diff --git a/components/VaultEntity.tsx b/components/VaultEntity.tsx index 091f915..87f19b6 100755 --- a/components/VaultEntity.tsx +++ b/components/VaultEntity.tsx @@ -1,15 +1,20 @@ 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 {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'; +import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3'; +import {toAddress} from '@yearn-finance/web-lib/utils/address'; +import {copyToClipboard} from '@yearn-finance/web-lib/utils/helpers'; +import {IconCopy} from '@yearn-finance/web-lib/icons/IconCopy'; +import {TAddress} from '@yearn-finance/web-lib/types'; +import {getNetwork} from '@yearn-finance/web-lib/utils/wagmi/utils'; +import {IconLinkOut} from '@yearn-finance/web-lib/icons/IconLinkOut'; +import {formatAmount} from '@yearn-finance/web-lib/utils/format.number'; const defaultFixModalData: TFixModalData = { isOpen: false, @@ -28,7 +33,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 +83,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 +112,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 +130,8 @@ function VaultEntity({ ,
-
- + copyToClipboard(renderSnippetMain())} className={'h-4 w-4 cursor-copy opacity-60 transition-colors hover:opacity-100'} />
@@ -138,7 +142,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 +160,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 +169,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 +177,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 +204,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 +221,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 +371,14 @@ function VaultEntity({ suffix: ( {'for vault '} - + {vault.symbol || 'not_set'} - - - + + ) @@ -384,14 +388,14 @@ function VaultEntity({ suffix: ( {'for underlying token '} - + {vault.token.symbol || 'not_set'} - - - + + ) @@ -421,7 +425,7 @@ function VaultEntity({ suffix: ( {'for vault '} - + {vaultData?.name} @@ -454,7 +458,7 @@ function VaultEntity({ suffix={( {'for strategy '} - + {strategy.name} @@ -478,7 +482,7 @@ function VaultEntity({ suffix={( {'for strategy '} - + {riskScore.strategy.name} {` (${riskScore.sum})`} @@ -504,7 +508,7 @@ function VaultEntity({ suffix={( {'for strategy '} - + {description.strategy.name} @@ -521,13 +525,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 +550,7 @@ function VaultEntity({ suffix={( {'for '} - + {shortAddress} @@ -565,14 +569,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..c186b99 --- /dev/null +++ b/components/common/AddressWithActions.tsx @@ -0,0 +1,64 @@ +import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3'; +import {IconCopy} from '@yearn-finance/web-lib/icons/IconCopy'; +import {IconLinkOut} from '@yearn-finance/web-lib/icons/IconLinkOut'; +import {TAddress} from '@yearn-finance/web-lib/types'; +import {toENS} from '@yearn-finance/web-lib/utils/address'; +import {copyToClipboard} from '@yearn-finance/web-lib/utils/helpers'; +import {getNetwork} from '@yearn-finance/web-lib/utils/wagmi/utils'; +import React, {ReactElement, useEffect, useState} from 'react'; + +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..4293b58 --- /dev/null +++ b/components/common/Card.tsx @@ -0,0 +1,145 @@ +import React, {Fragment, ReactElement} from 'react'; +import {Disclosure, Tab} from '@headlessui/react'; +import {AnimatePresence, motion} from 'framer-motion'; +import {IconChevron} from '@yearn-finance/web-lib/icons/IconChevron'; + +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..8b4b8ef --- /dev/null +++ b/components/common/Dropdown.tsx @@ -0,0 +1,66 @@ + +import React, {Fragment, ReactElement, cloneElement} from 'react'; +import {Menu, Transition} from '@headlessui/react'; +import {IconChevron} from '@yearn-finance/web-lib/icons/IconChevron'; + +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 => ( + <> + +
+ {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..d0fd211 100755 --- a/components/common/Header.tsx +++ b/components/common/Header.tsx @@ -1,14 +1,19 @@ +import {ModalMobileMenu} from '@yearn-finance/web-lib/components/ModalMobileMenu'; +import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3'; +import {IconHamburger} from '@yearn-finance/web-lib/icons/IconHamburger'; +import {IconNetworkArbitrum} from '@yearn-finance/web-lib/icons/IconNetworkArbitrum'; +import {IconNetworkEthereum} from '@yearn-finance/web-lib/icons/IconNetworkEthereum'; +import {IconNetworkFantom} from '@yearn-finance/web-lib/icons/IconNetworkFantom'; +import {IconNetworkOptimism} from '@yearn-finance/web-lib/icons/IconNetworkOptimism'; +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 +42,7 @@ function Header({
@@ -47,7 +52,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 +61,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..347d653 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/icons/IconSearch'; function KBarButton(): ReactElement { const {query} = useKBar(); @@ -8,9 +8,9 @@ function KBarButton(): ReactElement {