Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: veYFI test #383

Merged
merged 29 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
'extends': ['./node_modules/@yearn-finance/web-lib/.eslintrc.cjs'],
'extends': ['./node_modules/@yearn-finance/web-lib/.eslintrc.cjs', 'plugin:react-hooks/recommended'],
'parser': '@typescript-eslint/parser',
'parserOptions': {
'ecmaFeatures': {
Expand All @@ -16,6 +16,11 @@ module.exports = {
'@typescript-eslint/indent': ['error', 'tab'],
'no-multi-spaces': ['error', {ignoreEOLComments: false}],
'no-mixed-spaces-and-tabs': 'error',
'react/jsx-max-props-per-line': 'off'
'react/jsx-max-props-per-line': 'off',
'react-hooks/exhaustive-deps': [
'warn', {
'additionalHooks': '^useAsyncTrigger$'
}
]
}
};
23 changes: 8 additions & 15 deletions apps/common/components/AmountInput.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {Renderable} from '@yearn-finance/web-lib/components/Renderable';
import {isZero} from '@yearn-finance/web-lib/utils/isZero';

import type {ReactElement} from 'react';
import type {TNormalizedBN} from '@common/types/types';

type TAmountInputProps = {
amount: string | number;
maxAmount?: string | number;
amount: TNormalizedBN;
maxAmount?: TNormalizedBN;
maxLabel?: string;
label?: string;
placeholder?: string;
legend?: string;
legend?: string | ReactElement;
error?: string;
disabled?: boolean;
loading?: boolean;
Expand All @@ -31,13 +31,6 @@ export function AmountInput({
onLegendClick,
onMaxClick
}: TAmountInputProps): ReactElement {
let displayedAmount = amount.toString();
if (isZero(displayedAmount) && !disabled) {
displayedAmount = '';
}
if (isZero(displayedAmount) && disabled) {
displayedAmount = '0';
}
return (
<div className={'w-full'}>
{label && (
Expand All @@ -49,10 +42,10 @@ export function AmountInput({
<div className={'relative flex w-full items-center justify-center'}>
<input
className={`h-10 w-full p-2 font-mono text-base font-normal outline-none ${maxAmount && !disabled ? 'pr-12' : null} ${error ? 'border border-solid border-[#EA5204] focus:border-[#EA5204]' : 'border-0 border-none'} ${disabled ? 'bg-neutral-300 text-neutral-600' : 'bg-neutral-0'}`}
type={'number'}
min={0}
type={'text'}
autoComplete={'off'}
aria-label={label}
value={displayedAmount}
value={amount.normalized}
onChange={onAmountChange ? (e): void => onAmountChange(e.target.value) : undefined}
placeholder={loading ? '' : placeholder ?? '0'}
disabled={disabled}
Expand All @@ -70,7 +63,7 @@ export function AmountInput({
role={onLegendClick ? 'button' : 'text'}
onClick={onLegendClick}
suppressHydrationWarning
className={`mt-1 pl-2 text-xs md:mr-0 ${error ? 'text-[#EA5204]' : 'text-neutral-600'}`}>
className={`mt-1 pl-1 text-xs md:mr-0 ${error ? 'text-[#EA5204]' : 'text-neutral-600'}`}>
{error ?? legend}
</legend>
</Renderable>
Expand Down
8 changes: 3 additions & 5 deletions apps/common/components/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const DropdownOption = (option: TDropdownOption): ReactElement => {
};

const DropdownEmpty = ({isSearching}: {isSearching: boolean}): ReactElement => {
if(isSearching) {
if (!isSearching) {
return (
<div className={'relative flex h-14 flex-col items-center justify-center px-4 text-center'}>
<div className={'flex h-10 items-center justify-center'}>
Expand Down Expand Up @@ -170,10 +170,8 @@ export const Dropdown = ({selected, options, onChange, label, legend, isDisabled
leaveFrom={'transform scale-100 opacity-100'}
leaveTo={'transform scale-95 opacity-0'}
afterLeave={(): void => {
performBatchedUpdates((): void => {
set_isOpen(false);
set_search('');
});
set_isOpen(false);
set_search('');
}}>
<Combobox.Options className={'yearn--dropdown-menu z-50'}>
{filteredOptions.length === 0 ? (
Expand Down
28 changes: 20 additions & 8 deletions apps/common/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import {cl} from '@yearn-finance/web-lib/utils/cl';

import type {ChangeEvent, ReactElement} from 'react';

export type TSearchBar = {
searchPlaceholder: string;
searchValue: string;
set_searchValue: (searchValue: string) => void;
}
className?: string;
};

export function SearchBar({searchPlaceholder, searchValue, set_searchValue}: TSearchBar): ReactElement {
export function SearchBar({searchPlaceholder, searchValue, set_searchValue, className}: TSearchBar): ReactElement {
return (
<>
<div className={'mt-1 flex h-10 w-full max-w-md items-center border border-neutral-0 bg-neutral-0 p-2 md:w-2/3'}>
<div
className={cl(
className,
'mt-1 flex h-10 w-full max-w-md items-center border border-neutral-0 bg-neutral-0 p-2 md:w-2/3'
)}>
<div className={'relative flex h-10 w-full flex-row items-center justify-between'}>
<input
id={'search'}
suppressHydrationWarning
className={'h-10 w-full overflow-x-scroll border-none bg-transparent px-0 py-2 text-base outline-none scrollbar-none placeholder:text-neutral-400'}
className={
'h-10 w-full overflow-x-scroll border-none bg-transparent px-0 py-2 text-base outline-none scrollbar-none placeholder:text-neutral-400'
}
type={'text'}
placeholder={searchPlaceholder}
value={searchValue}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
if (set_searchValue) {
set_searchValue(e.target.value);
}
}} />
}}
/>
<div className={'absolute right-0 text-neutral-400'}>
<svg
width={'20'}
Expand All @@ -33,11 +43,13 @@ export function SearchBar({searchPlaceholder, searchValue, set_searchValue}: TSe
<path
fillRule={'evenodd'}
clipRule={'evenodd'}
d={'M10 1C5.02972 1 1 5.02972 1 10C1 14.9703 5.02972 19 10 19C12.1249 19 14.0779 18.2635 15.6176 17.0318L21.2929 22.7071C21.6834 23.0976 22.3166 23.0976 22.7071 22.7071C23.0976 22.3166 23.0976 21.6834 22.7071 21.2929L17.0318 15.6176C18.2635 14.0779 19 12.1249 19 10C19 5.02972 14.9703 1 10 1ZM3 10C3 6.13428 6.13428 3 10 3C13.8657 3 17 6.13428 17 10C17 13.8657 13.8657 17 10 17C6.13428 17 3 13.8657 3 10Z'}
fill={'currentcolor'}/>
d={
'M10 1C5.02972 1 1 5.02972 1 10C1 14.9703 5.02972 19 10 19C12.1249 19 14.0779 18.2635 15.6176 17.0318L21.2929 22.7071C21.6834 23.0976 22.3166 23.0976 22.7071 22.7071C23.0976 22.3166 23.0976 21.6834 22.7071 21.2929L17.0318 15.6176C18.2635 14.0779 19 12.1249 19 10C19 5.02972 14.9703 1 10 1ZM3 10C3 6.13428 6.13428 3 10 3C13.8657 3 17 6.13428 17 10C17 13.8657 13.8657 17 10 17C6.13428 17 3 13.8657 3 10Z'
}
fill={'currentcolor'}
/>
</svg>
</div>

</div>
</div>
</>
Expand Down
48 changes: 39 additions & 9 deletions apps/common/components/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import {useCallback, useMemo, useState} from 'react';
import {sort} from '@veYFI/utils';
import {cl} from '@yearn-finance/web-lib/utils/cl';
import {isZero} from '@yearn-finance/web-lib/utils/isZero';
import {Pagination} from '@common/components/Pagination';
import {usePagination} from '@common/hooks/usePagination';
import {IconChevronPlain} from '@common/icons/IconChevronPlain';
Expand All @@ -25,6 +24,7 @@ type TMetadata<T> = {
sortable?: boolean;
fullWidth?: boolean;
columnSpan?: number;
isDisabled?: (item: T) => boolean;
format?: (item: T) => string | number;
transform?: (item: T) => ReactElement;
}
Expand All @@ -36,9 +36,10 @@ type TTableProps<T> = {
initialSortBy?: Extract<keyof T, string>;
onRowClick?: (item: T) => void;
itemsPerPage?: number;
isLoading?: boolean;
}

export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, itemsPerPage}: TTableProps<T>): ReactElement {
export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, itemsPerPage, isLoading}: TTableProps<T>): ReactElement {
const [{sortedBy, order}, set_state] = useState<TState<T>>({sortedBy: initialSortBy, order: 'desc'});

const sortedData = useMemo((): T[] => {
Expand All @@ -65,10 +66,11 @@ export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, it
9: 'md:grid-cols-9',
10: 'md:grid-cols-10',
11: 'md:grid-cols-11',
12: 'md:grid-cols-12'
12: 'md:grid-cols-12',
13: 'md:grid-cols-13'
};

const numberOfColumns = Math.min(columns ?? (metadata.length), 12) as keyof typeof gridColsVariants;
const numberOfColumns = Math.min(columns ?? (metadata.length), 13) as keyof typeof gridColsVariants;

const colSpanVariants = {
1: 'md:col-span-1',
Expand All @@ -82,7 +84,8 @@ export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, it
9: 'md:col-span-9',
10: 'md:col-span-10',
11: 'md:col-span-11',
12: 'md:col-span-12'
12: 'md:col-span-12',
13: 'md:col-span-13'
};

return (
Expand All @@ -103,7 +106,7 @@ export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, it
className || ''
)}
>
<p className={'text-xs font-bold text-neutral-400'}>
<p className={'text-xs text-neutral-500'}>
{label}
</p>
{sortable && sortedBy === key && <IconChevronPlain className={`yearn--sort-chevron ${order === 'asc' ? 'rotate-180' : ''}`} />}
Expand All @@ -112,6 +115,25 @@ export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, it
))}
</div>

{currentItems.length === 0 && isLoading ?
(
<div className={'flex h-96 w-full flex-col items-center justify-center px-10 py-2'}>
<b className={'text-lg'}>{'Fetching gauge data'}</b>
<p className={'text-neutral-600'}>{'We are retrieving the gauges. Please wait.'}</p>
<div className={'flex h-10 items-center justify-center'}>
<span className={'loader'} />
</div>
</div>
) : currentItems.length === 0 && !isLoading ?
(
<div className={'flex h-96 w-full flex-col items-center justify-center px-10 py-2'}>
<b className={'text-lg'}>{'No Gauges'}</b>
<p className={'text-neutral-600'}>
{'No gauges available.'}
</p>
</div>
) : null
}
{currentItems.map((item, rowIndex): ReactElement => (
<div
key={`row_${rowIndex}`}
Expand All @@ -122,8 +144,14 @@ export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, it
)}
onClick={(): void => onRowClick?.(item)}
>
{metadata.map(({key, label, className, fullWidth, columnSpan, format, transform}): ReactElement => {
const isNumber = !isNaN(item[key] as number);
{metadata.map(({key, label, className, fullWidth, columnSpan, format, transform, isDisabled}): ReactElement => {
let isNumberLike = false;
if (typeof item[key] === 'bigint') {
isNumberLike = true;
} else {
isNumberLike = !isNaN(Number(item[key]));
}
const isNumber = isNumberLike;

return (
<div
Expand All @@ -137,7 +165,9 @@ export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, it
{!fullWidth && <label className={'inline text-start text-sm text-neutral-500 md:hidden'}>{label}</label>}
<div
className={cl(
isZero(item[key] as number) ? 'text-neutral-400' : 'text-neutral-900',
(
isDisabled && isDisabled?.(item)
) ? 'text-neutral-400' : 'text-neutral-900',
isNumber ? 'font-number' : 'font-aeonik',
fullWidth ? 'w-full' : undefined
)}>
Expand Down
3 changes: 2 additions & 1 deletion apps/common/contexts/useWallet.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {createContext, memo, useCallback, useContext, useEffect, useMemo, useState} from 'react';
import {useChainId} from 'wagmi';
import {OPT_YVSTERN_ERN_STAKING_CONTRACT, OPT_YVAGEUR_USDC_STAKING_CONTRACT, OPT_YVALETH_FRXETH_STAKING_CONTRACT, OPT_YVALETH_WETH_STAKING_CONTRACT, OPT_YVALUSD_FRAX_STAKING_CONTRACT, OPT_YVALUSD_USDC_STAKING_CONTRACT, OPT_YVDAI_STAKING_CONTRACT, OPT_YVDOLA_USDC_STAKING_CONTRACT, OPT_YVDOLAUSDC_STAKING_CONTRACT, OPT_YVERN_DOLA_STAKING_CONTRACT, OPT_YVERN_LUSD_STAKING_CONTRACT, OPT_YVETH_STAKING_CONTRACT, OPT_YVEXA_WETH_STAKING_CONTRACT, OPT_YVFRAX_DOLA_STAKING_CONTRACT, OPT_YVIB_WETH_STAKING_CONTRACT, OPT_YVLDO_WSTETH_STAKING_CONTRACT, OPT_YVLUSD_WETH_STAKING_CONTRACT, OPT_YVMAI_ALUSD_STAKING_CONTRACT, OPT_YVMAI_DOLA_STAKING_CONTRACT, OPT_YVMAI_STAKING_CONTRACT, OPT_YVMAI_USDC_STAKING_CONTRACT, OPT_YVMAIUSDC_STAKING_CONTRACT, OPT_YVMIM_USDC_STAKING_CONTRACT, OPT_YVMTA_USDC_STAKING_CONTRACT, OPT_YVOP_USDC_STAKING_CONTRACT, OPT_YVOP_VELO_STAKING_CONTRACT, OPT_YVOP_WETH_STAKING_CONTRACT, OPT_YVSNX_USDC_STAKING_CONTRACT, OPT_YVSUSCUSDC_STAKING_CONTRACT, OPT_YVTBTC_WBTC_STAKING_CONTRACT, OPT_YVTBTC_WETH_STAKING_CONTRACT, OPT_YVUSDC_STAKING_CONTRACT, OPT_YVUSDT_STAKING_CONTRACT, OPT_YVVELO_USDC_STAKING_CONTRACT, OPT_YVWUSDR_USDC_STAKING_CONTRACT, OPT_YVWUSDRV2_USDC_STAKING_CONTRACT, STACKING_TO_VAULT} from '@vaults/constants/optRewards';
import {OPT_YVAGEUR_USDC_STAKING_CONTRACT, OPT_YVALETH_FRXETH_STAKING_CONTRACT, OPT_YVALETH_WETH_STAKING_CONTRACT, OPT_YVALUSD_FRAX_STAKING_CONTRACT, OPT_YVALUSD_USDC_STAKING_CONTRACT, OPT_YVDAI_STAKING_CONTRACT, OPT_YVDOLA_USDC_STAKING_CONTRACT, OPT_YVDOLAUSDC_STAKING_CONTRACT, OPT_YVERN_DOLA_STAKING_CONTRACT, OPT_YVERN_LUSD_STAKING_CONTRACT, OPT_YVETH_STAKING_CONTRACT, OPT_YVEXA_WETH_STAKING_CONTRACT, OPT_YVFRAX_DOLA_STAKING_CONTRACT, OPT_YVIB_WETH_STAKING_CONTRACT, OPT_YVLDO_WSTETH_STAKING_CONTRACT, OPT_YVLUSD_WETH_STAKING_CONTRACT, OPT_YVMAI_ALUSD_STAKING_CONTRACT, OPT_YVMAI_DOLA_STAKING_CONTRACT, OPT_YVMAI_STAKING_CONTRACT, OPT_YVMAI_USDC_STAKING_CONTRACT, OPT_YVMAIUSDC_STAKING_CONTRACT, OPT_YVMIM_USDC_STAKING_CONTRACT, OPT_YVMTA_USDC_STAKING_CONTRACT, OPT_YVOP_USDC_STAKING_CONTRACT, OPT_YVOP_VELO_STAKING_CONTRACT, OPT_YVOP_WETH_STAKING_CONTRACT, OPT_YVSNX_USDC_STAKING_CONTRACT, OPT_YVSTERN_ERN_STAKING_CONTRACT, OPT_YVSUSCUSDC_STAKING_CONTRACT, OPT_YVTBTC_WBTC_STAKING_CONTRACT, OPT_YVTBTC_WETH_STAKING_CONTRACT, OPT_YVUSDC_STAKING_CONTRACT, OPT_YVUSDT_STAKING_CONTRACT, OPT_YVVELO_USDC_STAKING_CONTRACT, OPT_YVWUSDR_USDC_STAKING_CONTRACT, OPT_YVWUSDRV2_USDC_STAKING_CONTRACT, STACKING_TO_VAULT} from '@vaults/constants/optRewards';
import {useUI} from '@yearn-finance/web-lib/contexts/useUI';
import {useBalances} from '@yearn-finance/web-lib/hooks/useBalances';
import {toAddress} from '@yearn-finance/web-lib/utils/address';
Expand Down Expand Up @@ -214,6 +214,7 @@ export const WalletContextApp = memo(function WalletContextApp({children}: {chil
refresh: onRefresh
}), [balances, cumulatedValueInVaults, isLoading, onRefresh, nonce]);


return (
<WalletContext.Provider value={contextValue}>
{children}
Expand Down
19 changes: 19 additions & 0 deletions apps/common/hooks/useAsyncEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable react-hooks/exhaustive-deps */

import {type DependencyList, useCallback, useEffect} from 'react';

//Should be useAsyncEffect, but exhaustive-deps is messing with this.
function useAsyncTrigger(effect: () => Promise<void>, deps: DependencyList): () => Promise<void> {
const asyncEffectInCallback = useCallback(async (): Promise<void> => {
effect();
}, [...deps]);

useEffect((): void => {
asyncEffectInCallback();
}, [asyncEffectInCallback]);

return asyncEffectInCallback;
}


export {useAsyncTrigger};
1 change: 0 additions & 1 deletion apps/common/hooks/useTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ type TProps = {
endTime?: TSeconds;
}

// TODO Check if we can use `getTimeUntil` from the web lib for this computation
export function computeTimeLeft({endTime}: {endTime?: TSeconds}): number {
if (!endTime) {
return 0;
Expand Down
6 changes: 3 additions & 3 deletions apps/common/hooks/useTokenPrice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import type {TAddress} from '@yearn-finance/web-lib/types';
export function useTokenPrice(address: TAddress): number {
const {prices} = useYearn();

const tokenPrice = useMemo((): number => (
formatToNormalizedValue(toBigInt(prices?.[address] || 0), 6)
), [address, prices]);
const tokenPrice = useMemo((): number => {
return formatToNormalizedValue(toBigInt(prices?.[address] || 0), 6);
}, [address, prices]);

return tokenPrice;
}
2 changes: 1 addition & 1 deletion apps/common/schemas/yDaemonVaultsSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const yDaemonVaultTokenSchema = z.object({

export const yDaemonVaultSchema = z.object({
address: addressSchema,
type: z.literal('Automated').or(z.literal('Standard').or(z.literal('Experimental'))),
type: z.literal('Automated').or(z.literal('Standard').or(z.literal('Experimental').or(z.literal('Automated Yearn Vault').or(z.literal('Yearn Vault'))))),
symbol: z.string(),
display_symbol: z.string(),
formated_symbol: z.string(),
Expand Down
29 changes: 29 additions & 0 deletions apps/common/utils/QueryParamsProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {useMemo} from 'react';
import {usePathname, useRouter, useSearchParams} from 'next/navigation';

import type {ReactElement} from 'react';
import type {PartialLocation, QueryParamAdapterComponent} from 'use-query-params';

export const NextQueryParamAdapter: QueryParamAdapterComponent = ({children}): ReactElement | null => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();

const adapter = useMemo((): any => {
return {
replace(location: PartialLocation): void {
router.replace(pathname + location.search);
},
push(location: PartialLocation): void {
router.push(pathname + location.search);
},
get location(): {search: string} {
return {
search: searchParams.toString()
};
}
};
}, [router, pathname, searchParams]);

return children(adapter);
};
2 changes: 2 additions & 0 deletions apps/common/utils/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ export async function isApprovedERC20(
******************************************************************************/
type TAllowanceOf = {
connector: Connector | undefined,
chainID: number,
tokenAddress: TAddress,
spenderAddress: TAddress
}
export async function allowanceOf(props: TAllowanceOf): Promise<bigint> {
const wagmiProvider = await toWagmiProvider(props.connector);
const result = await readContract({
...wagmiProvider,
chainId: props.chainID,
abi: erc20ABI,
address: props.tokenAddress,
functionName: 'allowance',
Expand Down
Loading