From c8787a621d054413b2456a933ea81ede76f739be Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Mon, 12 Feb 2024 16:43:10 +0100 Subject: [PATCH] Factor validator selection --- .../components/forms/ToggleCheckbox.tsx | 2 +- packages/ui/src/common/model/Polyfill.ts | 11 ++ .../components/SelectValidatorAccounts.tsx | 187 ++++++++++++++++++ .../BuyMembershipFormModal.tsx | 155 ++------------- .../BuyMembershipSignModal.tsx | 7 +- .../modals/BuyMembershipModal/machine.ts | 4 +- .../UpdateMembershipFormModal.tsx | 170 ++++------------ .../modals/UpdateMembershipModal/types.ts | 1 - 8 files changed, 255 insertions(+), 282 deletions(-) create mode 100644 packages/ui/src/common/model/Polyfill.ts create mode 100644 packages/ui/src/memberships/components/SelectValidatorAccounts.tsx diff --git a/packages/ui/src/common/components/forms/ToggleCheckbox.tsx b/packages/ui/src/common/components/forms/ToggleCheckbox.tsx index bd9030e2ea..37401c3fb3 100644 --- a/packages/ui/src/common/components/forms/ToggleCheckbox.tsx +++ b/packages/ui/src/common/components/forms/ToggleCheckbox.tsx @@ -20,7 +20,7 @@ export interface Props { onBlur?: any } -function BaseToggleCheckbox({ +export function BaseToggleCheckbox({ id, isRequired, disabled, diff --git a/packages/ui/src/common/model/Polyfill.ts b/packages/ui/src/common/model/Polyfill.ts new file mode 100644 index 0000000000..b6706f6342 --- /dev/null +++ b/packages/ui/src/common/model/Polyfill.ts @@ -0,0 +1,11 @@ +import { isDefined } from '../utils' + +export const toSpliced = (array: T[], start: number, deleteCount?: number, ...items: T[]): T[] => { + const hasDeleteCount = isDefined(deleteCount) + + if ('toSpliced' in Array.prototype) { + return hasDeleteCount ? (array as any).toSpliced(start, deleteCount, ...items) : (array as any).toSpliced(start) + } + + return [...array.slice(0, start), ...items, ...(hasDeleteCount ? array.slice(start + deleteCount) : [])] +} diff --git a/packages/ui/src/memberships/components/SelectValidatorAccounts.tsx b/packages/ui/src/memberships/components/SelectValidatorAccounts.tsx new file mode 100644 index 0000000000..2e4e8e56dc --- /dev/null +++ b/packages/ui/src/memberships/components/SelectValidatorAccounts.tsx @@ -0,0 +1,187 @@ +import React, { useCallback, useEffect, useMemo, useReducer } from 'react' +import styled from 'styled-components' + +import { SelectAccount } from '@/accounts/components/SelectAccount' +import { Account } from '@/accounts/types' +import { ButtonGhost, ButtonPrimary } from '@/common/components/buttons' +import { BaseToggleCheckbox, InputComponent, Label } from '@/common/components/forms' +import { CrossIcon, PlusIcon } from '@/common/components/icons' +import { AlertSymbol } from '@/common/components/icons/symbols' +import { Row, RowInline } from '@/common/components/Modal' +import { Tooltip, TooltipDefault } from '@/common/components/Tooltip' +import { TextMedium, TextSmall } from '@/common/components/typography' +import { toSpliced } from '@/common/model/Polyfill' +import { useValidators } from '@/validators/hooks/useValidators' + +type SelectValidatorAccountsState = { + isValidator: boolean + accounts: (Account | undefined)[] +} + +type Action = + | { type: 'SetInitialAccounts'; value: Account[] } + | { type: 'ToggleIsValidator'; value: boolean } + | { type: 'AddAccount'; value: { index: number; account?: Account } } + | { type: 'RemoveAccount'; value: { index: number } } + +const reducer = (state: SelectValidatorAccountsState, action: Action): SelectValidatorAccountsState => { + switch (action.type) { + case 'SetInitialAccounts': { + return { isValidator: true, accounts: action.value } + } + case 'ToggleIsValidator': { + return { ...state, isValidator: action.value } + } + case 'AddAccount': { + const { index, account } = action.value + return { ...state, accounts: toSpliced(state.accounts, index, 1, account) } + } + case 'RemoveAccount': { + const { index } = action.value + return { ...state, accounts: toSpliced(state.accounts, index, 1) } + } + } +} + +type UseSelectValidatorAccounts = { + isValidatorAccount: (account: Account) => boolean + initialValidatorAccounts: Account[] + state: SelectValidatorAccountsState + onChange: (action: Action) => void +} +export const useSelectValidatorAccounts = (boundAccounts: Account[] = []): UseSelectValidatorAccounts => { + const [state, dispatch] = useReducer(reducer, { isValidator: false, accounts: [] }) + + const validators = useValidators({ skip: !state.isValidator && boundAccounts.length === 0 }) + const validatorAddresses = useMemo( + () => validators?.flatMap(({ stashAccount: stash, controllerAccount: ctrl }) => (ctrl ? [stash, ctrl] : [stash])), + [validators] + ) + + const isValidatorAccount = useCallback( + (account: Account) => !!validatorAddresses?.includes(account.address), + [validatorAddresses] + ) + + const initialValidatorAccounts = useMemo( + () => boundAccounts.filter(isValidatorAccount), + [boundAccounts, validatorAddresses] + ) + + useEffect(() => { + if (initialValidatorAccounts.length > 0) { + dispatch({ type: 'SetInitialAccounts', value: initialValidatorAccounts }) + } + }, [initialValidatorAccounts]) + + return { initialValidatorAccounts, state, isValidatorAccount, onChange: dispatch } +} + +export const SelectValidatorAccounts = ({ isValidatorAccount, state, onChange }: UseSelectValidatorAccounts) => { + const handleIsValidatorChange = (value: boolean) => onChange({ type: 'ToggleIsValidator', value }) + + const AddAccount = (index: number, account: Account | undefined) => + onChange({ type: 'AddAccount', value: { index, account } }) + const RemoveAccount = (index: number) => onChange({ type: 'RemoveAccount', value: { index } }) + + const validatorAccountSelectorFilter = (index: number, account: Account) => + toSpliced(state.accounts, index, 1).every( + (accountOrUndefined) => accountOrUndefined?.address !== account.address + ) && isValidatorAccount(account) + + return ( + <> + + + + + + {state.isValidator && ( + <> + + + + + + + * + + + If your validator account is not in your signer wallet, paste the account address to the field below: + + {state.accounts.map((account, index) => ( + + + + AddAccount(index, account)} + filter={(account) => validatorAccountSelectorFilter(index, account)} + /> + + { + RemoveAccount(index) + }} + > + + + + {account && !isValidatorAccount(account) && ( + + + + + + + + This account is neither a validator controller account nor a validator stash account. + + + )} + + ))} + + AddAccount(state.accounts.length, undefined)} + > + Add Validator Account + + + + + )} + + ) +} + +const SelectValidatorAccountWrapper = styled.div` + margin-top: -4px; + display: flex; + flex-direction: column; + gap: 8px; +` + +const InputNotificationIcon = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 12px; + height: 12px; + color: inherit; + padding-right: 2px; + + .blackPart, + .primaryPart { + fill: currentColor; + } +` diff --git a/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipFormModal.tsx b/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipFormModal.tsx index e81e54d2bb..498e80d1a4 100644 --- a/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipFormModal.tsx +++ b/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipFormModal.tsx @@ -1,8 +1,8 @@ import HCaptcha from '@hcaptcha/react-hcaptcha' import { BalanceOf } from '@polkadot/types/interfaces/runtime' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { uniqBy } from 'lodash' +import React, { useEffect, useState } from 'react' import { FormProvider, useForm } from 'react-hook-form' -import styled from 'styled-components' import * as Yup from 'yup' import { SelectAccount } from '@/accounts/components/SelectAccount' @@ -21,15 +21,13 @@ import { LabelLink, ToggleCheckbox, } from '@/common/components/forms' -import { Arrow, CrossIcon, PlusIcon } from '@/common/components/icons' -import { AlertSymbol } from '@/common/components/icons/symbols' +import { Arrow } from '@/common/components/icons' import { Loading } from '@/common/components/Loading' import { ModalFooter, ModalFooterGroup, ModalHeader, Row, - RowInline, ScrolledModal, ScrolledModalBody, ScrolledModalContainer, @@ -37,14 +35,14 @@ import { } from '@/common/components/Modal' import { Tooltip, TooltipDefault } from '@/common/components/Tooltip' import { TransactionInfo } from '@/common/components/TransactionInfo' -import { TextMedium, TextSmall } from '@/common/components/typography' +import { TextMedium } from '@/common/components/typography' import { definedValues } from '@/common/utils' import { useYupValidationResolver } from '@/common/utils/validation' import { AvatarInput } from '@/memberships/components/AvatarInput' +import { SelectValidatorAccounts, useSelectValidatorAccounts } from '@/memberships/components/SelectValidatorAccounts' import { SocialMediaSelector } from '@/memberships/components/SocialMediaSelector/SocialMediaSelector' import { useUploadAvatarAndSubmit } from '@/memberships/hooks/useUploadAvatarAndSubmit' import { useGetMembersCountQuery } from '@/memberships/queries' -import { useValidators } from '@/validators/hooks/useValidators' import { SelectMember } from '../../components/SelectMember' import { @@ -80,7 +78,6 @@ const CreateMemberSchema = Yup.object().shape({ ), hasTerms: Yup.boolean().required().oneOf([true]), isReferred: Yup.boolean(), - isValidator: Yup.boolean(), referrer: ReferrerSchema, externalResources: ExternalResourcesSchema, }) @@ -93,7 +90,6 @@ export interface MemberFormFields { about: string avatarUri: File | string | null isReferred?: boolean - isValidator?: boolean validatorAccounts?: Account[] referrer?: Member hasTerms?: boolean @@ -108,7 +104,6 @@ const formDefaultValues = { about: '', avatarUri: null, isReferred: false, - isValidator: false, validatorAccounts: [], referrer: undefined, hasTerms: false, @@ -144,25 +139,13 @@ export const BuyMembershipForm = ({ }, }) - const [handle, isReferred, isValidator, referrer, captchaToken] = form.watch([ - 'handle', - 'isReferred', - 'isValidator', - 'referrer', - 'captchaToken', - ]) - const [validatorAccounts, setValidatorAccounts] = useState<(Account | undefined)[]>([undefined]) + const [handle, isReferred, referrer, captchaToken] = form.watch(['handle', 'isReferred', 'referrer', 'captchaToken']) - const validators = useValidators({ skip: !isValidator ?? true }) - const validatorAddresses = useMemo( - () => validators?.flatMap(({ stashAccount: stash, controllerAccount: ctrl }) => (ctrl ? [stash, ctrl] : [stash])), - [validators] - ) - - const isValidValidatorAccount = useCallback( - (account: Account | undefined) => !account || validatorAddresses?.includes(account.address), - [validatorAddresses] - ) + const selectValidatorAccounts = useSelectValidatorAccounts() + const { + isValidatorAccount, + state: { isValidator, accounts: validatorAccounts }, + } = selectValidatorAccounts useEffect(() => { if (handle) { @@ -180,31 +163,14 @@ export const BuyMembershipForm = ({ !isUploading && form.formState.isValid && (!isValidator || - (validatorAccounts?.filter((account) => !account || !isValidValidatorAccount(account)).length === 0 && - validatorAccounts?.filter((account) => !!account).length)) + (validatorAccounts.length > 0 && validatorAccounts.every((account) => account && isValidatorAccount(account)))) + const isDisabled = type === 'onBoarding' && process.env.REACT_APP_CAPTCHA_SITE_KEY ? !captchaToken || !isFormValid : !isFormValid - const addValidatorAccount = (index: number, value: Account | undefined) => { - setValidatorAccounts((accounts) => accounts.toSpliced(index, 1, value)) - } - - const removeValidatorAccount = (index: number) => { - validatorAccounts && setValidatorAccounts((accounts) => accounts.toSpliced(index, 1)) - } - - const validatorAccountSelectorFilter = (index: number, account: Account) => - (!validatorAccounts || - ![...validatorAccounts.toSpliced(index, 1)].find( - (accountOrUndefined) => accountOrUndefined?.address === account.address - )) && - !!validatorAddresses?.includes(account.address) - const submit = () => { - const accounts = (validatorAccounts.filter((account) => !!account) as Account[]).filter((account, index, self) => - self.slice(index + 1).every(({ address }) => address !== account.address) - ) - form.setValue('validatorAccounts' as keyof MemberFormFields, accounts) + const accounts = uniqBy(validatorAccounts as Account[], 'address') + form.setValue('validatorAccounts', accounts) const values = form.getValues() uploadAvatarAndSubmit({ ...values, externalResources: { ...definedValues(values.externalResources) } }) } @@ -290,74 +256,7 @@ export const BuyMembershipForm = ({ - {type === 'general' && ( - <> - - - - - {isValidator && ( - <> - - - - - - - * - - - If your validator account is not in your signer wallet, paste the account address to the field - below: - - {validatorAccounts?.map((account, index) => ( - - - - addValidatorAccount(index, account)} - filter={(account) => validatorAccountSelectorFilter(index, account)} - /> - - { - removeValidatorAccount(index) - }} - > - - - - {!isValidValidatorAccount(account) && ( - - - - - - - - This account is neither a validator controller account nor a validator stash account. - - - )} - - ))} - - addValidatorAccount(validatorAccounts.length, undefined)} - > - Add Validator Account - - - - - )} - - )} + {type === 'general' && } {process.env.REACT_APP_CAPTCHA_SITE_KEY && type === 'onBoarding' && ( @@ -426,25 +325,3 @@ export const BuyMembershipFormModal = ({ onClose, onSubmit, membershipPrice }: B ) } - -export const SelectValidatorAccountWrapper = styled.div` - margin-top: -4px; - display: flex; - flex-direction: column; - gap: 8px; -` - -export const InputNotificationIcon = styled.div` - display: flex; - justify-content: center; - align-items: center; - width: 12px; - height: 12px; - color: inherit; - padding-right: 2px; - - .blackPart, - .primaryPart { - fill: currentColor; - } -` diff --git a/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipSignModal.tsx b/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipSignModal.tsx index 86577ba0ef..3cd2aa8591 100644 --- a/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipSignModal.tsx +++ b/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipSignModal.tsx @@ -68,13 +68,14 @@ export const BuyMembershipSignModal = ({ } }, [!balance, !membershipPrice, !paymentInfo]) + const shouldBindValidatorAccounts = formData.validatorAccounts?.length const signDisabled = !isReady || !hasFunds || !validationInfo return ( - {formData.isValidator + {shouldBindValidatorAccounts ? 'You intend to create a validator membership.' : 'You intend to create a new membership.'} @@ -129,7 +130,7 @@ export const BuyMembershipSignModal = ({ transactionFee={paymentInfo?.partialFee.toBn()} next={{ disabled: signDisabled, - label: formData.isValidator ? 'Create membership' : 'Sign and create a member', + label: shouldBindValidatorAccounts ? 'Create membership' : 'Sign and create a member', onClick: sign, }} > diff --git a/packages/ui/src/memberships/modals/BuyMembershipModal/machine.ts b/packages/ui/src/memberships/modals/BuyMembershipModal/machine.ts index 44eae36580..814951875c 100644 --- a/packages/ui/src/memberships/modals/BuyMembershipModal/machine.ts +++ b/packages/ui/src/memberships/modals/BuyMembershipModal/machine.ts @@ -64,14 +64,14 @@ export const buyMembershipMachine = createMachine getDataFromEvent(event.data.events, 'members', 'MembershipBought', 0), }), - cond: (context, event) => isTransactionSuccess(context, event) && !!context.form?.isValidator, + cond: (context, event) => isTransactionSuccess(context, event) && !!context.form?.validatorAccounts?.length, }, { target: 'success', actions: assign({ memberId: (context, event) => getDataFromEvent(event.data.events, 'members', 'MembershipBought', 0), }), - cond: (context, event) => isTransactionSuccess(context, event) && !context.form?.isValidator, + cond: (context, event) => isTransactionSuccess(context, event) && !context.form?.validatorAccounts?.length, }, { target: 'error', diff --git a/packages/ui/src/memberships/modals/UpdateMembershipModal/UpdateMembershipFormModal.tsx b/packages/ui/src/memberships/modals/UpdateMembershipModal/UpdateMembershipFormModal.tsx index 3e1033fdbe..7ea5687445 100644 --- a/packages/ui/src/memberships/modals/UpdateMembershipModal/UpdateMembershipFormModal.tsx +++ b/packages/ui/src/memberships/modals/UpdateMembershipModal/UpdateMembershipFormModal.tsx @@ -1,3 +1,4 @@ +import { difference } from 'lodash' import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useForm, FormProvider } from 'react-hook-form' import * as Yup from 'yup' @@ -6,41 +7,37 @@ import { AnySchema } from 'yup' import { filterAccount, SelectAccount } from '@/accounts/components/SelectAccount' import { useMyAccounts } from '@/accounts/hooks/useMyAccounts' import { accountOrNamed } from '@/accounts/model/accountOrNamed' -import { encodeAddress } from '@/accounts/model/encodeAddress' import { Account } from '@/accounts/types' -import { ButtonGhost, ButtonPrimary } from '@/common/components/buttons' -import { InputComponent, InputText, InputTextarea, Label, ToggleCheckbox } from '@/common/components/forms' -import { CrossIcon, PlusIcon } from '@/common/components/icons' -import { AlertSymbol } from '@/common/components/icons/symbols' +import { InputComponent, InputText, InputTextarea } from '@/common/components/forms' import { Loading } from '@/common/components/Loading' import { ModalHeader, ModalTransactionFooter, Row, - RowInline, ScrolledModal, ScrolledModalBody, ScrolledModalContainer, } from '@/common/components/Modal' -import { Tooltip, TooltipDefault } from '@/common/components/Tooltip' -import { TextMedium, TextSmall } from '@/common/components/typography' +import { TextMedium } from '@/common/components/typography' import { Warning } from '@/common/components/Warning' import { WithNullableValues } from '@/common/types/form' import { definedValues } from '@/common/utils' import { useYupValidationResolver } from '@/common/utils/validation' import { AvatarInput } from '@/memberships/components/AvatarInput' +import { SelectValidatorAccounts, useSelectValidatorAccounts } from '@/memberships/components/SelectValidatorAccounts' import { SocialMediaSelector } from '@/memberships/components/SocialMediaSelector/SocialMediaSelector' import { useUploadAvatarAndSubmit } from '@/memberships/hooks/useUploadAvatarAndSubmit' import { useGetMembersCountQuery } from '@/memberships/queries' -import { useValidators } from '@/validators/hooks/useValidators' import { AvatarURISchema, ExternalResourcesSchema, HandleSchema } from '../../model/validation' import { MemberWithDetails } from '../../types' -import { InputNotificationIcon, SelectValidatorAccountWrapper } from '../BuyMembershipModal/BuyMembershipFormModal' import { UpdateMemberForm } from './types' import { changedOrNull, hasAnyEdits, hasAnyMetadateChanges, membershipExternalResourceToObject } from './utils' +type FormFields = Omit & { + validatorAddresses: string[] +} interface Props { onClose: () => void onSubmit: (params: WithNullableValues, memberId: string, controllerAccount: string) => void @@ -57,18 +54,23 @@ const UpdateMemberSchema = Yup.object().shape({ export const UpdateMembershipFormModal = ({ onClose, onSubmit, member }: Props) => { const { allAccounts } = useMyAccounts() - const validators = useValidators() - const validatorAddresses = useMemo( + + const boundAccounts: Account[] = useMemo( () => - validators - ?.flatMap(({ stashAccount: stash, controllerAccount: ctrl }) => (ctrl ? [stash, ctrl] : [stash])) - .map(encodeAddress), - [validators] - ) - const initialValidatorAccounts = useMemo( - () => member.boundAccounts.filter((address) => validatorAddresses?.includes(address)), - [member.boundAccounts, validatorAddresses] + member.boundAccounts.map( + (address) => + allAccounts.find((account) => account.address === address) ?? + accountOrNamed(allAccounts, address, 'Unsaved account') + ), + [allAccounts, member] ) + const selectValidatorAccounts = useSelectValidatorAccounts(boundAccounts) + const { + initialValidatorAccounts, + state: { isValidator, accounts: validatorAccounts }, + isValidatorAccount, + } = selectValidatorAccounts + const updateMemberFormInitial = useMemo( () => ({ id: member.id, @@ -80,19 +82,16 @@ export const UpdateMembershipFormModal = ({ onClose, onSubmit, member }: Props) controllerAccount: member.controllerAccount, externalResources: membershipExternalResourceToObject(member.externalResources) ?? {}, isValidator: initialValidatorAccounts.length > 0, - validatorAccounts: initialValidatorAccounts, + validatorAddresses: initialValidatorAccounts.map((account) => account.address), }), [member, initialValidatorAccounts] ) - const isValidValidatorAccount = useCallback( - (account: Account | undefined) => !account || validatorAddresses?.includes(account.address), - [validatorAddresses] - ) const [handleMap, setHandleMap] = useState(member.handle) const { data } = useGetMembersCountQuery({ variables: { where: { handle_eq: handleMap } } }) const context = { size: data?.membershipsConnection.totalCount, isHandleChanged: handleMap !== member.handle } - const { uploadAvatarAndSubmit, isUploading } = useUploadAvatarAndSubmit((fields) => + + const { uploadAvatarAndSubmit, isUploading } = useUploadAvatarAndSubmit((fields) => onSubmit( { ...changedOrNull( @@ -100,11 +99,11 @@ export const UpdateMembershipFormModal = ({ onClose, onSubmit, member }: Props) updateMemberFormInitial ), validatorAccounts: isValidator - ? fields.validatorAccounts?.filter((address) => !initialValidatorAccounts.includes(address)) + ? difference(fields.validatorAddresses, updateMemberFormInitial.validatorAddresses) : [], validatorAccountsToBeRemoved: isValidator - ? initialValidatorAccounts.filter((address) => !fields.validatorAccounts?.includes(address)) - : initialValidatorAccounts, + ? difference(updateMemberFormInitial.validatorAddresses, fields.validatorAddresses) + : updateMemberFormInitial.validatorAddresses, }, member.id, member.controllerAccount @@ -117,26 +116,7 @@ export const UpdateMembershipFormModal = ({ onClose, onSubmit, member }: Props) mode: 'onChange', }) - useEffect(() => { - form.reset({ - ...updateMemberFormInitial, - rootAccount: accountOrNamed(allAccounts, member.rootAccount, 'Root Account'), - controllerAccount: accountOrNamed(allAccounts, member.controllerAccount, 'Controller Account'), - }) - }, [updateMemberFormInitial, member, allAccounts]) - - const [controllerAccount, rootAccount, handle, isValidator] = form.watch([ - 'controllerAccount', - 'rootAccount', - 'handle', - 'isValidator', - ]) - const [validatorAccounts, setValidatorAccounts] = useState<(Account | undefined)[]>([]) - useEffect(() => { - setValidatorAccounts([ - ...initialValidatorAccounts.map((address) => accountOrNamed(allAccounts, address, 'Unsaved account')), - ]) - }, [initialValidatorAccounts, allAccounts]) + const [controllerAccount, rootAccount, handle] = form.watch(['controllerAccount', 'rootAccount', 'handle']) useEffect(() => { form.trigger('handle') @@ -149,28 +129,12 @@ export const UpdateMembershipFormModal = ({ onClose, onSubmit, member }: Props) const filterRoot = useCallback(filterAccount(controllerAccount), [controllerAccount]) const filterController = useCallback(filterAccount(rootAccount), [rootAccount]) - const addValidatorAccount = (index: number, value: Account | undefined) => { - setValidatorAccounts((accounts) => accounts.toSpliced(index, 1, value)) - } - - const removeValidatorAccount = (index: number) => { - validatorAccounts && setValidatorAccounts((accounts) => accounts.toSpliced(index, 1)) - } - - const validatorAccountSelectorFilter = (index: number, account: Account) => - (!validatorAccounts || - ![...validatorAccounts.toSpliced(index, 1)].find( - (accountOrUndefined) => accountOrUndefined?.address === account.address - )) && - !!validatorAddresses?.includes(account.address) - const formData = useMemo( () => ({ ...form.getValues(), - validatorAccounts: (validatorAccounts.filter((account) => !!account) as Account[]).map( - ({ address }) => address - ), + isValidator, + validatorAddresses: validatorAccounts.flatMap((account) => account?.address ?? []), } as UpdateMemberForm), [form.getValues(), validatorAccounts] ) @@ -179,13 +143,12 @@ export const UpdateMembershipFormModal = ({ onClose, onSubmit, member }: Props) form.formState.isValid && hasAnyEdits(formData, updateMemberFormInitial) && (!isValidator || - (validatorAccounts?.filter((account) => !account || !isValidValidatorAccount(account)).length === 0 && - validatorAccounts?.filter((account) => !!account).length)) + (validatorAccounts.length > 0 && validatorAccounts.every((account) => account && isValidatorAccount(account)))) const willBecomeUnverifiedValidator = updateMemberFormInitial.isValidator && hasAnyMetadateChanges(formData, updateMemberFormInitial) - const submit = () => uploadAvatarAndSubmit(formData) + const submit = () => uploadAvatarAndSubmit(formData as FormFields) return ( @@ -254,72 +217,7 @@ export const UpdateMembershipFormModal = ({ onClose, onSubmit, member }: Props) /> )} - - - - - - {isValidator && ( - <> - - - - - - - * - - - If your validator account is not in your signer wallet, paste the account address to the field - below: - - {validatorAccounts?.map((account, index) => ( - - - - addValidatorAccount(index, account)} - filter={(account) => validatorAccountSelectorFilter(index, account)} - /> - - { - removeValidatorAccount(index) - }} - className="remove-button" - > - - - - {!isValidValidatorAccount(account) && ( - - - - - - - - This account is neither a validator controller account nor a validator stash account. - - - )} - - ))} - - addValidatorAccount(validatorAccounts.length, undefined)} - > - Add Validator Account - - - - - )} + diff --git a/packages/ui/src/memberships/modals/UpdateMembershipModal/types.ts b/packages/ui/src/memberships/modals/UpdateMembershipModal/types.ts index bd96bbeba8..65c2e3d75d 100644 --- a/packages/ui/src/memberships/modals/UpdateMembershipModal/types.ts +++ b/packages/ui/src/memberships/modals/UpdateMembershipModal/types.ts @@ -10,7 +10,6 @@ export interface UpdateMemberForm { rootAccount?: Account controllerAccount?: Account externalResources: Record - isValidator?: boolean validatorAccounts?: Address[] validatorAccountsToBeRemoved?: Address[] }