From be17b6d544b133bfa35264af24b86a5de972830b Mon Sep 17 00:00:00 2001 From: Eugene Chybisov Date: Thu, 17 Feb 2022 20:46:28 +0200 Subject: [PATCH] feat: add useToken and useTokenBalance hooks, update SwapInput --- .../src/components/ContainerDrawer/types.ts | 2 +- .../src/components/SelectTokenDrawer/types.ts | 2 +- .../SwapChainButton/SwapChainButton.tsx | 23 +++--- .../src/components/SwapChainButton/types.ts | 10 +-- .../src/components/SwapInputAdornment.tsx | 54 -------------- .../SwapInputAdornment.style.tsx | 17 +++++ .../SwapInputAdornment/SwapInputAdornment.tsx | 70 +++++++++++++++++++ .../components/SwapInputAdornment/index.ts | 1 + .../src/components/TokenList/TokenList.tsx | 4 +- .../components/TokenList/TokenListItem.tsx | 2 +- packages/widget/src/hooks/useToken.ts | 21 ++++++ packages/widget/src/hooks/useTokenBalance.ts | 32 +++++++++ packages/widget/src/hooks/useTokens.ts | 4 +- packages/widget/src/i18n/en/translation.json | 4 +- .../widget/src/pages/SwapPage/SwapPage.tsx | 22 ++---- .../src/providers/SwapFormProvider/types.ts | 8 +-- 16 files changed, 178 insertions(+), 98 deletions(-) delete mode 100644 packages/widget/src/components/SwapInputAdornment.tsx create mode 100644 packages/widget/src/components/SwapInputAdornment/SwapInputAdornment.style.tsx create mode 100644 packages/widget/src/components/SwapInputAdornment/SwapInputAdornment.tsx create mode 100644 packages/widget/src/components/SwapInputAdornment/index.ts create mode 100644 packages/widget/src/hooks/useToken.ts create mode 100644 packages/widget/src/hooks/useTokenBalance.ts diff --git a/packages/widget/src/components/ContainerDrawer/types.ts b/packages/widget/src/components/ContainerDrawer/types.ts index 28f2f5d1e..8caa514f2 100644 --- a/packages/widget/src/components/ContainerDrawer/types.ts +++ b/packages/widget/src/components/ContainerDrawer/types.ts @@ -10,6 +10,6 @@ export type ContainerDrawerProps = DrawerProps & { }; export interface ContainerDrawerBase { - openDrawer(type: SwapFormDirection): void; + openDrawer(formType: SwapFormDirection): void; closeDrawer(): void; } diff --git a/packages/widget/src/components/SelectTokenDrawer/types.ts b/packages/widget/src/components/SelectTokenDrawer/types.ts index 1d7e1865b..bf98c3e8f 100644 --- a/packages/widget/src/components/SelectTokenDrawer/types.ts +++ b/packages/widget/src/components/SelectTokenDrawer/types.ts @@ -1,7 +1,7 @@ import { SwapFormDirection } from '../../providers/SwapFormProvider'; export interface SelectTokenDrawerBase { - openDrawer(type: SwapFormDirection): void; + openDrawer(formType: SwapFormDirection): void; closeDrawer(): void; } diff --git a/packages/widget/src/components/SwapChainButton/SwapChainButton.tsx b/packages/widget/src/components/SwapChainButton/SwapChainButton.tsx index 0dc64b0dc..5df6d34ad 100644 --- a/packages/widget/src/components/SwapChainButton/SwapChainButton.tsx +++ b/packages/widget/src/components/SwapChainButton/SwapChainButton.tsx @@ -1,6 +1,6 @@ -import { getChainByKey } from '@lifinance/sdk'; +import { useToken } from '@lifinance/widget/hooks/useToken'; import { KeyboardArrowDown as KeyboardArrowDownIcon } from '@mui/icons-material'; -import { useFormContext, useWatch } from 'react-hook-form'; +import { useFormState, useWatch } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { SwapFormKeyHelper } from '../../providers/SwapFormProvider'; import { Button } from './SwapChainButton.style'; @@ -8,32 +8,31 @@ import { SwapChainButtonProps } from './types'; export const SwapChainButton: React.FC = ({ onClick, - type, + formType, }) => { const { t } = useTranslation(); - const { - register, - formState: { isSubmitting }, - } = useFormContext(); - const [chain, token] = useWatch({ + const { isSubmitting } = useFormState(); + const [chainKey, tokenAddress] = useWatch({ name: [ - SwapFormKeyHelper.getChainKey(type), - SwapFormKeyHelper.getTokenKey(type), + SwapFormKeyHelper.getChainKey(formType), + SwapFormKeyHelper.getTokenKey(formType), ], }); + const { chain, token } = useToken(chainKey, tokenAddress); + return ( ); diff --git a/packages/widget/src/components/SwapChainButton/types.ts b/packages/widget/src/components/SwapChainButton/types.ts index ff8799af5..4b0bcde4f 100644 --- a/packages/widget/src/components/SwapChainButton/types.ts +++ b/packages/widget/src/components/SwapChainButton/types.ts @@ -1,6 +1,8 @@ -import { SwapFormDirection } from '../../providers/SwapFormProvider'; +import { + SwapFormDirection, + SwapFormTypeProps, +} from '../../providers/SwapFormProvider'; -export interface SwapChainButtonProps { - onClick?(type: SwapFormDirection): void; - type: SwapFormDirection; +export interface SwapChainButtonProps extends SwapFormTypeProps { + onClick?(formType: SwapFormDirection): void; } diff --git a/packages/widget/src/components/SwapInputAdornment.tsx b/packages/widget/src/components/SwapInputAdornment.tsx deleted file mode 100644 index 4ef0d259e..000000000 --- a/packages/widget/src/components/SwapInputAdornment.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { InputAdornment, Typography } from '@mui/material'; -import { styled } from '@mui/material/styles'; -import { useTranslation } from 'react-i18next'; - -const SwapInputAdornment = styled(Typography)(({ theme }) => ({ - borderLeft: '1px solid', - borderColor: theme.palette.grey[300], - paddingLeft: 8, - marginLeft: 8, -})); - -const SwapMaxInputAdornment = styled(Typography)({ - textDecoration: 'underline', - marginRight: 8, - '&:hover': { - cursor: 'pointer', - }, -}); - -export const SwapFromInputAdornment: React.FC<{ - maxAmount: number; - price: number; -}> = ({ maxAmount, price }) => { - const { t } = useTranslation(); - return ( - - - {t(`swap.max`)} - - - {t(`swap.maxAmount`, { - value: maxAmount, - minimumFractionDigits: 5, - })} - - - {t(`swap.price`, { value: price })} - - - ); -}; - -export const SwapToInputAdornment: React.FC<{ price: number }> = ({ - price, -}) => { - const { t } = useTranslation(); - return ( - - - {t(`swap.price`, { value: price })} - - - ); -}; diff --git a/packages/widget/src/components/SwapInputAdornment/SwapInputAdornment.style.tsx b/packages/widget/src/components/SwapInputAdornment/SwapInputAdornment.style.tsx new file mode 100644 index 000000000..260549442 --- /dev/null +++ b/packages/widget/src/components/SwapInputAdornment/SwapInputAdornment.style.tsx @@ -0,0 +1,17 @@ +import { Typography } from '@mui/material'; +import { styled } from '@mui/material/styles'; + +export const SwapPriceTypography = styled(Typography)(({ theme }) => ({ + borderLeft: '1px solid', + borderColor: theme.palette.grey[300], + paddingLeft: 8, + marginLeft: 8, +})); + +export const SwapMaxAmountTypography = styled(Typography)({ + textDecoration: 'underline', + marginRight: 8, + '&:hover': { + cursor: 'pointer', + }, +}); diff --git a/packages/widget/src/components/SwapInputAdornment/SwapInputAdornment.tsx b/packages/widget/src/components/SwapInputAdornment/SwapInputAdornment.tsx new file mode 100644 index 000000000..340a82b15 --- /dev/null +++ b/packages/widget/src/components/SwapInputAdornment/SwapInputAdornment.tsx @@ -0,0 +1,70 @@ +import { InputAdornment, Skeleton, Typography } from '@mui/material'; +import BigNumber from 'bignumber.js'; +import { useMemo } from 'react'; +import { useWatch } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { useTokenBalance } from '../../hooks/useTokenBalance'; +import { + SwapFormKeyHelper, + SwapFormTypeProps, +} from '../../providers/SwapFormProvider'; +import { formatTokenAmount } from '../../utils/format'; +import { + SwapMaxAmountTypography, + SwapPriceTypography, +} from './SwapInputAdornment.style'; + +export const SwapInputAdornment: React.FC = ({ + formType, +}) => { + const { t } = useTranslation(); + const [chainKey, tokenAddress] = useWatch({ + name: [ + SwapFormKeyHelper.getChainKey(formType), + SwapFormKeyHelper.getTokenKey(formType), + ], + }); + const { token, tokenWithBalance, isFetching } = useTokenBalance( + chainKey, + tokenAddress, + ); + + const amount = useMemo( + () => + token + ? formatTokenAmount(token, new BigNumber(tokenWithBalance?.amount ?? 0)) + : null, + [token, tokenWithBalance?.amount], + ); + + return ( + + {isFetching ? ( + + ) : ( + <> + {formType === 'from' && token && amount ? ( + <> + + {t(`swap.max`)} + + + {t(`swap.maxAmount`, { + amount, + })} + + + ) : null} + + {t(`swap.price`, { price: tokenWithBalance?.priceUSD ?? 0 })} + + + )} + + ); +}; diff --git a/packages/widget/src/components/SwapInputAdornment/index.ts b/packages/widget/src/components/SwapInputAdornment/index.ts new file mode 100644 index 000000000..51ace39bc --- /dev/null +++ b/packages/widget/src/components/SwapInputAdornment/index.ts @@ -0,0 +1 @@ +export * from './SwapInputAdornment'; diff --git a/packages/widget/src/components/TokenList/TokenList.tsx b/packages/widget/src/components/TokenList/TokenList.tsx index f142d43a3..8275fd102 100644 --- a/packages/widget/src/components/TokenList/TokenList.tsx +++ b/packages/widget/src/components/TokenList/TokenList.tsx @@ -95,8 +95,8 @@ export const TokenList: React.FC = ({ }); const handleTokenClick = useCallback( - (token: string) => { - setValue(SwapFormKeyHelper.getTokenKey(formType), token); + (tokenAddress: string) => { + setValue(SwapFormKeyHelper.getTokenKey(formType), tokenAddress); onClick?.(); }, [formType, onClick, setValue], diff --git a/packages/widget/src/components/TokenList/TokenListItem.tsx b/packages/widget/src/components/TokenList/TokenListItem.tsx index eeb0cf47b..7d7ee26d8 100644 --- a/packages/widget/src/components/TokenList/TokenListItem.tsx +++ b/packages/widget/src/components/TokenList/TokenListItem.tsx @@ -11,7 +11,7 @@ import { TokenListItemBaseProps, TokenListItemProps } from './types'; export const MemoizedTokenListItem: React.FC = memo( ({ onClick, size, start, token, isBalancesLoading }) => { - const handleClick = () => onClick?.(token.symbol); + const handleClick = () => onClick?.(token.address); return ( { + const { data: possibilities, isLoading } = useSwapPossibilities(); + + const [chain, token] = useMemo(() => { + const chain = getChainByKey(chainKey); + const token = possibilities?.tokens.find( + (token) => token.address === tokenAddress && token.chainId === chain.id, + ); + return [chain, token]; + }, [chainKey, possibilities?.tokens, tokenAddress]); + + return { + chain, + token, + isLoading, + }; +}; diff --git a/packages/widget/src/hooks/useTokenBalance.ts b/packages/widget/src/hooks/useTokenBalance.ts new file mode 100644 index 000000000..027cc537e --- /dev/null +++ b/packages/widget/src/hooks/useTokenBalance.ts @@ -0,0 +1,32 @@ +import Lifi, { ChainKey } from '@lifinance/sdk'; +import { useQuery } from 'react-query'; +import { usePriorityAccount } from './connectorHooks'; +import { useToken } from './useToken'; + +export const useTokenBalance = (chainKey: ChainKey, tokenAddress: string) => { + const account = usePriorityAccount(); + const { token } = useToken(chainKey, tokenAddress); + + const { data: tokenWithBalance, isFetching } = useQuery( + ['token', token?.symbol, account], + async ({ queryKey: [_, tokenSymbol, account] }) => { + if (!account || !token) { + return null; + } + const tokenBalance = await Lifi.getTokenBalance(account, token); + return tokenBalance; + }, + { + enabled: Boolean(account) && Boolean(token), + refetchIntervalInBackground: true, + refetchInterval: 60_000, + staleTime: 60_000, + }, + ); + + return { + token, + tokenWithBalance, + isFetching, + }; +}; diff --git a/packages/widget/src/hooks/useTokens.ts b/packages/widget/src/hooks/useTokens.ts index 69664102e..4ea4313d4 100644 --- a/packages/widget/src/hooks/useTokens.ts +++ b/packages/widget/src/hooks/useTokens.ts @@ -68,8 +68,8 @@ export const useTokens = (selectedChain: ChainKey) => { isLoading: isBalancesLoading, refetch, } = useQuery( - ['tokens', account, selectedChain], - async ({ queryKey: [_, account, chainKey] }) => { + ['tokens', selectedChain, account], + async ({ queryKey: [_, chainKey, account] }) => { if (!account || !possibilities) { return []; } diff --git a/packages/widget/src/i18n/en/translation.json b/packages/widget/src/i18n/en/translation.json index 55e4354d8..89d9f03e3 100644 --- a/packages/widget/src/i18n/en/translation.json +++ b/packages/widget/src/i18n/en/translation.json @@ -12,8 +12,8 @@ "from": "I'd like to swap", "to": "And receive to", "max": "Max", - "maxAmount": "({{value, number}})", - "price": "\u2248 {{value, currency(currency: USD)}}", + "maxAmount": "({{amount}})", + "price": "\u2248 {{price, currency(currency: USD)}}", "sendToRecipient": "Send to recipient", "routePriority": { "title": "Route priority", diff --git a/packages/widget/src/pages/SwapPage/SwapPage.tsx b/packages/widget/src/pages/SwapPage/SwapPage.tsx index fd463d238..1aae6d35a 100644 --- a/packages/widget/src/pages/SwapPage/SwapPage.tsx +++ b/packages/widget/src/pages/SwapPage/SwapPage.tsx @@ -16,10 +16,7 @@ import { SettingsDrawer } from '../../components/SettingsDrawer'; import { SwapButton } from '../../components/SwapButton'; import { SwapChainButton } from '../../components/SwapChainButton'; import { SwapInput } from '../../components/SwapInput'; -import { - SwapFromInputAdornment, - SwapToInputAdornment, -} from '../../components/SwapInputAdornment'; +import { SwapInputAdornment } from '../../components/SwapInputAdornment'; import { SwapStepper } from '../../components/SwapStepper'; import { Switch } from '../../components/Switch'; import { @@ -38,8 +35,8 @@ export const SwapPage: React.FC = ({ settingsRef }) => { } = useFormContext(); const drawerRef = useRef(null); - const handleChainButton = (type: SwapFormDirection) => - drawerRef.current?.openDrawer(type); + const handleChainButton = (formType: SwapFormDirection) => + drawerRef.current?.openDrawer(formType); useEffect(() => { register(SwapFormKey.FromToken); @@ -53,19 +50,14 @@ export const SwapPage: React.FC = ({ settingsRef }) => { {t(`swap.from`)} - + - } + endAdornment={} aria-describedby="" inputProps={{ min: 0, @@ -93,14 +85,14 @@ export const SwapPage: React.FC = ({ settingsRef }) => { - + } + endAdornment={} aria-describedby="" inputProps={{ min: 0, diff --git a/packages/widget/src/providers/SwapFormProvider/types.ts b/packages/widget/src/providers/SwapFormProvider/types.ts index 638b84092..721e56ca6 100644 --- a/packages/widget/src/providers/SwapFormProvider/types.ts +++ b/packages/widget/src/providers/SwapFormProvider/types.ts @@ -33,10 +33,10 @@ export enum SwapFormKey { export type SwapFormDirection = 'from' | 'to'; export const SwapFormKeyHelper = { - getChainKey: (type: SwapFormDirection) => `${type}Chain`, - getTokenKey: (type: SwapFormDirection) => `${type}Token`, - getSearchTokensFilterKey: (type: SwapFormDirection) => - `${type}SearchTokensFilter`, + getChainKey: (formType: SwapFormDirection) => `${formType}Chain`, + getTokenKey: (formType: SwapFormDirection) => `${formType}Token`, + getSearchTokensFilterKey: (formType: SwapFormDirection) => + `${formType}SearchTokensFilter`, }; export interface SwapFormTypeProps {