diff --git a/packages/extension-polkagate/src/components/PopupSignArea.tsx b/packages/extension-polkagate/src/components/PopupSignArea.tsx new file mode 100644 index 000000000..e35aba58a --- /dev/null +++ b/packages/extension-polkagate/src/components/PopupSignArea.tsx @@ -0,0 +1,452 @@ +// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +/* eslint-disable react/jsx-max-props-per-line */ + +import type { Header } from '@polkadot/types/interfaces'; +import type { AnyTuple } from '@polkadot/types/types'; +import type { HexString } from '@polkadot/util/types'; + +import { faSignature } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Close as CloseIcon } from '@mui/icons-material'; +import { Grid, Tooltip, Typography, useTheme } from '@mui/material'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@polkadot/api/types/submittable'; +import { AccountsStore } from '@polkadot/extension-base/stores'; +import { ISubmittableResult } from '@polkadot/types/types'; +import keyring from '@polkadot/ui-keyring'; +import { cryptoWaitReady } from '@polkadot/util-crypto'; + +import SelectProxyModal2 from '../fullscreen/governance/components/SelectProxyModal2'; +import { useAccountDisplay, useInfo, useProxies, useTranslation } from '../hooks'; +import Qr from '../popup/signing/Qr'; +import { CMD_MORTAL } from '../popup/signing/Request'; +import { send, signAndSend } from '../util/api'; +import { Proxy, ProxyItem, ProxyTypes, TxInfo, TxResult } from '../util/types'; +import { getSubstrateAddress, saveAsHistory } from '../util/utils'; +import { Identity, Password, PButton, Popup, Progress, TwoButtons, Warning } from '.'; +import { HeaderBrand } from '../partials'; + +interface Props { + address: string; + call: SubmittableExtrinsicFunction<'promise', AnyTuple> | undefined | SubmittableExtrinsic<'promise', ISubmittableResult>; + disabled?: boolean; + isPasswordError?: boolean; + onSecondaryClick: () => void; + params?: unknown[] | (() => unknown[]) | undefined; + primaryBtn?: boolean; + primaryBtnText?: string; + proxyModalHeight?: number | undefined + prevState?: Record; + proxyTypeFilter: ProxyTypes[] | string[]; + secondaryBtnText?: string; + selectedProxy: Proxy | undefined; + setSelectedProxy?: React.Dispatch>; + setIsPasswordError: React.Dispatch>; + step: number; + setStep: React.Dispatch>; + showBackButtonWithUseProxy?: boolean; + token?: string | undefined + setTxInfo: React.Dispatch>; + extraInfo: Record; + steps: Record; + setRefresh?: React.Dispatch>; + previousStep?: number; +} + +/** + * @description + * This puts usually at the end of review page where user can do enter password, + * choose proxy or use other alternatives like signing using ledger + * +*/ +export default function PopupSignArea({ address, call, disabled, extraInfo, isPasswordError, onSecondaryClick, params, prevState, previousStep, primaryBtn, primaryBtnText, proxyModalHeight, proxyTypeFilter, secondaryBtnText, selectedProxy, setIsPasswordError, setRefresh, setSelectedProxy, setStep, setTxInfo, showBackButtonWithUseProxy = true, steps, token }: Props): React.ReactElement { + const { t } = useTranslation(); + const theme = useTheme(); + const { account, api, chain, formatted } = useInfo(address); + const senderName = useAccountDisplay(address); + const selectedProxyName = useAccountDisplay(getSubstrateAddress(selectedProxy?.delegate)); + const proxies = useProxies(api, formatted); + + const [proxyItems, setProxyItems] = useState(); + const [password, setPassword] = useState(); + const [showProxy, setShowProxy] = useState(); + const [showQR, setShowQR] = useState(); + const [lastHeader, setLastHeader] = useState
(); + const [rawNonce, setRawNonce] = useState(); + + const from = selectedProxy?.delegate ?? formatted; + + const isLedger = useMemo(() => account?.isHardware, [account?.isHardware]); + const showUseProxy = useMemo(() => !account?.isHardware && !account?.isQR && account?.isExternal && !selectedProxy, [account, selectedProxy]); + const showQrSign = useMemo(() => account?.isQR, [account]); + + const alertText = useMemo(() => { + if (isLedger) { + return t('This is a ledger account. To complete this transaction, use your ledger.'); + } + + if (showQrSign) { + return t('This is a QR-attached account. To complete this transaction, you need to use your QR-signer.'); + } + + if (showUseProxy) { + return t('This is a watch-only account. To complete this transaction, you must use a proxy.'); + } + + return undefined; + }, [isLedger, showQrSign, showUseProxy, t]); + + useEffect(() => { + cryptoWaitReady().then(() => keyring.loadAll({ store: new AccountsStore() })).catch(() => null); + }, []); + + const ptx = useMemo((): SubmittableExtrinsic<'promise', ISubmittableResult> | undefined => { + if (!call || !api) { + return; + } + + const tx = (params ? call(...params) : call) as SubmittableExtrinsic<'promise', ISubmittableResult>; + + return selectedProxy ? api.tx.proxy.proxy(formatted, selectedProxy.proxyType, tx) : tx; + }, [api, call, formatted, params, selectedProxy]); + + const payload = useMemo(() => { + if (!api || !ptx || !lastHeader || !rawNonce) { + return; + } + + try { + const _payload = { + address: from, + blockHash: lastHeader.hash.toHex(), + blockNumber: api.registry.createType('BlockNumber', lastHeader.number.toNumber()).toHex(), + era: api.registry.createType('ExtrinsicEra', { current: lastHeader.number.toNumber(), period: 64 }).toHex(), + genesisHash: api.genesisHash.toHex(), + method: api.createType('Call', ptx).toHex(), // TODO: DOES SUPPORT nested calls, batches , ... + nonce: api.registry.createType('Compact', rawNonce).toHex(), + signedExtensions: [ + 'CheckNonZeroSender', + 'CheckSpecVersion', + 'CheckTxVersion', + 'CheckGenesis', + 'CheckMortality', + 'CheckNonce', + 'CheckWeight', + 'ChargeTransactionPayment' + ], + specVersion: api.runtimeVersion.specVersion.toHex(), + tip: api.registry.createType('Compact', 0).toHex(), + transactionVersion: api.runtimeVersion.transactionVersion.toHex(), + version: ptx.version + }; + + return api.registry.createType('ExtrinsicPayload', _payload, { version: _payload.version }); + } catch (error) { + console.error('Something went wrong when making payload:', error); + + return undefined; + } + }, [api, from, lastHeader, rawNonce, ptx]); + + const selectedProxyAddress = selectedProxy?.delegate as unknown as string; + + useEffect((): void => { + if (api && from) { + api.rpc.chain.getHeader().then(setLastHeader).catch(console.error); + api.query.system.account(from).then((res) => setRawNonce(res?.nonce || 0)).catch(console.error); + } + }, [api, formatted, from, selectedProxy]); + + useEffect((): void => { + const fetchedProxyItems = proxies?.map((p: Proxy) => ({ proxy: p, status: 'current' })) as ProxyItem[]; + + setProxyItems(fetchedProxyItems); + }, [proxies]); + + const _onChange = useCallback( + (pass: string): void => { + pass.length > 3 && pass && setPassword(pass); + pass.length > 3 && pass && setIsPasswordError && setIsPasswordError(false); + }, [setIsPasswordError] + ); + + const goToSelectProxy = useCallback(() => { + setShowProxy(true); + + setStep(steps.PROXY); + }, [setStep, steps]); + + const closeSelectProxy = useCallback(() => { + setShowProxy(false); + }, []); + + const goToQrScan = useCallback(() => { + setShowQR(true); + + setStep(steps.SIGN_QR || 200); // TODO: add to STEPS + }, [setStep, steps]); + + const closeQrModal = useCallback(() => { + setShowQR(false); + previousStep !== undefined && setStep(previousStep); + }, [previousStep, setStep]); + + const handleTxResult = useCallback((txResult: TxResult) => { + try { + if (!txResult || !api || !chain) { + return; + } + + setRefresh && setRefresh(true); + + const _token = token || api.registry.chainTokens[0]; + const decimal = api.registry.chainDecimals[0]; + + const info = { + block: txResult?.block || 0, + chain, + date: Date.now(), + decimal, + failureText: txResult?.failureText, + from: { address: String(formatted), name: senderName }, + success: txResult?.success, + throughProxy: selectedProxyAddress ? { address: selectedProxyAddress, name: selectedProxyName } : undefined, + token: _token, + txHash: txResult?.txHash || '', + ...extraInfo + }; + + setTxInfo({ ...info, api, chain }); + saveAsHistory(String(from), info); + setStep(steps.CONFIRM); + } catch (e) { + console.log('error:', e); + } + }, [api, chain, extraInfo, formatted, from, selectedProxyAddress, selectedProxyName, senderName, setRefresh, setStep, setTxInfo, steps, token]); + + const onConfirm = useCallback(async () => { + try { + if (!formatted || !api || !ptx || !from) { + return; + } + + const signer = keyring.getPair(from); + + signer.unlock(password); + setStep(steps.WAIT_SCREEN); + + const txResult = await signAndSend(api, ptx, signer, formatted); + + handleTxResult(txResult); + } catch (e) { + console.log('error:', e); + setIsPasswordError(true); + } + }, [api, formatted, from, handleTxResult, password, ptx, setIsPasswordError, setStep, steps]); + + const onSignature = useCallback(async ({ signature }: { signature: HexString }) => { + if (!api || !payload || !signature || !ptx || !from) { + return; + } + + setStep(steps.WAIT_SCREEN); + + const txResult = await send(from, api, ptx, payload, signature); + + handleTxResult(txResult); + }, [api, from, handleTxResult, payload, ptx, setStep, steps.WAIT_SCREEN]); + + const SignUsingProxy = () => ( + <> + div': { m: 0, p: 0 }, pt: '5px' }}> + + {alertText} + + + {showBackButtonWithUseProxy + ? + : + } + + ); + + const SignWithQR = () => ( + <> + div': { m: 0, p: 0 }, pt: '5px' }}> + + {alertText} + + + + + ); + + const SignUsingProxyPage = () => ( + + <> + div:last-child': { px: '15px' }, '> div:last-child button': { left: '6%' } }}> + + + + + + ); + + const SignWithQRPage = () => ( + + <> + + + div': { width: 'inherit' }, pt: '30px' }}> + {formatted && (account?.genesisHash || api?.genesisHash?.toHex()) && payload + ? + : + } + + + + + ); + + return ( + + {showQrSign + ? + : showUseProxy + ? + : <> + + + + + {(!!proxies?.length || prevState?.selectedProxyAddress) && + + {selectedProxy && + + } + + } + > + + {selectedProxy ? t('Update proxy') : t('Use proxy')} + + + } + + div': { m: 0, width: '100%' }, pt: '15px' }}> + + + + } + {showProxy && setSelectedProxy && + + } + {showQR && + + } + + ); +} diff --git a/packages/extension-polkagate/src/components/index.ts b/packages/extension-polkagate/src/components/index.ts index 238fa8a5a..e41de11eb 100644 --- a/packages/extension-polkagate/src/components/index.ts +++ b/packages/extension-polkagate/src/components/index.ts @@ -92,5 +92,6 @@ export { default as DisplayLogo } from './DisplayLogo'; export { default as FullScreenIcon } from './FullScreenIcon'; export { default as OptionalCopyButton } from './OptionalCopyButton'; export { default as Waiting } from './Waiting'; +export { default as PopupSignArea } from './PopupSignArea'; export * from './contexts'; diff --git a/packages/extension-polkagate/src/fullscreen/manageProxies/components/ProxyTableFL.tsx b/packages/extension-polkagate/src/fullscreen/manageProxies/components/ProxyTableFL.tsx index 7cfb12259..67cffe73e 100644 --- a/packages/extension-polkagate/src/fullscreen/manageProxies/components/ProxyTableFL.tsx +++ b/packages/extension-polkagate/src/fullscreen/manageProxies/components/ProxyTableFL.tsx @@ -129,7 +129,7 @@ export default function ProxyTableFL ({ api, chain, handleDelete, labelAlignment {proxyItems === undefined && } - {proxyItems === null && + {(proxyItems === null || proxyItems?.length === 0) && diff --git a/packages/extension-polkagate/src/popup/manageProxies/AddProxy.tsx b/packages/extension-polkagate/src/popup/manageProxies/AddProxy.tsx index 24c2e999f..ee61c54b1 100644 --- a/packages/extension-polkagate/src/popup/manageProxies/AddProxy.tsx +++ b/packages/extension-polkagate/src/popup/manageProxies/AddProxy.tsx @@ -17,16 +17,16 @@ import getAllAddresses from '../../util/getAllAddresses'; import { Proxy, ProxyItem } from '../../util/types'; import { sanitizeChainName } from '../../util/utils'; import ShowIdentity from './partials/ShowIdentity'; +import { STEPS } from '.'; interface Props { address: string; api: ApiPromise; - showAddProxy: boolean; - setShowAddProxy: React.Dispatch>; chain: Chain; proxyItems: ProxyItem[]; setProxyItems: React.Dispatch>; onChange: () => void; + setStep: React.Dispatch>; } interface DropdownOption { @@ -38,7 +38,7 @@ const isEqualProxy = (a: Proxy, b: Proxy) => { return a.delay === b.delay && a.delegate === b.delegate && a.proxyType === b.proxyType; }; -export default function AddProxy ({ address, api, chain, onChange, proxyItems, setProxyItems, setShowAddProxy, showAddProxy }: Props): React.ReactElement { +export default function AddProxy({ address, api, chain, onChange, proxyItems, setProxyItems, setStep }: Props): React.ReactElement { const { t } = useTranslation(); const { hierarchy } = useContext(AccountContext); const formatted = useFormatted(address); @@ -49,7 +49,7 @@ export default function AddProxy ({ address, api, chain, onChange, proxyItems, s const [realAddress, setRealAddress] = useState(); const [selectedProxyType, setSelectedProxyType] = useState('Any'); const [delay, setDelay] = useState(0); - const [addButtonDisabled, setAddButtonDisabled] = useState(true); + // const [addButtonDisabled, setAddButtonDisabled] = useState(true); const proxyAccountIdentity = useAccountInfo2(api, realAddress); @@ -66,40 +66,32 @@ export default function AddProxy ({ address, api, chain, onChange, proxyItems, s const allAddresses = getAllAddresses(hierarchy, true, true, chain.ss58Format, address); - const _addProxy = useCallback(() => { + const addButtonDisabled = useMemo(() => { + if (!realAddress || !selectedProxyType || myselfAsProxy || alreadyExisting) { + return true; + } else { + return false; + } + }, [alreadyExisting, myselfAsProxy, realAddress, selectedProxyType]); + + const addProxy = useCallback(() => { const proxy = { delay, delegate: realAddress, proxyType: selectedProxyType } as Proxy; setProxyItems([{ proxy, status: 'new' }, ...proxyItems]); - setShowAddProxy(!showAddProxy); + setStep(STEPS.INDEX); onChange(); - }, [delay, onChange, proxyItems, realAddress, selectedProxyType, setProxyItems, setShowAddProxy, showAddProxy]); + }, [delay, onChange, proxyItems, realAddress, selectedProxyType, setProxyItems, setStep]); - const _selectProxyType = useCallback((type: string | number): void => { + const selectProxyType = useCallback((type: string | number): void => { setSelectedProxyType(type as string); }, []); - const _selectDelay = useCallback((value: string): void => { + const selectDelay = useCallback((value: string): void => { const nDelay = value ? parseInt(value.replace(/\D+/g, ''), 10) : 0; setDelay(nDelay); }, []); - useEffect(() => { - if (!realAddress || !selectedProxyType || myselfAsProxy) { - setAddButtonDisabled(true); - - return; - } - - if (alreadyExisting) { - setAddButtonDisabled(true); - - return; - } - - setAddButtonDisabled(false); - }, [alreadyExisting, myselfAsProxy, realAddress, selectedProxyType]); - return ( <> @@ -123,7 +115,7 @@ export default function AddProxy ({ address, api, chain, onChange, proxyItems, s defaultValue={proxyTypeOptions[0].value} helperText={t('The permissions allowed for this proxy account')} label={t('Proxy type')} - onChange={_selectProxyType} + onChange={selectProxyType} options={proxyTypeOptions} value={selectedProxyType || proxyTypeOptions[0].value} /> @@ -133,7 +125,7 @@ export default function AddProxy ({ address, api, chain, onChange, proxyItems, s @@ -163,7 +155,7 @@ export default function AddProxy ({ address, api, chain, onChange, proxyItems, s } diff --git a/packages/extension-polkagate/src/popup/manageProxies/Review.tsx b/packages/extension-polkagate/src/popup/manageProxies/Review.tsx index 8515a59f1..d9e7d20b7 100644 --- a/packages/extension-polkagate/src/popup/manageProxies/Review.tsx +++ b/packages/extension-polkagate/src/popup/manageProxies/Review.tsx @@ -11,18 +11,17 @@ import React, { useCallback, useContext, useEffect, useMemo, useState } from 're import { ApiPromise } from '@polkadot/api'; import { Chain } from '@polkadot/extension-chains/types'; -import keyring from '@polkadot/ui-keyring'; import { BN, BN_ONE } from '@polkadot/util'; -import { ActionContext, CanPayErrorAlert, PasswordUseProxyConfirm, ProxyTable, ShowBalance, WrongPasswordAlert } from '../../components'; -import { useAccount, useAccountDisplay, useCanPayFeeAndDeposit } from '../../hooks'; +import { ActionContext, CanPayErrorAlert, PopupSignArea, ProxyTable, ShowBalance, WrongPasswordAlert } from '../../components'; +import { useAccountDisplay, useCanPayFeeAndDeposit, useFormatted } from '../../hooks'; import useTranslation from '../../hooks/useTranslation'; import { SubTitle, WaitScreen } from '../../partials'; import Confirmation from '../../partials/Confirmation'; -import { signAndSend } from '../../util/api'; import { Proxy, ProxyItem, TxInfo } from '../../util/types'; -import { getFormattedAddress, getSubstrateAddress, saveAsHistory } from '../../util/utils'; +import { getSubstrateAddress } from '../../util/utils'; import ManageProxiesTxDetail from './partials/ManageProxiesTxDetail'; +import { STEPS } from '.'; interface Props { address: string; @@ -31,57 +30,59 @@ interface Props { depositValue: BN; proxies: ProxyItem[]; depositToPay: BN | undefined; + step: number; + setStep: React.Dispatch>; } -export default function Review({ address, api, chain, depositToPay, depositValue, proxies }: Props): React.ReactElement { +export default function Review ({ address, api, chain, depositToPay, depositValue, proxies, setStep, step }: Props): React.ReactElement { const { t } = useTranslation(); - const name = useAccountDisplay(address); - const account = useAccount(address); const onAction = useContext(ActionContext); + const formatted = useFormatted(address); const [helperText, setHelperText] = useState(); - const [proxiesToChange, setProxiesToChange] = useState(); const [estimatedFee, setEstimatedFee] = useState(); const [txInfo, setTxInfo] = useState(); - const [password, setPassword] = useState(); const [isPasswordError, setIsPasswordError] = useState(false); - const [showWaitScreen, setShowWaitScreen] = useState(false); - const [showConfirmation, setShowConfirmation] = useState(false); const [selectedProxy, setSelectedProxy] = useState(); - const formatted = getFormattedAddress(address, undefined, chain.ss58Format); const selectedProxyAddress = selectedProxy?.delegate as unknown as string; const selectedProxyName = useAccountDisplay(getSubstrateAddress(selectedProxyAddress)); - const canPayFeeAndDeposit = useCanPayFeeAndDeposit(formatted?.toString(), selectedProxy?.delegate, estimatedFee, depositToPay); + const canPayFeeAndDeposit = useCanPayFeeAndDeposit(formatted, selectedProxy?.delegate, estimatedFee, depositToPay); const removeProxy = api.tx.proxy.removeProxy; /** (delegate, proxyType, delay) **/ const addProxy = api.tx.proxy.addProxy; /** (delegate, proxyType, delay) **/ const batchAll = api.tx.utility.batchAll; - const goToMyAccounts = useCallback(() => { - setShowConfirmation(false); - setShowWaitScreen(false); - onAction('/'); - }, [onAction]); + const proxiesToChange = useMemo(() => proxies.filter(({ status }) => ['remove', 'new'].includes(status)), [proxies]); - const calls = useMemo((): SubmittableExtrinsic<'promise'>[] => { - const temp: SubmittableExtrinsic<'promise'>[] = []; + const { call, params } = useMemo(() => { + if (proxiesToChange.length === 0 || !addProxy || !removeProxy || !batchAll) { + return { call: undefined, params: undefined }; + } - proxiesToChange?.forEach((item: ProxyItem) => { - const p = item.proxy; + if (proxiesToChange.length === 1) { + const { delay, delegate, proxyType } = proxiesToChange[0].proxy; - item.status === 'remove' && temp.push(removeProxy(p.delegate, p.proxyType, p.delay)); - item.status === 'new' && temp.push(addProxy(p.delegate, p.proxyType, p.delay)); - }); + return proxiesToChange[0].status === 'new' + ? { call: addProxy, params: [delegate, proxyType, delay] } + : { call: removeProxy, params: [delegate, proxyType, delay] }; + } - return temp; - }, [addProxy, proxiesToChange, removeProxy]); + const params: SubmittableExtrinsic<'promise'>[] = []; - const tx = useMemo(() => calls.length !== 0 && calls.length > 1 ? batchAll(calls) : calls[0], [batchAll, calls]); + proxiesToChange.forEach(({ proxy, status }) => { + const { delay, delegate, proxyType } = proxy; + + status === 'remove' && params.push(removeProxy(delegate, proxyType, delay)); + status === 'new' && params.push(addProxy(delegate, proxyType, delay)); + }); + + return { call: batchAll, params: [params] }; + }, [addProxy, batchAll, proxiesToChange, removeProxy]); useEffect(() => { - if (!tx) { + if (!call || !params || !formatted) { return; } @@ -90,148 +91,118 @@ export default function Review({ address, api, chain, depositToPay, depositValue } // eslint-disable-next-line no-void - void tx.paymentInfo(formatted).then((i) => setEstimatedFee(i?.partialFee)); - }, [api, formatted, tx]); - - const onNext = useCallback(async (): Promise => { - try { - const from = selectedProxy?.delegate ?? formatted; - const signer = keyring.getPair(from); - - signer.unlock(password); - setShowWaitScreen(true); - - const decidedTx = selectedProxy ? api.tx.proxy.proxy(formatted, selectedProxy.proxyType, tx) : tx; - - const { block, failureText, fee, success, txHash } = await signAndSend(api, decidedTx, signer, selectedProxy?.delegate ?? formatted); - - const info = { - action: 'Manage Proxy', - block: block || 0, - chain, - date: Date.now(), - failureText, - fee: fee || String(estimatedFee || 0), - from: { address: formatted, name }, - subAction: 'Add/Remove Proxy', - success, - throughProxy: selectedProxyAddress ? { address: selectedProxyAddress, name: selectedProxyName } : undefined, - txHash: txHash || '' - }; - - setTxInfo({ ...info, api, chain }); - saveAsHistory(from, info); - setShowWaitScreen(false); - setShowConfirmation(true); - } catch (e) { - console.log('error:', e); - setIsPasswordError(true); - } - }, [api, chain, estimatedFee, formatted, name, password, selectedProxy, selectedProxyAddress, selectedProxyName, tx]); + void call(...params).paymentInfo(formatted).then((i) => setEstimatedFee(i?.partialFee)); + }, [api, call, formatted, params]); useEffect(() => { const addingLength = proxies.filter((item) => item.status === 'new').length; const removingLength = proxies.filter((item) => item.status === 'remove').length; - addingLength && setHelperText(t('You are adding {{addingLength}} Prox{{iesOrY}}', { replace: { addingLength, iesOrY: addingLength > 1 ? 'ies' : 'y' } })); - removingLength && setHelperText(t('You are removing {{removingLength}} Prox{{iesOrY}}', { replace: { iesOrY: removingLength > 1 ? 'ies' : 'y', removingLength } })); - addingLength && removingLength && setHelperText(t('Adding {{addingLength}} and removing {{removingLength}} Proxies', { replace: { addingLength, removingLength } })); + addingLength && setHelperText(t('You are adding {{addingLength}} Prox{{iesOrY}}', { replace: { addingLength, iesOrY: addingLength > 1 ? 'ies' : 'y' } })); + removingLength && setHelperText(t('You are removing {{removingLength}} Prox{{iesOrY}}', { replace: { iesOrY: removingLength > 1 ? 'ies' : 'y', removingLength } })); + addingLength && removingLength && setHelperText(t('Adding {{addingLength}} and removing {{removingLength}} Proxies', { replace: { addingLength, removingLength } })); }, [proxies, t]); - useEffect(() => { - const toChange = proxies.filter((item) => item.status === 'remove' || item.status === 'new'); + const extraInfo = useMemo(() => ({ + action: 'Manage Proxy', + fee: String(estimatedFee || 0), + subAction: 'Add/Remove Proxy' + }), [estimatedFee]); - setProxiesToChange(toChange); - }, [proxies]); + const onBackClick = useCallback(() => { + setStep(STEPS.INDEX); + }, [setStep]); + + const goToMyAccounts = useCallback(() => { + onAction('/'); + }, [onAction]); return ( <> - {isPasswordError && - - } - {canPayFeeAndDeposit.isAbleToPay === false && - - } - - ('Review')} /> - - - {helperText} - - ('Proxies')} - maxHeight={window.innerHeight / 3} - mode='Status' - proxies={proxiesToChange} - style={{ - m: '20px auto 10px', - width: '92%' - }} - /> - - - - {t('Deposit:')} - - - + {[STEPS.REVIEW, STEPS.PROXY, STEPS.SIGN_QR].includes(step) && + <> + {isPasswordError && + + } + {canPayFeeAndDeposit.isAbleToPay === false && + + } + + - - - - - {t('Fee:')} + + {helperText} - - + + + + + {t('Deposit')}: + + + + + + + + + {t('Fee')}: + + + + + - - - ('Password for {{name}}', { replace: { name: selectedProxyName || name || '' } })} - onChange={setPassword} - onConfirmClick={onNext} - proxiedAddress={address} - proxies={proxies} - proxyTypeFilter={['Any', 'NonTransfer']} - selectedProxy={selectedProxy} - setIsPasswordError={setIsPasswordError} - setSelectedProxy={setSelectedProxy} - style={{ - bottom: '80px', - left: '4%', - position: 'absolute', - width: '92%' - }} - /> - { + + } + {step === STEPS.WAIT_SCREEN && } - {txInfo && + {step === STEPS.CONFIRM && txInfo && ('Manage Proxies')} + headerTitle={t('Manage Proxies')} onPrimaryBtnClick={goToMyAccounts} - primaryBtnText={t('My accounts')} - showConfirmation={showConfirmation} + primaryBtnText={t('My accounts')} + showConfirmation txInfo={txInfo} > (); - const { account, api, chain } = useInfo(address); + const { account, api, chain, formatted } = useInfo(address); const [proxyItems, setProxyItems] = useState(); - const [showAddProxy, setShowAddProxy] = useState(false); - const [showReviewProxy, setShowReviewProxy] = useState(false); - const [formatted, setFormatted] = useState(); const [helperText, setHelperText] = useState(); const [depositValue, setDepositValue] = useState(); const [disableAddProxyButton, setEnableAddProxyButton] = useState(true); const [disableToConfirmButton, setEnableToConfirmButton] = useState(true); const [available, setAvailable] = useState(0); + const [step, setStep] = useState(STEPS.INDEX); const proxyDepositBase = api ? api.consts.proxy.proxyDepositBase as unknown as BN : BN_ZERO; const proxyDepositFactor = api ? api.consts.proxy.proxyDepositFactor as unknown as BN : BN_ZERO; @@ -42,15 +49,15 @@ export default function ManageProxies(): React.ReactElement { return BN_ZERO; } - const alreadyHasProxy = proxyItems.filter((proxyItem) => proxyItem.status === 'current'); + const currentProxies = proxyItems.filter((proxyItem) => proxyItem.status === 'current'); const newProxiesLength = proxyItems.filter((proxyItem) => proxyItem.status === 'new').length; const removeProxiesLength = proxyItems.filter((proxyItem) => proxyItem.status === 'remove').length; - if (alreadyHasProxy.length === 0 && removeProxiesLength === 0) { + if (currentProxies.length === 0 && removeProxiesLength === 0) { return depositValue; } - const alreadyHasProxyDeposit = (proxyDepositFactor.muln((alreadyHasProxy.length + removeProxiesLength))).add(proxyDepositBase); + const alreadyHasProxyDeposit = (proxyDepositFactor.muln((currentProxies.length + removeProxiesLength))).add(proxyDepositBase); if (newProxiesLength > removeProxiesLength) { return alreadyHasProxyDeposit.add(proxyDepositFactor.muln(newProxiesLength - removeProxiesLength)); @@ -60,16 +67,20 @@ export default function ManageProxies(): React.ReactElement { }, [depositValue, proxyDepositBase, proxyDepositFactor, proxyItems]); const onBackClick = useCallback(() => { - showReviewProxy ? setShowReviewProxy(!showReviewProxy) : onAction('/'); - }, [onAction, showReviewProxy]); + if ([STEPS.ADD_PROXY, STEPS.REVIEW].includes(step)) { + setStep(STEPS.INDEX); + } else { + onAction('/'); + } + }, [onAction, step]); const openAddProxy = useCallback(() => { - !disableAddProxyButton && setShowAddProxy(!showAddProxy); - }, [disableAddProxyButton, showAddProxy]); + !disableAddProxyButton && setStep(STEPS.ADD_PROXY); + }, [disableAddProxyButton]); - const toConfirm = useCallback(() => { - setShowReviewProxy(!showReviewProxy); - }, [showReviewProxy]); + const toReview = useCallback(() => { + setStep(STEPS.REVIEW); + }, []); const checkForChanges = useCallback(() => { if (!disableAddProxyButton) { @@ -79,7 +90,7 @@ export default function ManageProxies(): React.ReactElement { anyChanges && setEnableToConfirmButton(true); } - setAvailable(proxyItems?.filter((item) => item.status !== 'remove')?.length || 0); + setAvailable(proxyItems?.filter(({ status }) => status !== 'remove')?.length ?? 0); }, [disableAddProxyButton, proxyItems]); const onSelect = useCallback((selected: Proxy) => { @@ -123,10 +134,9 @@ export default function ManageProxies(): React.ReactElement { }, [checkForChanges, proxyItems]); useEffect(() => { - chain && setFormatted(getFormattedAddress(address, undefined, chain.ss58Format)); !available ? setDepositValue(BN_ZERO) : setDepositValue(proxyDepositBase.add(proxyDepositFactor.muln(available))) as unknown as BN; checkForChanges(); - }, [address, api, available, chain, checkForChanges, formatted, proxyDepositBase, proxyDepositFactor, proxyItems]); + }, [address, api, available, chain, checkForChanges, proxyDepositBase, proxyDepositFactor, proxyItems]); useEffect(() => { proxyItems !== undefined && setEnableAddProxyButton(false); @@ -150,12 +160,12 @@ export default function ManageProxies(): React.ReactElement { return ( <> - {!showAddProxy && !showReviewProxy && + {step === STEPS.INDEX && <> {helperText} @@ -194,7 +204,7 @@ export default function ManageProxies(): React.ReactElement { fontWeight={300} lineHeight='23px' > - {t('Deposit:')} + {t('Deposit')}: } - {showAddProxy && !showReviewProxy && + {step === STEPS.ADD_PROXY && proxyItems && api && } - {showReviewProxy && + {[STEPS.PROXY, STEPS.SIGN_QR, STEPS.REVIEW, STEPS.WAIT_SCREEN, STEPS.CONFIRM].includes(step) && api && depositValue && proxyItems && }