From 1bed599cc02821863ebd5fed4ebd7e3ced2e83b4 Mon Sep 17 00:00:00 2001 From: Yaroslav Grachev Date: Fri, 15 Nov 2024 16:43:40 +0300 Subject: [PATCH 1/2] fix: multisig select --- .../entities/wallet/lib/wallet-utils.ts | 12 +-- .../shared/ui-kit/Select/Select.stories.tsx | 48 +++++++++-- src/renderer/shared/ui-kit/Select/Select.tsx | 13 ++- .../widgets/CreateWallet/lib/types.ts | 4 +- .../model/__tests__/confirm-model.test.ts | 4 +- .../model/__tests__/flow-model.test.ts | 6 +- .../model/__tests__/form-model.test.ts | 6 +- .../CreateWallet/model/__tests__/mock.ts | 9 +- .../CreateWallet/model/confirm-model.ts | 3 +- .../widgets/CreateWallet/model/flow-model.ts | 75 ++++++++-------- .../widgets/CreateWallet/model/form-model.ts | 70 ++++++++++----- .../CreateWallet/model/signatory-model.ts | 1 + .../ui/MultisigWallet/ConfirmationStep.tsx | 58 +++++++------ .../ui/MultisigWallet/MultisigWallet.tsx | 4 +- .../MultisigWallet/NameNetworkSelection.tsx | 56 ++++++------ .../SelectSignatoriesThreshold.tsx | 33 ++++--- .../MultisigWallet/components/Signatory.tsx | 86 ++++++++++--------- .../components/SignerSelection.tsx | 19 ++-- .../ui/MultisigWallet/components/index.ts | 1 + 19 files changed, 287 insertions(+), 221 deletions(-) diff --git a/src/renderer/entities/wallet/lib/wallet-utils.ts b/src/renderer/entities/wallet/lib/wallet-utils.ts index a83f821d33..3e560f82fc 100644 --- a/src/renderer/entities/wallet/lib/wallet-utils.ts +++ b/src/renderer/entities/wallet/lib/wallet-utils.ts @@ -66,6 +66,7 @@ function isNovaWallet(wallet?: Wallet): wallet is NovaWalletWallet { function isWalletConnect(wallet?: Wallet): wallet is WalletConnectWallet { return wallet?.type === WalletType.WALLET_CONNECT; } + function isProxied(wallet?: Wallet): wallet is ProxiedWallet { return wallet?.type === WalletType.PROXIED; } @@ -87,6 +88,7 @@ const VALID_SIGNATORY_WALLET_TYPES = [ WalletType.WALLET_CONNECT, WalletType.NOVA_WALLET, ]; + function isValidSignatory(wallet?: Wallet): boolean { if (!wallet) return false; @@ -133,8 +135,8 @@ function getAccountBy(wallets: Wallet[], accountFn: (account: Account, wallet: W * object and its parent Wallet object and returns a boolean indicating * whether it should be included in the result. Defaults to undefined. * - * @returns {Wallet | undefined} - The matching Wallet object, or undefined if - * no matching Wallet is found. + * @returns {Wallet | null} - The matching Wallet object, or undefined if no + * matching Wallet is found. */ function getWalletFilteredAccounts( wallets: Wallet[], @@ -164,8 +166,8 @@ function getWalletsFilteredAccounts( walletFn?: (wallet: Wallet) => boolean; accountFn?: (account: Account, wallet: Wallet) => boolean; }, -): Wallet[] | undefined { - if (!predicates.walletFn && !predicates.accountFn) return undefined; +): Wallet[] | null { + if (!predicates.walletFn && !predicates.accountFn) return null; const result = wallets.reduce((acc, wallet) => { if (!predicates.walletFn || predicates.walletFn(wallet)) { @@ -181,5 +183,5 @@ function getWalletsFilteredAccounts( return acc; }, []); - return result.length > 0 ? result : undefined; + return result.length > 0 ? result : null; } diff --git a/src/renderer/shared/ui-kit/Select/Select.stories.tsx b/src/renderer/shared/ui-kit/Select/Select.stories.tsx index 6283f8fa9c..115c1c60d0 100644 --- a/src/renderer/shared/ui-kit/Select/Select.stories.tsx +++ b/src/renderer/shared/ui-kit/Select/Select.stories.tsx @@ -1,6 +1,7 @@ import { type Meta, type StoryObj } from '@storybook/react'; import { useState } from 'react'; +import { Icon } from '@/shared/ui'; import { Box } from '../Box/Box'; import { Select } from './Select'; @@ -17,12 +18,10 @@ const meta: Meta = { return ( ); @@ -35,6 +34,43 @@ type Story = StoryObj; export const Default: Story = {}; +export const RichContent: Story = { + render: (args) => { + const [value, onChange] = useState('item_4'); + + return ( + + + + ); + }, +}; + export const Invalid: Story = { args: { invalid: true, diff --git a/src/renderer/shared/ui-kit/Select/Select.tsx b/src/renderer/shared/ui-kit/Select/Select.tsx index a0ae4b7ff4..be10ec6c5f 100644 --- a/src/renderer/shared/ui-kit/Select/Select.tsx +++ b/src/renderer/shared/ui-kit/Select/Select.tsx @@ -47,7 +47,7 @@ const Root = ({
- + {asset ? ( + + ) : null} +
- + diff --git a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/Signatory.tsx b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/Signatory.tsx index 2123904d0d..2aff64a9a7 100644 --- a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/Signatory.tsx +++ b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/Signatory.tsx @@ -1,4 +1,3 @@ -import { useForm } from 'effector-forms'; import { useUnit } from 'effector-react'; import { useEffect, useMemo, useState } from 'react'; @@ -41,32 +40,32 @@ export const Signatory = ({ selectedWalletId, }: Props) => { const { t } = useI18n(); + const chain = useUnit(formModel.$chain); + const contacts = useUnit(contactModel.$contacts); + const wallets = useUnit(walletModel.$wallets); + const [query, setQuery] = useState(''); const [options, setOptions] = useState([]); - const contacts = useUnit(contactModel.$contacts); - const wallets = useUnit(walletModel.$wallets); - const { - fields: { chain }, - } = useForm(formModel.$createMultisigForm); - const contactsFiltered = useMemo( - () => - performSearch({ - query, - records: contacts, - weights: { - name: 1, - address: 0.5, - }, - }), - [query, contacts], - ); + const contactsFiltered = useMemo(() => { + return performSearch({ + query, + records: contacts, + weights: { name: 1, address: 0.5 }, + }); + }, [query, contacts]); const ownAccountName = walletUtils.getWalletsFilteredAccounts(wallets, { walletFn: (w) => walletUtils.isValidSignatory(w) && (!selectedWalletId || w.id.toString() === selectedWalletId), - accountFn: (a) => - toAccountId(signatoryAddress) === a.accountId && accountUtils.isChainIdMatch(a, chain.value.chainId), + accountFn: (a) => { + if (!chain) return false; + + const accountIdMatch = toAccountId(signatoryAddress) === a.accountId; + const chainIdMatch = accountUtils.isChainIdMatch(a, chain.chainId); + + return accountIdMatch && chainIdMatch; + }, })?.[0]?.name || ''; const contactAccountName = @@ -83,7 +82,7 @@ export const Signatory = ({ }, [isOwnAccount, ownAccountName, contactAccountName, name]); useEffect(() => { - if (!isOwnAccount || wallets.length === 0) return; + if (!isOwnAccount || wallets.length === 0 || !chain) return; const walletByGroup = walletSelectUtils.getWalletByGroups(wallets, query); const opts = Object.entries(walletByGroup).reduce((acc, [walletType, wallets], index) => { @@ -94,23 +93,24 @@ export const Signatory = ({ const accountOptions = wallets.reduce((acc, wallet) => { if (!wallet.accounts.length || !walletUtils.isValidSignatory(wallet)) return acc; - return acc.concat( - wallet.accounts - .filter( - (account) => - accountUtils.isChainAndCryptoMatch(account, chain.value) && - accountUtils.isNonBaseVaultAccount(account, wallet), - ) - .map((account) => { - const address = toAddress(account.accountId, { prefix: chain.value.addressPrefix }); - - return { - value: address, - element:
, - id: account.walletId.toString(), - }; - }), - ); + const accounts = wallet.accounts + .filter((account) => { + const isChainMatch = accountUtils.isChainAndCryptoMatch(account, chain); + const isCorrectAccount = accountUtils.isNonBaseVaultAccount(account, wallet); + + return isChainMatch && isCorrectAccount; + }) + .map((account) => { + const address = toAddress(account.accountId, { prefix: chain?.addressPrefix }); + + return { + id: account.walletId.toString(), + value: address, + element:
, + }; + }); + + return acc.concat(accounts); }, [] as ComboboxOption[]); if (accountOptions.length === 0) { @@ -150,7 +150,7 @@ export const Signatory = ({ setOptions( contactsFiltered.map(({ name, address }) => { - const displayAddress = toAddress(address, { prefix: chain.value.addressPrefix }); + const displayAddress = toAddress(address, { prefix: chain?.addressPrefix }); return { id: signatoryIndex.toString(), @@ -177,11 +177,13 @@ export const Signatory = ({ }, [displayName]); const onAddressChange = (data: ComboboxOption) => { - const isEthereumChain = networkUtils.isEthereumBased(chain.value.options); + if (!chain) return; + + const isEthereumChain = networkUtils.isEthereumBased(chain.options); const validateFn = isEthereumChain ? validateEthereumAddress : validateSubstrateAddress; const validatedAddress = validateFn(data.value) ? data.value : ''; - const fixedAddress = toAddress(validatedAddress, { prefix: chain.value.addressPrefix }); + const fixedAddress = toAddress(validatedAddress, { prefix: chain?.addressPrefix }); signatoryModel.events.changeSignatory({ index: signatoryIndex, @@ -226,7 +228,7 @@ export const Signatory = ({ placeholder={t('createMultisigAccount.signatorySelection')} options={options} query={query} - value={toAddress(signatoryAddress, { prefix: chain.value.addressPrefix })} + value={toAddress(signatoryAddress, { prefix: chain?.addressPrefix })} prefixElement={prefixElement} onChange={(data) => { onAddressChange(data); diff --git a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/SignerSelection.tsx b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/SignerSelection.tsx index 9c2f403b24..a4191d909f 100644 --- a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/SignerSelection.tsx +++ b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/SignerSelection.tsx @@ -14,11 +14,11 @@ import { Signer } from './Signer'; export const SignerSelection = () => { const { t } = useI18n(); + + const chain = useUnit(formModel.$chain); const ownedSignatoriesWallets = useUnit(signatoryModel.$ownedSignatoriesWallets) || []; - const { - fields: { chain }, - submit, - } = useForm(formModel.$createMultisigForm); + + const { submit } = useForm(formModel.$createMultisigForm); const onSubmit = (event: FormEvent, account: Account) => { flowModel.events.signerSelected(account); @@ -30,13 +30,14 @@ export const SignerSelection = () => {
    {ownedSignatoriesWallets.map(({ accounts, type, name }) => { + if (!chain) return null; + const account = accounts[0].type === AccountType.BASE ? accounts[0] - : accounts.find((account) => (account as ChainAccount).chainId === chain.value.chainId); - if (!account) { - return null; - } + : accounts.find((account) => (account as ChainAccount).chainId === chain.chainId); + + if (!account) return null; return ( { account={account} walletName={name} walletType={type} - chain={chain.value} + chain={chain} onSubmit={onSubmit} /> ); diff --git a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/index.ts b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/index.ts index b3e943a78d..ae746c1e9f 100644 --- a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/index.ts +++ b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/index.ts @@ -1,3 +1,4 @@ export { SelectSignatories } from './SelectSignatories'; export { WalletItem } from './WalletItem'; export { MultisigCreationFees } from './MultisigCreationFees'; +export { SelectedSignatoriesModal } from './SelectedSignatoriesModal'; From fec24d8af1843057dceaf179ed515fd05a308791 Mon Sep 17 00:00:00 2001 From: Yaroslav Grachev Date: Mon, 18 Nov 2024 11:44:11 +0300 Subject: [PATCH 2/2] fix: threshold new select --- src/renderer/shared/ui-kit/Select/Select.tsx | 4 +- .../MultisigWallet/NameNetworkSelection.tsx | 6 +- .../SelectSignatoriesThreshold.tsx | 55 +++++++++---------- .../components/SelectSignatories.tsx | 2 +- 4 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/renderer/shared/ui-kit/Select/Select.tsx b/src/renderer/shared/ui-kit/Select/Select.tsx index be10ec6c5f..d5c9fb3836 100644 --- a/src/renderer/shared/ui-kit/Select/Select.tsx +++ b/src/renderer/shared/ui-kit/Select/Select.tsx @@ -102,8 +102,8 @@ const Content = ({ children }: PropsWithChildren) => { elevation={1} className={cnTw( 'z-50 flex flex-col', - 'h-max max-h-[--radix-popper-available-height] max-w-60', - 'min-w-20 overflow-hidden duration-100 animate-in fade-in zoom-in-95', + 'h-max max-h-[--radix-popper-available-height] min-w-20', + 'overflow-hidden duration-100 animate-in fade-in zoom-in-95', { 'border-border-dark bg-background-dark': theme === 'dark', }, diff --git a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/NameNetworkSelection.tsx b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/NameNetworkSelection.tsx index 188573a6d0..66b5a8d49b 100644 --- a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/NameNetworkSelection.tsx +++ b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/NameNetworkSelection.tsx @@ -3,15 +3,17 @@ import { useUnit } from 'effector-react'; import { type ChainId } from '@/shared/core'; import { useI18n } from '@/shared/i18n'; +import { nonNullable } from '@/shared/lib/utils'; import { Button, FootnoteText, Input, InputHint, SmallTitleText } from '@/shared/ui'; import { Box, Field, Select } from '@/shared/ui-kit'; import { ChainTitle } from '@/entities/chain'; import { networkModel, networkUtils } from '@/entities/network'; -import { MultisigCreationFees } from '@/widgets/CreateWallet/ui/MultisigWallet/components/'; import { Step } from '../../lib/types'; import { flowModel } from '../../model/flow-model'; import { formModel } from '../../model/form-model'; +import { MultisigCreationFees } from './components'; + interface Props { onGoBack: () => void; } @@ -78,7 +80,7 @@ export const NameNetworkSelection = ({ onGoBack }: Props) => { {t('createMultisigAccount.backButton')}
    - {asset ? ( + {nonNullable(asset) ? ( ) : null} diff --git a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/SelectSignatoriesThreshold.tsx b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/SelectSignatoriesThreshold.tsx index 2b38792741..93251b26e5 100644 --- a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/SelectSignatoriesThreshold.tsx +++ b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/SelectSignatoriesThreshold.tsx @@ -1,10 +1,11 @@ import { useForm } from 'effector-forms'; import { useUnit } from 'effector-react'; -import { type FormEvent, useMemo, useState } from 'react'; +import { type FormEvent, useState } from 'react'; import { useI18n } from '@/shared/i18n'; -import { Alert, Button, InputHint, Select, SmallTitleText } from '@/shared/ui'; -import { type DropdownOption } from '@/shared/ui/types'; +import { nonNullable } from '@/shared/lib/utils'; +import { Alert, Button, InputHint, SmallTitleText } from '@/shared/ui'; +import { Box, Select } from '@/shared/ui-kit'; import { walletModel } from '@/entities/wallet'; import { Step } from '../../lib/types'; import { flowModel } from '../../model/flow-model'; @@ -14,19 +15,6 @@ import { signatoryModel } from '../../model/signatory-model'; import { MultisigCreationFees, SelectSignatories } from './components'; const MIN_THRESHOLD = 2; -const getThresholdOptions = (optionsAmount: number): DropdownOption[] => { - if (optionsAmount === 0) return []; - - return Array.from({ length: optionsAmount }, (_, index) => { - const value = index + 2; - - return { - id: value.toString(), - element: value, - value, - }; - }); -}; export const SelectSignatoriesThreshold = () => { const { t } = useI18n(); @@ -49,11 +37,12 @@ export const SelectSignatoriesThreshold = () => { const [hasClickedNext, setHasClickedNext] = useState(false); - const thresholdOptions = useMemo(() => getThresholdOptions(signatories.length - 1), [signatories.length]); - const hasOwnedSignatory = !!ownedSignatoriesWallets && ownedSignatoriesWallets?.length > 0; const hasEnoughSignatories = signatories.length >= MIN_THRESHOLD; const isThresholdValid = threshold.value >= MIN_THRESHOLD && threshold.value <= signatories.length; + + const asset = chain?.assets.at(0); + const canSubmit = hasOwnedSignatory && hasEnoughSignatories && @@ -115,17 +104,21 @@ export const SelectSignatoriesThreshold = () => {
    - threshold.onChange(Number(value))} + > + {Array.from({ length: signatories.length - 1 }, (_, index) => ( + + {index + 2} + + ))} + + {t('createMultisigAccount.thresholdHint')} @@ -182,7 +175,9 @@ export const SelectSignatoriesThreshold = () => { {t('createMultisigAccount.backButton')}
    - + {nonNullable(asset) ? ( + + ) : null} diff --git a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/SelectSignatories.tsx b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/SelectSignatories.tsx index 21b4b07d77..43ea30295c 100644 --- a/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/SelectSignatories.tsx +++ b/src/renderer/widgets/CreateWallet/ui/MultisigWallet/components/SelectSignatories.tsx @@ -20,7 +20,7 @@ export const SelectSignatories = () => {
    {signatories.map((value, index) => (