From e1bb3ff8ff5bdc9668c679f953d893a19055f33e Mon Sep 17 00:00:00 2001 From: S2kael Date: Sat, 21 Oct 2023 19:23:47 +0700 Subject: [PATCH 1/4] [Issue-1929] Update display balance DOT on earning screen --- .../Transaction/parts/FreeBalanceToYield.tsx | 274 ++++++++++++++++++ .../src/Popup/Transaction/parts/index.ts | 1 + .../Popup/Transaction/variants/Yield/Earn.tsx | 67 +++-- .../src/messaging/transaction/base.ts | 4 +- 4 files changed, 324 insertions(+), 22 deletions(-) create mode 100644 packages/extension-koni-ui/src/Popup/Transaction/parts/FreeBalanceToYield.tsx diff --git a/packages/extension-koni-ui/src/Popup/Transaction/parts/FreeBalanceToYield.tsx b/packages/extension-koni-ui/src/Popup/Transaction/parts/FreeBalanceToYield.tsx new file mode 100644 index 0000000000..f8919143f2 --- /dev/null +++ b/packages/extension-koni-ui/src/Popup/Transaction/parts/FreeBalanceToYield.tsx @@ -0,0 +1,274 @@ +// Copyright 2019-2022 @polkadot/extension-koni-ui authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { useGetBalance, useSelector } from '@subwallet/extension-koni-ui/hooks'; +import useTranslation from '@subwallet/extension-koni-ui/hooks/common/useTranslation'; +import { Theme, ThemeProps } from '@subwallet/extension-koni-ui/types'; +import { ActivityIndicator, Number, Typography } from '@subwallet/react-ui'; +import CN from 'classnames'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import styled, { useTheme } from 'styled-components'; + +interface BalanceInfo { + token: string; + chain: string; +} + +type Props = ThemeProps & { + address: string; + tokens: BalanceInfo[]; + label?: string; + onBalanceReady?: (rs: boolean) => void; +} + +interface PartProps { + token: string; + chain: string; + address: string; + setLoading: (val: boolean) => void; + setError: (val: string | null) => void; + showNetwork: boolean; + first: boolean; +} + +const parseToLoadingMap = (tokens: BalanceInfo[]): Record => { + const result: Record = {}; + + tokens.forEach(({ token }) => { + result[token] = true; + }); + + return result; +}; + +const parseToErrorMap = (tokens: BalanceInfo[]): Record => { + const result: Record = {}; + + tokens.forEach(({ token }) => { + result[token] = null; + }); + + return result; +}; + +const PartComponent: React.FC = (props: PartProps) => { + const { address, chain, first, setError, setLoading, showNetwork, token } = props; + + const { token: theme } = useTheme() as Theme; + const { t } = useTranslation(); + + const { chainInfoMap } = useSelector((state) => state.chainStore); + + const { error, isLoading, nativeTokenBalance, nativeTokenSlug, tokenBalance } = useGetBalance(chain, address, token, true); + + const balance = useMemo(() => { + if (token) { + if (nativeTokenSlug === token) { + return nativeTokenBalance; + } else { + return tokenBalance; + } + } + + return undefined; + }, [nativeTokenBalance, nativeTokenSlug, token, tokenBalance]); + + const suffix = useMemo(() => { + let result = balance?.symbol || ''; + + const chainInfo = chainInfoMap[chain]; + + if (showNetwork && chainInfo) { + result += ` (${chainInfo.name})`; + } + + return result; + }, [balance?.symbol, chain, chainInfoMap, showNetwork]); + + useEffect(() => { + setLoading(isLoading); + }, [isLoading, setLoading]); + + useEffect(() => { + setError(error); + }, [error, setError]); + + if (isLoading || error) { + return null; + } + + return ( + <> + { + !first && ( +  {t('and')}  + ) + } + { + balance && ( + + ) + } + + ); +}; + +const Component = (props: Props) => { + const { address, className, label, onBalanceReady, tokens } = props; + + const { t } = useTranslation(); + + const loadingRef = useRef>(parseToLoadingMap(tokens)); + const errorRef = useRef>(parseToErrorMap(tokens)); + + const showNetwork = useMemo(() => { + let temp = ''; + + for (const { chain } of tokens) { + if (temp) { + if (temp !== chain) { + return true; + } + } else { + temp = chain; + } + } + + return false; + }, [tokens]); + + const [isLoading, _setIsLoading] = useState(true); + const [error, _setError] = useState(null); + + const setLoading = useCallback((slug: string) => { + return (data: boolean) => { + loadingRef.current[slug] = data; + + let isLoading = false; + + for (const loading of Object.values(loadingRef.current)) { + if (loading) { + isLoading = true; + break; + } + } + + _setIsLoading(isLoading); + }; + }, []); + + const setError = useCallback((slug: string) => { + return (data: string | null) => { + errorRef.current[slug] = data; + + let error: string | null = null; + + for (const value of Object.values(errorRef.current)) { + if (value) { + error = value; + break; + } + } + + _setError(error); + }; + }, []); + + // const error = useMemo(() => noXcm ? originError : (originError || xcmError), [originError, xcmError, noXcm]); + // const isLoading = useMemo(() => noXcm ? originIsLoading : (originIsLoading || xcmIsLoading), [noXcm, originIsLoading, xcmIsLoading]); + // + // const renderBalance = useCallback(({ nativeTokenBalance, nativeTokenSlug, tokenBalance, tokenSlug }: BalanceData) => { + // return ( + // <> + // { + // !isLoading && !error && !!nativeTokenSlug && (tokenSlug === nativeTokenSlug) && ( + // + // ) + // } + // { + // !isLoading && !error && !!tokenSlug && (tokenSlug !== nativeTokenSlug) && ( + // + // ) + // } + // + // ); + // }, [error, isLoading, token.colorTextTertiary, noXcm]); + + useEffect(() => { + onBalanceReady?.(!isLoading && !error); + }, [error, isLoading, onBalanceReady]); + + if (!address && !tokens.length) { + return <>; + } + + return ( + + {!error && {label || t('Sender available balance:')}} + {isLoading && } + {error && {error}} + { + tokens.map(({ chain, token }, index) => { + return ( + + ); + }) + } + + ); +}; + +const FreeBalanceToYield = styled(Component)(({ theme: { token } }: Props) => { + return { + display: 'flex', + flexWrap: 'wrap', + color: token.colorTextTertiary, + + '.__label': { + marginRight: 3 + }, + + '.error-message': { + color: token.colorError + }, + + '&.ant-typography': { + marginBottom: 0 + } + }; +}); + +export default FreeBalanceToYield; diff --git a/packages/extension-koni-ui/src/Popup/Transaction/parts/index.ts b/packages/extension-koni-ui/src/Popup/Transaction/parts/index.ts index 663b7f2a26..c3c7d04e3c 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/parts/index.ts +++ b/packages/extension-koni-ui/src/Popup/Transaction/parts/index.ts @@ -4,6 +4,7 @@ export { default as BondedBalance } from './BondedBalance'; export { default as FreeBalance } from './FreeBalance'; export { default as FreeBalanceToStake } from './FreeBalanceToStake'; +export { default as FreeBalanceToYield } from './FreeBalanceToYield'; export { default as TransactionContent } from './TransactionContent'; export { default as TransactionFooter } from './TransactionFooter'; export { default as YieldOutlet } from './YieldOutlet'; diff --git a/packages/extension-koni-ui/src/Popup/Transaction/variants/Yield/Earn.tsx b/packages/extension-koni-ui/src/Popup/Transaction/variants/Yield/Earn.tsx index 838a92f71a..a6538cfd18 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/Yield/Earn.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/Yield/Earn.tsx @@ -1,7 +1,7 @@ // Copyright 2019-2022 @polkadot/extension-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { NominatorMetadata, OptimalYieldPathRequest, SubmitJoinNativeStaking, SubmitJoinNominationPool, ValidatorInfo, YieldAssetExpectedEarning, YieldCompoundingPeriod, YieldPoolInfo, YieldPoolType } from '@subwallet/extension-base/background/KoniTypes'; +import { NominatorMetadata, OptimalYieldPathRequest, SubmitJoinNativeStaking, SubmitJoinNominationPool, ValidatorInfo, YieldAssetExpectedEarning, YieldCompoundingPeriod, YieldPoolInfo, YieldPoolType, YieldStepType } from '@subwallet/extension-base/background/KoniTypes'; import { AccountJson } from '@subwallet/extension-base/background/types'; import { calculateReward } from '@subwallet/extension-base/koni/api/yield'; import { _getAssetDecimals, _getAssetSymbol, _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; @@ -30,7 +30,7 @@ import styled, { useTheme } from 'styled-components'; import { isEthereumAddress } from '@polkadot/util-crypto'; import { fetchEarningChainValidators, getJoinYieldParams, handleValidateYield, handleYieldStep } from '../../helper'; -import { FreeBalanceToStake, TransactionContent, YieldOutlet } from '../../parts'; +import { FreeBalance, FreeBalanceToYield, TransactionContent, YieldOutlet } from '../../parts'; interface Props extends ThemeProps { item: YieldPoolInfo; @@ -81,6 +81,7 @@ const Component = () => { const [submitString, setSubmitString] = useState(); const currentStep = processState.currentStep; + const stepType = processState.steps?.[currentStep]?.type; const [form] = Form.useForm(); @@ -88,14 +89,12 @@ const Component = () => { const currentFrom = useWatchTransaction('from', form, defaultData); const currentPoolInfo = useMemo(() => poolInfoMap[methodSlug], [methodSlug, poolInfoMap]); - const needDotBalance = useMemo(() => !['westend', 'polkadot'].includes(currentPoolInfo.chain), [currentPoolInfo.chain]); - - const [isDotBalanceReady, setIsDotBalanceReady] = useState(!needDotBalance); - const chainState = useFetchChainState(currentPoolInfo.chain); const chainNetworkPrefix = useGetChainPrefixBySlug(currentPoolInfo.chain); const preCheckAction = usePreCheckAction(currentFrom); + const hasXcm = !['westend', 'polkadot'].includes(currentPoolInfo.chain); + const extrinsicType = useMemo(() => getEarnExtrinsicType(methodSlug), [methodSlug]); const currentYieldPosition = useGetYieldPositionByAddressAndSlug(currentFrom, currentPoolInfo.slug); @@ -379,6 +378,28 @@ const Component = () => { return processState.currentStep === processState.steps.length - 1; }, [processState.currentStep, processState.steps.length]); + const balanceTokens = useMemo(() => { + const result: Array<{ chain: string, token: string }> = []; + + const chain = currentPoolInfo.chain; + + for (const inputAsset of currentPoolInfo.inputAssets) { + result.push({ + token: inputAsset, + chain: chain + }); + } + + if (hasXcm) { + result.push({ + token: dotPolkadotSlug, + chain: 'polkadot' + }); + } + + return result; + }, [currentPoolInfo.chain, currentPoolInfo.inputAssets, hasXcm]); + const onClick = useCallback(() => { setSubmitLoading(true); dispatchProcessState({ @@ -533,10 +554,6 @@ const Component = () => {
- {/* } size={'xs'}> */} - {/* {'DOT'} */} - {/* */} -
{ /> + + { - !['westend', 'polkadot'].includes(currentPoolInfo.chain) && ( - ) @@ -613,12 +637,11 @@ const Component = () => { key={name} style={{ display: 'flex', flexDirection: 'column' }} > - @@ -691,7 +714,7 @@ const Component = () => {