Remove code duplication in Create and Derive modals (#5895)
* Factor out code duplication in createAccount * Factor out account names * Factor out account pass * Factor out common UI elements * Move CreateAccountInputs to a separate file * Rename "getResult" to "commitAccount" * Rename "AccountPass" to "AccountPassword" * Use shorter name for component Props * Rename "isPassValid" to "isPasswordValid" * Update packages/page-accounts/src/modals/CreateAccountInputs.tsx Co-authored-by: Jaco <jacogr@gmail.com> * Update packages/page-accounts/src/util.tsx Co-authored-by: Jaco <jacogr@gmail.com> * Rename _onCommit prop to onCommit * Apply new lint rules Co-authored-by: Jaco <jacogr@gmail.com> Co-authored-by: MiZiet <michalzietalak@o2.pl>
This commit is contained in:
parent
9060edff3f
commit
f248714613
@ -2,16 +2,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import type { CreateResult } from '@polkadot/ui-keyring/types';
|
||||
import type { AddressState, CreateOptions, CreateProps, DeriveValidationOutput, PairType, SeedType } from '../types';
|
||||
|
||||
import FileSaver from 'file-saver';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DEV_PHRASE } from '@polkadot/keyring/defaults';
|
||||
import { getEnvironment } from '@polkadot/react-api/util';
|
||||
import { AddressRow, Button, Checkbox, CopyButton, Dropdown, Expander, Input, InputAddress, MarkError, MarkWarning, Modal, TextArea } from '@polkadot/react-components';
|
||||
import { AddressRow, Button, Checkbox, CopyButton, Dropdown, Expander, Input, MarkError, MarkWarning, Modal, TextArea } from '@polkadot/react-components';
|
||||
import { useApi, useLedger, useStepper } from '@polkadot/react-hooks';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
import { settings } from '@polkadot/ui-settings';
|
||||
@ -19,11 +16,12 @@ import { isHex, u8aToHex } from '@polkadot/util';
|
||||
import { hdLedger, hdValidatePath, keyExtractSuri, mnemonicGenerate, mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import { tryCreateAccount } from '../util';
|
||||
import CreateAccountInputs from './CreateAccountInputs';
|
||||
import CreateConfirmation from './CreateConfirmation';
|
||||
import CreateEthDerivationPath, { ETH_DEFAULT_PATH } from './CreateEthDerivationPath';
|
||||
import CreateSuriLedger from './CreateSuriLedger';
|
||||
import ExternalWarning from './ExternalWarning';
|
||||
import PasswordInput from './PasswordInput';
|
||||
|
||||
const DEFAULT_PAIR_TYPE = 'sr25519';
|
||||
const STEPS_COUNT = 3;
|
||||
@ -134,35 +132,11 @@ function updateAddress (seed: string, derivePath: string, seedType: SeedType, pa
|
||||
};
|
||||
}
|
||||
|
||||
export function downloadAccount ({ json, pair }: CreateResult): void {
|
||||
const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' });
|
||||
|
||||
FileSaver.saveAs(blob, `${pair.address}.json`);
|
||||
}
|
||||
|
||||
function createAccount (seed: string, derivePath: string, pairType: PairType, { genesisHash, name, tags = [] }: CreateOptions, password: string, success: string): ActionStatus {
|
||||
// we will fill in all the details below
|
||||
const status = { action: 'create' } as ActionStatus;
|
||||
const commitAccount = () =>
|
||||
keyring.addUri(getSuri(seed, derivePath, pairType), password, { genesisHash, isHardware: false, name, tags }, pairType === 'ed25519-ledger' ? 'ed25519' : pairType);
|
||||
|
||||
try {
|
||||
const result = keyring.addUri(getSuri(seed, derivePath, pairType), password, { genesisHash, isHardware: false, name, tags }, pairType === 'ed25519-ledger' ? 'ed25519' : pairType);
|
||||
const { address } = result.pair;
|
||||
|
||||
status.account = address;
|
||||
status.status = 'success';
|
||||
status.message = success;
|
||||
|
||||
InputAddress.setLastValue('account', address);
|
||||
|
||||
if (getEnvironment() === 'web') {
|
||||
downloadAccount(result);
|
||||
}
|
||||
} catch (error) {
|
||||
status.status = 'error';
|
||||
status.message = (error as Error).message;
|
||||
}
|
||||
|
||||
return status;
|
||||
return tryCreateAccount(commitAccount, success);
|
||||
}
|
||||
|
||||
function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, type: propsType }: CreateProps): React.ReactElement<CreateProps> {
|
||||
@ -231,16 +205,6 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ
|
||||
[derivePath, pairType, seedType]
|
||||
);
|
||||
|
||||
const _onChangeName = useCallback(
|
||||
(name: string) => setName({ isNameValid: !!name.trim(), name }),
|
||||
[]
|
||||
);
|
||||
|
||||
const _onPasswordChange = useCallback(
|
||||
(password: string, isPasswordValid: boolean) => setPassword({ isPasswordValid, password }),
|
||||
[]
|
||||
);
|
||||
|
||||
const _toggleMnemonicSaved = useCallback(
|
||||
() => setIsMnemonicSaved(!isMnemonicSaved),
|
||||
[isMnemonicSaved]
|
||||
@ -397,22 +361,12 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ
|
||||
</Modal.Columns>
|
||||
</>}
|
||||
{step === 2 && <>
|
||||
<Modal.Columns hint={t<string>('The name for this account and how it will appear under your addresses. With an on-chain identity, it can be made available to others.')}>
|
||||
<Input
|
||||
autoFocus
|
||||
help={t<string>('Name given to this account. You can edit it. To use the account to validate or nominate, it is a good practice to append the function of the account in the name, e.g "name_you_want - stash".')}
|
||||
isError={!isNameValid}
|
||||
label={t<string>('name')}
|
||||
onChange={_onChangeName}
|
||||
onEnter={_onCommit}
|
||||
placeholder={t<string>('new account')}
|
||||
value={name}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<PasswordInput
|
||||
onChange={_onPasswordChange}
|
||||
onEnter={_onCommit}
|
||||
/>
|
||||
<CreateAccountInputs
|
||||
name={{ isNameValid, name }}
|
||||
onCommit={_onCommit}
|
||||
setName={setName}
|
||||
setPassword={setPassword}
|
||||
/>;
|
||||
<Modal.Columns>
|
||||
<ExternalWarning />
|
||||
</Modal.Columns>
|
||||
|
||||
63
packages/page-accounts/src/modals/CreateAccountInputs.tsx
Normal file
63
packages/page-accounts/src/modals/CreateAccountInputs.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2017-2021 @polkadot/app-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Input, Modal } from '@polkadot/react-components';
|
||||
import { useTranslation } from '@polkadot/react-components/translate';
|
||||
|
||||
import PasswordInput from './PasswordInput';
|
||||
|
||||
interface AccountName {
|
||||
name: string;
|
||||
isNameValid: boolean;
|
||||
}
|
||||
|
||||
interface AccountPassword {
|
||||
password: string;
|
||||
isPasswordValid: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
name: AccountName;
|
||||
onCommit: () => void;
|
||||
setName: (value: AccountName) => void;
|
||||
setPassword: (value: AccountPassword) => void;
|
||||
}
|
||||
|
||||
const CreateAccountInputs = ({ name: { isNameValid, name }, onCommit, setName, setPassword }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const _onChangeName = useCallback(
|
||||
(name: string) => setName({ isNameValid: !!name.trim(), name }),
|
||||
[setName]
|
||||
);
|
||||
|
||||
const _onChangePass = useCallback(
|
||||
(password: string, isValid: boolean) => setPassword({ isPasswordValid: isValid, password }),
|
||||
[setPassword]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Columns hint={t<string>('The name for this account and how it will appear under your addresses. With an on-chain identity, it can be made available to others.')}>
|
||||
<Input
|
||||
className='full'
|
||||
help={t<string>('Name given to this account. You can edit it. To use the account to validate or nominate, it is a good practice to append the function of the account in the name, e.g "name_you_want - stash".')}
|
||||
isError={!isNameValid}
|
||||
label={t<string>('name')}
|
||||
onChange={_onChangeName}
|
||||
onEnter={onCommit}
|
||||
placeholder={t<string>('new account')}
|
||||
value={name}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<PasswordInput
|
||||
onChange={_onChangePass}
|
||||
onEnter={onCommit}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CreateAccountInputs);
|
||||
@ -13,7 +13,8 @@ import { keyring } from '@polkadot/ui-keyring';
|
||||
import { keyExtractPath } from '@polkadot/util-crypto';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import { downloadAccount } from './Create';
|
||||
import { tryCreateAccount } from '../util';
|
||||
import CreateAccountInputs from './CreateAccountInputs';
|
||||
import CreateConfirmation from './CreateConfirmation';
|
||||
|
||||
interface Props {
|
||||
@ -54,29 +55,15 @@ function deriveValidate (suri: string, pairType: KeypairType): string | null {
|
||||
}
|
||||
|
||||
function createAccount (source: KeyringPair, suri: string, name: string, password: string, success: string, genesisHash?: string): ActionStatus {
|
||||
// we will fill in all the details below
|
||||
const status = { action: 'create' } as ActionStatus;
|
||||
|
||||
try {
|
||||
const commitAccount = () => {
|
||||
const derived = source.derive(suri);
|
||||
|
||||
derived.setMeta({ ...derived.meta, genesisHash, name, parentAddress: source.address, tags: [] });
|
||||
|
||||
const result = keyring.addPair(derived, password || '');
|
||||
const { address } = result.pair;
|
||||
return keyring.addPair(derived, password || '');
|
||||
};
|
||||
|
||||
status.account = address;
|
||||
status.status = 'success';
|
||||
status.message = success;
|
||||
|
||||
downloadAccount(result);
|
||||
InputAddress.setLastValue('account', address);
|
||||
} catch (error) {
|
||||
status.status = 'error';
|
||||
status.message = (error as Error).message;
|
||||
}
|
||||
|
||||
return status;
|
||||
return tryCreateAccount(commitAccount, success);
|
||||
}
|
||||
|
||||
function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
|
||||
@ -85,16 +72,15 @@ function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
|
||||
const { queueAction } = useContext(StatusContext);
|
||||
const [source] = useState(() => keyring.getPair(from));
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
|
||||
const [{ isPasswordValid, password }, setPassword] = useState({ isPasswordValid: false, password: '' });
|
||||
const [{ address, deriveError }, setDerive] = useState<DeriveAddress>({ address: null, deriveError: null });
|
||||
const [isConfirmationOpen, toggleConfirmation] = useToggle();
|
||||
const [{ isLocked, lockedError }, setIsLocked] = useState<LockState>({ isLocked: source.isLocked, lockedError: null });
|
||||
const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
|
||||
const [{ isPassValid, password }, setPassword] = useState({ isPassValid: false, password: '' });
|
||||
const [{ isPass2Valid, password2 }, setPassword2] = useState({ isPass2Valid: false, password2: '' });
|
||||
const [{ isRootValid, rootPass }, setRootPass] = useState({ isRootValid: false, rootPass: '' });
|
||||
const [suri, setSuri] = useState('');
|
||||
const debouncedSuri = useDebounce(suri);
|
||||
const isValid = !!address && !deriveError && isNameValid && isPassValid && isPass2Valid;
|
||||
const isValid = !!address && !deriveError && isNameValid && isPasswordValid;
|
||||
|
||||
useEffect((): void => {
|
||||
setIsLocked({ isLocked: source.isLocked, lockedError: null });
|
||||
@ -115,21 +101,6 @@ function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
|
||||
});
|
||||
}, [debouncedSuri, isLocked, source]);
|
||||
|
||||
const _onChangeName = useCallback(
|
||||
(name: string) => setName({ isNameValid: !!name.trim(), name }),
|
||||
[]
|
||||
);
|
||||
|
||||
const _onChangePass = useCallback(
|
||||
(password: string) => setPassword({ isPassValid: keyring.isPassValid(password), password }),
|
||||
[]
|
||||
);
|
||||
|
||||
const _onChangePass2 = useCallback(
|
||||
(password2: string) => setPassword2({ isPass2Valid: keyring.isPassValid(password2) && (password2 === password), password2 }),
|
||||
[password]
|
||||
);
|
||||
|
||||
const _onChangeRootPass = useCallback(
|
||||
(rootPass: string): void => {
|
||||
setRootPass({ isRootValid: !!rootPass, rootPass });
|
||||
@ -231,34 +202,12 @@ function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
|
||||
{deriveError && (
|
||||
<MarkError content={deriveError} />
|
||||
)}
|
||||
<Input
|
||||
className='full'
|
||||
help={t<string>('Name given to this account. You can edit it. To use the account to validate or nominate, it is a good practice to append the function of the account in the name, e.g "name_you_want - stash".')}
|
||||
isError={!isNameValid}
|
||||
label={t<string>('name')}
|
||||
onChange={_onChangeName}
|
||||
onEnter={_onCommit}
|
||||
placeholder={t<string>('new account')}
|
||||
value={name}
|
||||
/>
|
||||
<Password
|
||||
className='full'
|
||||
help={t<string>('This password is used to encrypt your private key. It must be strong and unique! You will need it to sign transactions with this account. You can recover this account using this password together with the backup file (generated in the next step).')}
|
||||
isError={!isPassValid}
|
||||
label={t<string>('password')}
|
||||
onChange={_onChangePass}
|
||||
onEnter={_onCommit}
|
||||
value={password}
|
||||
/>
|
||||
<Password
|
||||
className='full'
|
||||
help={t<string>('Verify the password entered above.')}
|
||||
isError={!isPass2Valid}
|
||||
label={t<string>('password (repeat)')}
|
||||
onChange={_onChangePass2}
|
||||
onEnter={_onCommit}
|
||||
value={password2}
|
||||
/>
|
||||
<CreateAccountInputs
|
||||
name={{ isNameValid, name }}
|
||||
onCommit={_onCommit}
|
||||
setName={setName}
|
||||
setPassword={setPassword}
|
||||
/>;
|
||||
</AddressRow>
|
||||
)}
|
||||
</Modal.Content>
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
// Copyright 2017-2021 @polkadot/app-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KeyringAddress } from '@polkadot/ui-keyring/types';
|
||||
import type { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import type { CreateResult, KeyringAddress } from '@polkadot/ui-keyring/types';
|
||||
import type { SortedAccount } from './types';
|
||||
|
||||
import FileSaver from 'file-saver';
|
||||
import React from 'react';
|
||||
|
||||
import { Menu } from '@polkadot/react-components';
|
||||
import { getEnvironment } from '@polkadot/react-api/util';
|
||||
import { InputAddress, Menu } from '@polkadot/react-components';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
|
||||
export function createMenuGroup (key: string, items: (React.ReactNode | false | undefined | null)[], header?: string): React.ReactNode | null {
|
||||
@ -21,6 +24,40 @@ export function createMenuGroup (key: string, items: (React.ReactNode | false |
|
||||
: null;
|
||||
}
|
||||
|
||||
export function downloadAccount ({ json, pair }: CreateResult): void {
|
||||
FileSaver.saveAs(
|
||||
new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' }),
|
||||
`${pair.address}.json`
|
||||
);
|
||||
}
|
||||
|
||||
export function tryCreateAccount (commitAccount: () => CreateResult, success: string): ActionStatus {
|
||||
const status: ActionStatus = {
|
||||
action: 'create',
|
||||
message: success,
|
||||
status: 'success'
|
||||
};
|
||||
|
||||
try {
|
||||
const result = commitAccount();
|
||||
const address = result.pair.address;
|
||||
|
||||
status.account = address;
|
||||
|
||||
if (getEnvironment() === 'web') {
|
||||
downloadAccount(result);
|
||||
}
|
||||
|
||||
downloadAccount(result);
|
||||
InputAddress.setLastValue('account', address);
|
||||
} catch (error) {
|
||||
status.status = 'error';
|
||||
status.message = (error as Error).message;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
function expandList (mapped: SortedAccount[], entry: SortedAccount): SortedAccount[] {
|
||||
mapped.push(entry);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user