From 4cd62e0d1b4b715c5527ae288f4374312986b653 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Sun, 24 Dec 2023 09:51:58 +0800 Subject: [PATCH 01/13] fix: issue 291 --- .../detectDuplicateWalletDialog.module.scss | 32 +++++ .../DetectDuplicateWalletDialog/index.tsx | 122 +++++++++++++++++ .../components/ImportHardware/name-wallet.tsx | 67 +++++---- .../src/components/ImportKeystore/index.tsx | 101 ++++++++------ .../src/components/PasswordRequest/index.tsx | 4 +- .../ReplaceDuplicateWalletDialog/index.tsx | 129 ++++++++++++++++++ .../replaceDuplicateWalletDialog.module.scss | 24 ++++ .../WalletImportConflictDialog/hooks.ts | 81 +++++++++++ .../WalletImportConflictDialog/index.tsx | 80 +++++++++++ .../walletEditorDialog.module.scss | 3 + .../src/components/WalletSetting/index.tsx | 47 +++++-- .../WalletSetting/walletSetting.module.scss | 11 +- .../src/components/WalletWizard/index.tsx | 106 ++++++++------ packages/neuron-ui/src/locales/en.json | 12 +- packages/neuron-ui/src/locales/zh-tw.json | 12 +- packages/neuron-ui/src/locales/zh.json | 12 +- .../src/services/remote/remoteApiWrapper.ts | 1 + .../neuron-ui/src/services/remote/wallets.ts | 1 + packages/neuron-ui/src/states/init/wallet.ts | 2 + .../neuron-ui/src/stories/Navbar.stories.tsx | 2 +- .../src/stories/PasswordRequest.stories.tsx | 12 +- .../src/stories/WalletSetting.stories.tsx | 2 + packages/neuron-ui/src/styles/mixin.scss | 1 + packages/neuron-ui/src/types/App/index.d.ts | 1 + .../neuron-ui/src/types/Controller/index.d.ts | 5 + packages/neuron-ui/src/utils/enums.ts | 1 + .../neuron-ui/src/widgets/Icons/Detect.svg | 10 ++ packages/neuron-ui/src/widgets/Icons/icon.tsx | 8 ++ packages/neuron-wallet/src/controllers/api.ts | 4 + .../neuron-wallet/src/controllers/wallets.ts | 11 +- .../neuron-wallet/src/exceptions/wallet.ts | 8 ++ .../neuron-wallet/src/services/wallets.ts | 47 ++++++- 32 files changed, 814 insertions(+), 145 deletions(-) create mode 100755 packages/neuron-ui/src/components/DetectDuplicateWalletDialog/detectDuplicateWalletDialog.module.scss create mode 100644 packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx create mode 100644 packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx create mode 100755 packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/replaceDuplicateWalletDialog.module.scss create mode 100644 packages/neuron-ui/src/components/WalletImportConflictDialog/hooks.ts create mode 100644 packages/neuron-ui/src/components/WalletImportConflictDialog/index.tsx create mode 100755 packages/neuron-ui/src/components/WalletImportConflictDialog/walletEditorDialog.module.scss create mode 100644 packages/neuron-ui/src/widgets/Icons/Detect.svg diff --git a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/detectDuplicateWalletDialog.module.scss b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/detectDuplicateWalletDialog.module.scss new file mode 100755 index 0000000000..68343457f0 --- /dev/null +++ b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/detectDuplicateWalletDialog.module.scss @@ -0,0 +1,32 @@ +.content { + width: 648px; + .detail { + margin: 0; + color: var(--secondary-text-color); + } + .groupWrap { + margin-top: 16px; + height: 177px; + overflow-y: scroll; + border: 1px solid var(--divide-line-color); + border-radius: 8px; + padding-left: 16px; + > div { + border-bottom: 1px solid var(--divide-line-color); + padding-top: 16px; + &:last-child { + border: none; + } + } + .radioItem { + padding: 0 0 16px; + &:hover, + &:focus { + background: transparent; + button { + visibility: visible; + } + } + } + } +} diff --git a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx new file mode 100644 index 0000000000..28b7557368 --- /dev/null +++ b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx @@ -0,0 +1,122 @@ +import React, { useMemo, useState, useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import Dialog from 'widgets/Dialog' +import RadioGroup from 'widgets/RadioGroup' +import { useState as useGlobalState, useDispatch, AppActions } from 'states' +import { requestPassword } from 'services/remote' +import PasswordRequest from 'components/PasswordRequest' +import styles from './detectDuplicateWalletDialog.module.scss' + +const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { + const { + wallet: { id: currentID = '' }, + settings: { wallets = [] }, + } = useGlobalState() + const dispatch = useDispatch() + const [deletableWallets, setDeletableWallets] = useState([]) + const [t] = useTranslation() + + const groups = useMemo(() => { + const obj = {} as { + [key: string]: State.WalletIdentity[] + } + wallets.forEach(item => { + if (item.extendedKey in obj) { + obj[item.extendedKey].push(item) + } else { + obj[item.extendedKey] = [item] + } + }) + + return Object.values(obj).filter(list => list.length > 1) + }, [wallets]) + + const handleGroupChange = useCallback( + value => { + const [extendedKey, id] = value.split('_') + const list: string[] = [] + wallets.forEach(item => { + if (item.extendedKey === extendedKey) { + if (item.id !== id) { + list.push(item.id) + } + } else if (deletableWallets.includes(item.id)) { + list.push(item.id) + } + }) + + setDeletableWallets(list) + }, + [wallets, deletableWallets, setDeletableWallets] + ) + + const onConfirm = useCallback(async () => { + const getRequest = (id: string) => { + if (wallets.find(item => item.id === id && item.device)) { + return requestPassword({ walletID: id, action: 'delete-wallet' }) + } + return new Promise(resolve => { + dispatch({ + type: AppActions.RequestPassword, + payload: { + walletID: id, + actionType: 'delete', + onSuccess: async () => { + await resolve(id) + }, + }, + }) + }) + } + + let ids = deletableWallets + if (deletableWallets.includes(currentID)) { + ids = ids.filter(item => item !== currentID) + ids.push(currentID) + } + + // eslint-disable-next-line no-restricted-syntax + for (const id of ids) { + // eslint-disable-next-line no-await-in-loop + await getRequest(id) + } + + onClose() + }, [deletableWallets, requestPassword, onClose, dispatch]) + + return ( + <> + +
+

{t('settings.wallet-manager.detected-duplicate.detail')}

+
+ {groups.map(group => ( + ({ + value: `${wallet.extendedKey}_${wallet.id}`, + label: {wallet.name}, + }))} + /> + ))} +
+
+
+ + + ) +} + +DetectDuplicateWalletDialog.displayName = 'DetectDuplicateWalletDialog' + +export default DetectDuplicateWalletDialog diff --git a/packages/neuron-ui/src/components/ImportHardware/name-wallet.tsx b/packages/neuron-ui/src/components/ImportHardware/name-wallet.tsx index 5960236158..b8951337ae 100644 --- a/packages/neuron-ui/src/components/ImportHardware/name-wallet.tsx +++ b/packages/neuron-ui/src/components/ImportHardware/name-wallet.tsx @@ -3,10 +3,11 @@ import { useTranslation } from 'react-i18next' import Button from 'widgets/Button' import TextField from 'widgets/TextField' import { createHardwareWallet } from 'services/remote' -import { CONSTANTS, isSuccessResponse, useDialogWrapper } from 'utils' +import { CONSTANTS, isSuccessResponse, useDialogWrapper, ErrorCode } from 'utils' import Alert from 'widgets/Alert' import { FinishCreateLoading, getAlertStatus } from 'components/WalletWizard' import { importedWalletDialogShown } from 'services/localCache' +import ReplaceDuplicateWalletDialog, { useReplaceDuplicateWallet } from 'components/ReplaceDuplicateWalletDialog' import { ImportStep, ActionType, ImportHardwareState } from './common' import styles from './findDevice.module.scss' @@ -26,6 +27,7 @@ const NameWallet = ({ const [walletName, setWalletName] = useState(`${model?.manufacturer} ${model?.product}`) const [errorMsg, setErrorMsg] = useState('') const { dialogRef, openDialog, closeDialog } = useDialogWrapper() + const { onImportingExitingWalletError, dialogProps } = useReplaceDuplicateWallet() const onBack = useCallback(() => { dispatch({ step: ImportStep.ImportHardware }) @@ -46,6 +48,11 @@ const NameWallet = ({ importedWalletDialogShown.setStatus(res.result.id, true) } } else { + if (res.status === ErrorCode.ImportingExitingWallet) { + onImportingExitingWalletError(res.message) + return + } + setErrorMsg(typeof res.message === 'string' ? res.message : res.message!.content!) } }) @@ -62,33 +69,37 @@ const NameWallet = ({ }, []) return ( -
-
{t('import-hardware.title.name-wallet')}
-
- -
- - {errorMsg || t('wizard.new-name')} - -
-
- - + <> +
+
{t('import-hardware.title.name-wallet')}
+
+ +
+ + {errorMsg || t('wizard.new-name')} + +
+
+ + + + + ) } diff --git a/packages/neuron-ui/src/components/ImportKeystore/index.tsx b/packages/neuron-ui/src/components/ImportKeystore/index.tsx index 07be66530d..732aeecb37 100644 --- a/packages/neuron-ui/src/components/ImportKeystore/index.tsx +++ b/packages/neuron-ui/src/components/ImportKeystore/index.tsx @@ -15,6 +15,7 @@ import { useDialogWrapper, } from 'utils' +import ReplaceDuplicateWalletDialog, { useReplaceDuplicateWallet } from 'components/ReplaceDuplicateWalletDialog' import { FinishCreateLoading, CreateFirstWalletNav } from 'components/WalletWizard' import TextField from 'widgets/TextField' import { importedWalletDialogShown } from 'services/localCache' @@ -48,6 +49,7 @@ const ImportKeystore = () => { const navigate = useNavigate() const [fields, setFields] = useState(defaultFields) const [openingFile, setOpeningFile] = useState(false) + const { onImportingExitingWalletError, dialogProps } = useReplaceDuplicateWallet() const goBack = useGoBack() const disabled = !!( @@ -115,6 +117,11 @@ const ImportKeystore = () => { throw new PasswordIncorrectException() } + if (res.status === ErrorCode.ImportingExitingWallet) { + onImportingExitingWalletError(res.message) + return + } + if (res.message) { const msg = typeof res.message === 'string' ? res.message : res.message.content || '' if (msg) { @@ -193,51 +200,55 @@ const ImportKeystore = () => { ) return ( -
-
{t('import-keystore.title')}
- - {Object.entries(fields) - .filter(([key]) => !key.endsWith('Error')) - .map(([key, value]) => { - return ( - {}} - role="button" - tabIndex={-1} - > - {t('import-keystore.select-file')} - - ) : null - } - required - /> - ) - })} -
- -
- - + <> +
+
{t('import-keystore.title')}
+ + {Object.entries(fields) + .filter(([key]) => !key.endsWith('Error')) + .map(([key, value]) => { + return ( + {}} + role="button" + tabIndex={-1} + > + {t('import-keystore.select-file')} + + ) : null + } + required + /> + ) + })} +
+ +
+ + + + + ) } diff --git a/packages/neuron-ui/src/components/PasswordRequest/index.tsx b/packages/neuron-ui/src/components/PasswordRequest/index.tsx index e653f84423..998911c31b 100644 --- a/packages/neuron-ui/src/components/PasswordRequest/index.tsx +++ b/packages/neuron-ui/src/components/PasswordRequest/index.tsx @@ -173,7 +173,9 @@ const PasswordRequest = () => { } case 'delete': { await deleteWallet({ id: walletID, password })(dispatch).then(status => { - if (status === ErrorCode.PasswordIncorrect) { + if (isSuccessResponse({ status })) { + onSuccess?.() + } else if (status === ErrorCode.PasswordIncorrect) { throw new PasswordIncorrectException() } }) diff --git a/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx new file mode 100644 index 0000000000..504e5e7647 --- /dev/null +++ b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx @@ -0,0 +1,129 @@ +import React, { useMemo, useState, useCallback } from 'react' +import { useNavigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import Dialog from 'widgets/Dialog' +import RadioGroup from 'widgets/RadioGroup' +import { useState as useGlobalState, useDispatch, updateWalletProperty, setCurrentWallet } from 'states' +import { RoutePath, isSuccessResponse } from 'utils' +import { replaceWallet } from 'services/remote' +import styles from './replaceDuplicateWalletDialog.module.scss' + +const useReplaceDuplicateWallet = () => { + const [extendedKey, setExtendedKey] = useState('') + const [tmpId, setTmpId] = useState('') + + const onClose = useCallback(() => { + setTmpId('') + }, [setTmpId]) + + const onImportingExitingWalletError = ( + message: + | string + | { + content?: string + meta?: { [key: string]: string } + } + ) => { + try { + const msg = typeof message === 'string' ? '' : message.content + if (msg) { + const obj = JSON.parse(msg) + setExtendedKey(obj.extendedKey) + setTmpId(obj.id) + } + } catch (error) { + onClose() + } + } + + const show = useMemo(() => !!extendedKey && !!tmpId, [tmpId, extendedKey]) + + return { + onImportingExitingWalletError, + dialogProps: { + show, + onClose, + extendedKey, + tmpId, + }, + } +} + +const ReplaceDuplicateWalletDialog = ({ + show, + onClose, + extendedKey, + tmpId, +}: { + show: boolean + onClose: () => void + extendedKey: string + tmpId: string +}) => { + const { + settings: { wallets = [] }, + } = useGlobalState() + const dispatch = useDispatch() + const navigate = useNavigate() + const [selectedId, setSelectedId] = useState('') + const [t] = useTranslation() + + const group = useMemo(() => wallets.filter(item => item.extendedKey === extendedKey), [wallets, extendedKey]) + + const handleGroupChange = useCallback( + value => { + setSelectedId(value) + }, + [setSelectedId] + ) + + const onConfirm = useCallback(async () => { + replaceWallet({ + id: selectedId, + tmpId, + }) + .then(res => { + if (isSuccessResponse(res)) { + navigate(RoutePath.Overview) + return + } + onClose() + }) + .finally(() => { + setSelectedId('') + }) + }, [selectedId, updateWalletProperty, onClose, dispatch, setCurrentWallet]) + + return ( + +
+

{t('settings.wallet-manager.importing-existing.detail')}

+
+ ({ + value: wallet.id, + label: {wallet.name}, + }))} + /> +
+
+
+ ) +} + +ReplaceDuplicateWalletDialog.displayName = 'ReplaceDuplicateWalletDialog' + +export default ReplaceDuplicateWalletDialog + +export { useReplaceDuplicateWallet } diff --git a/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/replaceDuplicateWalletDialog.module.scss b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/replaceDuplicateWalletDialog.module.scss new file mode 100755 index 0000000000..a4b447ec48 --- /dev/null +++ b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/replaceDuplicateWalletDialog.module.scss @@ -0,0 +1,24 @@ +.content { + width: 648px; + .detail { + margin: 0; + color: var(--secondary-text-color); + } + .groupWrap { + margin-top: 16px; + overflow-y: scroll; + border: 1px solid var(--divide-line-color); + border-radius: 8px; + padding: 16px 0 0 16px; + .radioItem { + padding: 0 0 16px; + &:hover, + &:focus { + background: transparent; + button { + visibility: visible; + } + } + } + } +} diff --git a/packages/neuron-ui/src/components/WalletImportConflictDialog/hooks.ts b/packages/neuron-ui/src/components/WalletImportConflictDialog/hooks.ts new file mode 100644 index 0000000000..0a344b51cf --- /dev/null +++ b/packages/neuron-ui/src/components/WalletImportConflictDialog/hooks.ts @@ -0,0 +1,81 @@ +import { useState, useMemo, useCallback } from 'react' +import { StateDispatch, updateWalletProperty } from 'states' +import { ErrorCode, ResponseCode, CONSTANTS } from 'utils' +import i18n from 'utils/i18n' + +const { MAX_WALLET_NAME_LENGTH } = CONSTANTS + +export const useWalletEditor = () => { + const [name, setName] = useState('') + const initialize = useCallback( + (initName: string = '') => { + setName(initName) + }, + [setName] + ) + return { + initialize, + name: { + value: name, + onChange: (e: React.SyntheticEvent) => { + const { value } = e.target as HTMLInputElement + setName(value) + }, + }, + } +} + +export const useInputs = ({ name }: ReturnType) => { + return useMemo( + () => [ + { + ...name, + label: i18n.t('settings.wallet-manager.edit-wallet.wallet-name'), + placeholder: i18n.t('settings.wallet-manager.edit-wallet.wallet-name'), + maxLength: MAX_WALLET_NAME_LENGTH, + }, + ], + [name] + ) +} + +export const useOnSubmit = ( + name: string, + id: string, + dispatch: StateDispatch, + disabled: boolean, + callback: () => void +) => { + return useCallback(() => { + if (disabled) { + return + } + updateWalletProperty({ + id, + name, + })(dispatch).then(status => { + if (status === ResponseCode.SUCCESS) { + callback() + } + }) + }, [name, id, dispatch, disabled]) +} + +export const useHint = (name: string, usedNames: string[], t: (key: string, opts: object) => string): string | null => { + return useMemo(() => { + if (name === '') { + return t(`messages.codes.${ErrorCode.FieldRequired}`, { fieldName: 'name' }) + } + if (usedNames.includes(name)) { + return t(`messages.codes.${ErrorCode.FieldUsed}`, { fieldName: 'name', fieldValue: name }) + } + return null + }, [name, usedNames, t]) +} + +export default { + useWalletEditor, + useInputs, + useOnSubmit, + useHint, +} diff --git a/packages/neuron-ui/src/components/WalletImportConflictDialog/index.tsx b/packages/neuron-ui/src/components/WalletImportConflictDialog/index.tsx new file mode 100644 index 0000000000..d32385f233 --- /dev/null +++ b/packages/neuron-ui/src/components/WalletImportConflictDialog/index.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import TextField from 'widgets/TextField' +import Dialog from 'widgets/Dialog' + +import { useState as useGlobalState, useDispatch } from 'states' + +import { ErrorCode } from 'utils' +import styles from './walletEditorDialog.module.scss' + +import { useHint, useOnSubmit, useInputs, useWalletEditor } from './hooks' + +const WalletNotFound = () => { + const [t] = useTranslation() + return ( +
+

{t(`messages.codes.${ErrorCode.FieldNotFound}`, { fieldName: 'wallet' })}

+
+ ) +} + +const WalletEditorDialog = ({ + show, + onCancel, + onSuccess, + id, +}: { + show: boolean + onCancel: () => void + onSuccess: () => void + id: string +}) => { + const { + settings: { wallets = [] }, + } = useGlobalState() + const dispatch = useDispatch() + const [t] = useTranslation() + const wallet = useMemo(() => wallets.find(w => w.id === id), [id, wallets]) || { id: '', name: '' } + const usedNames = wallets.map(w => w.name).filter(w => w !== wallet.name) + + const editor = useWalletEditor() + const { initialize } = editor + + useEffect(() => { + initialize(wallet.name) + }, [initialize, wallet.name]) + + const inputs = useInputs(editor) + const hint = useHint(editor.name.value, usedNames, t) + const disabled = hint !== null || editor.name.value === wallet.name + + const onSubmit = useOnSubmit(editor.name.value, wallet.id, dispatch, disabled, onSuccess) + + return ( + + <> + {wallet.id ? ( +
+ {inputs.map(item => ( + + ))} +
+ ) : ( + + )} + +
+ ) +} + +WalletEditorDialog.displayName = 'WalletEditorDialog' + +export default WalletEditorDialog diff --git a/packages/neuron-ui/src/components/WalletImportConflictDialog/walletEditorDialog.module.scss b/packages/neuron-ui/src/components/WalletImportConflictDialog/walletEditorDialog.module.scss new file mode 100755 index 0000000000..78357b3428 --- /dev/null +++ b/packages/neuron-ui/src/components/WalletImportConflictDialog/walletEditorDialog.module.scss @@ -0,0 +1,3 @@ +.content { + width: 648px; +} diff --git a/packages/neuron-ui/src/components/WalletSetting/index.tsx b/packages/neuron-ui/src/components/WalletSetting/index.tsx index 19a94a203a..59d2c9f5ad 100644 --- a/packages/neuron-ui/src/components/WalletSetting/index.tsx +++ b/packages/neuron-ui/src/components/WalletSetting/index.tsx @@ -1,18 +1,22 @@ -import React, { useEffect, useCallback, useState } from 'react' +import React, { useEffect, useCallback, useState, useMemo } from 'react' import { useNavigate, useLocation } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { WalletWizardPath } from 'components/WalletWizard' -import { ReactComponent as EditWallet } from 'widgets/Icons/Edit.svg' -import { ReactComponent as DeleteWallet } from 'widgets/Icons/Delete.svg' -import { ReactComponent as CreateWallet } from 'widgets/Icons/Add.svg' -import { ReactComponent as ImportKeystore } from 'widgets/Icons/SoftWalletImportKeystore.svg' -import { ReactComponent as Export } from 'widgets/Icons/Export.svg' -import { ReactComponent as ImportHardware } from 'widgets/Icons/HardWalletImport.svg' -import { ReactComponent as AddSimple } from 'widgets/Icons/AddSimple.svg' +import { + Edit as EditWallet, + Add as CreateWallet, + Detect, + Delete as DeleteWallet, + Export, + AddSimple, + ImportKeystore, + ImportHardware, +} from 'widgets/Icons/icon' import Tooltip from 'widgets/Tooltip' import Toast from 'widgets/Toast' import { StateDispatch } from 'states' import WalletEditorDialog from 'components/WalletEditorDialog' +import DetectDuplicateWalletDialog from 'components/DetectDuplicateWalletDialog' import { backToTop, RoutePath, @@ -63,6 +67,13 @@ const WalletSetting = ({ const [showEditDialog, setShowEditDialog] = useState(false) const [editWallet, setEditWallet] = useState('') const [notice, setNotice] = useState('') + const [showDetectDialog, setShowDetectDialog] = useState(false) + + const hasDupliacteWallets = useMemo(() => { + const extendedKeys = wallets.map(item => item.extendedKey) + const extendedKeySet = new Set(extendedKeys) + return extendedKeys.length > extendedKeySet.size + }, [wallets]) useEffect(() => { backToTop() @@ -124,6 +135,14 @@ const WalletSetting = ({ setNotice(t('settings.wallet-manager.edit-success')) }, [setShowEditDialog, setNotice]) + const handleDetect = useCallback(() => { + setShowDetectDialog(true) + }, [setShowDetectDialog]) + + const onDetectDialogClose = useCallback(() => { + setShowDetectDialog(false) + }, [setShowDetectDialog]) + return (
-
+
@@ -168,10 +187,16 @@ const WalletSetting = ({ trigger="click" showTriangle > - + + {hasDupliacteWallets ? ( + + ) : null}
+ {showDetectDialog ? : null} + setNotice('')} />
) diff --git a/packages/neuron-ui/src/components/WalletSetting/walletSetting.module.scss b/packages/neuron-ui/src/components/WalletSetting/walletSetting.module.scss index 9ad7f189cc..9905525747 100644 --- a/packages/neuron-ui/src/components/WalletSetting/walletSetting.module.scss +++ b/packages/neuron-ui/src/components/WalletSetting/walletSetting.module.scss @@ -7,14 +7,19 @@ padding: 0 0 5px 8px; } -.addWrap { +.actionWrap { position: relative; - display: inline-block; + display: flex; + gap: 24px; margin: 16px 0 0 14px; } -.addBtn { +.actionBtn { @include icon-hover-button(); + svg { + width: 16px; + height: 16px; + } } .actions { diff --git a/packages/neuron-ui/src/components/WalletWizard/index.tsx b/packages/neuron-ui/src/components/WalletWizard/index.tsx index 15f1c79c71..4956ce1f47 100644 --- a/packages/neuron-ui/src/components/WalletWizard/index.tsx +++ b/packages/neuron-ui/src/components/WalletWizard/index.tsx @@ -18,6 +18,7 @@ import { } from 'utils' import i18n from 'utils/i18n' import MnemonicInput from 'widgets/MnemonicInput' +import ReplaceDuplicateWalletDialog, { useReplaceDuplicateWallet } from 'components/ReplaceDuplicateWalletDialog' import Alert from 'widgets/Alert' import { Loading } from 'widgets/Icons/icon' import TextField from 'widgets/TextField' @@ -49,6 +50,10 @@ const importWalletWithMnemonic = (params: Controller.ImportMnemonicParams) => (n importedWalletDialogShown.setStatus(res.result.id, true) navigate(RoutePath.Overview) } else if (res.status > 0) { + if (res.status === ErrorCode.ImportingExitingWallet) { + throw res + } + showErrorMessage(i18n.t(`messages.error`), i18n.t(`messages.codes.${res.status}`)) } else if (res.message) { const msg = typeof res.message === 'string' ? res.message : res.message.content || '' @@ -315,6 +320,8 @@ const Submission = ({ state = initState, wallets = [], dispatch }: WizardElement const [t] = useTranslation() const message = 'wizard.set-wallet-name-and-password' + const { onImportingExitingWalletError, dialogProps } = useReplaceDuplicateWallet() + const isNameUnused = useMemo(() => name && !wallets.find(w => w.name === name), [name, wallets]) const isPwdComplex = useMemo(() => { try { @@ -372,7 +379,13 @@ const Submission = ({ state = initState, wallets = [], dispatch }: WizardElement if (type === MnemonicAction.Create) { createWalletWithMnemonic(p)(navigate).finally(() => closeDialog()) } else { - importWalletWithMnemonic(p)(navigate).finally(() => closeDialog()) + importWalletWithMnemonic(p)(navigate) + .catch(error => { + onImportingExitingWalletError(error.message) + }) + .finally(() => { + closeDialog() + }) } }, 0) }, @@ -380,52 +393,55 @@ const Submission = ({ state = initState, wallets = [], dispatch }: WizardElement ) return ( -
- {type === MnemonicAction.Create && ( -
- {[0, 1, 2].map(v => ( -
- ))} + <> + + {type === MnemonicAction.Create && ( +
+ {[0, 1, 2].map(v => ( +
+ ))} +
+ )} +
{t(message)}
+ {submissionInputs.map((input, idx) => ( +
+ +
+ ))} +
+ + {t('wizard.new-name')} + + + {t('wizard.complex-password')} + + + {t('wizard.same-password')} +
- )} -
{t(message)}
- {submissionInputs.map((input, idx) => ( -
- +
+ +
- ))} -
- - {t('wizard.new-name')} - - - {t('wizard.complex-password')} - - - {t('wizard.same-password')} - -
-
- -
- - + + + + ) } diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 08c1ba3add..ed0d5534b4 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -183,7 +183,8 @@ "repeat-password": "Repeat Password", "finish-create": "Finish Creating", "creating-wallet": "Wallet is being preparing, please wait", - "add-one": "Add One More" + "add-one": "Add One More", + "detect-duplicate-wallets": "Detect Duplicate Wallets" }, "import-keystore": { "title": "Import Keystore File", @@ -382,6 +383,15 @@ "password": "Password", "wallet-detail": { "balance": "Balance" + }, + "importing-existing": { + "title": "Importing an existing wallet", + "detail": "You imported a wallet that already exists, choose to replace the existing wallet or cancel the import.", + "replace": "Replace" + }, + "detected-duplicate": { + "title": "Detected duplicate wallets", + "detail": "You have duplicate wallets in your account and it is recommended that you choose to keep the wallet that needs to be unique to make it less difficult to manage." } }, "network": { diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 66ecd3fc35..b183ae957b 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -177,7 +177,8 @@ "repeat-password": "重復密碼", "finish-create": "完成創建", "creating-wallet": "錢包準備中,請稍後", - "add-one": "添加錢包" + "add-one": "添加錢包", + "detect-duplicate-wallets": "檢測重複錢包" }, "import-keystore": { "title": "導入 Keystore 文件", @@ -377,6 +378,15 @@ "password": "密碼", "wallet-detail": { "balance": "餘額" + }, + "importing-existing": { + "title": "導入已存在錢包", + "detail": "您導入了一個已經存在的錢包,請選擇替換現有錢包或取消導入。", + "replace": "替換" + }, + "detected-duplicate": { + "title": "檢測到重複的錢包", + "detail": "您的帳戶中有重複的錢包,建議您選擇保留需要獨一無二的錢包,以便於更輕鬆地管理。" } }, "network": { diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 7d24c9dc24..4cd237a5a0 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -176,7 +176,8 @@ "repeat-password": "重复密码", "finish-create": "完成创建", "creating-wallet": "钱包准备中,请稍后", - "add-one": "添加钱包" + "add-one": "添加钱包", + "detect-duplicate-wallets": "检测重复钱包" }, "import-keystore": { "title": "导入 Keystore 文件", @@ -375,6 +376,15 @@ "password": "密码", "wallet-detail": { "balance": "余额" + }, + "importing-existing": { + "title": "导入已存在钱包", + "detail": "您导入了一个已经存在的钱包,请选择替换现有钱包或取消导入。", + "replace": "替换" + }, + "detected-duplicate": { + "title": "检测到重复的钱包", + "detail": "您的账户中有重复的钱包,建议您选择保留需要独一无二的钱包,以便于更轻松地管理。" } }, "network": { diff --git a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts index 3ca2ffdff6..703ed1bcb9 100644 --- a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts +++ b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts @@ -59,6 +59,7 @@ type Action = | 'create-wallet' | 'update-wallet' | 'delete-wallet' + | 'replace-wallet' | 'backup-wallet' | 'get-all-addresses' | 'update-address-description' diff --git a/packages/neuron-ui/src/services/remote/wallets.ts b/packages/neuron-ui/src/services/remote/wallets.ts index 89da4ef168..3b3b6916c3 100644 --- a/packages/neuron-ui/src/services/remote/wallets.ts +++ b/packages/neuron-ui/src/services/remote/wallets.ts @@ -8,6 +8,7 @@ export const importKeystore = remoteApi('import export const createWallet = remoteApi('create-wallet') export const updateWallet = remoteApi('update-wallet') export const deleteWallet = remoteApi('delete-wallet') +export const replaceWallet = remoteApi('replace-wallet') export const backupWallet = remoteApi('backup-wallet') export const getAddressesByWalletID = remoteApi('get-all-addresses') export const updateAddressDescription = diff --git a/packages/neuron-ui/src/states/init/wallet.ts b/packages/neuron-ui/src/states/init/wallet.ts index 0de42e6d8e..617f6276c7 100644 --- a/packages/neuron-ui/src/states/init/wallet.ts +++ b/packages/neuron-ui/src/states/init/wallet.ts @@ -5,6 +5,7 @@ export const emptyWallet: State.Wallet = { id: '', balance: '0', addresses: [], + extendedKey: '', } const wallet = currentWallet.load() @@ -17,6 +18,7 @@ export const walletState: State.Wallet = { device: wallet?.device, isHD: wallet?.isHD, isWatchOnly: wallet?.isWatchOnly, + extendedKey: '', } export default walletState diff --git a/packages/neuron-ui/src/stories/Navbar.stories.tsx b/packages/neuron-ui/src/stories/Navbar.stories.tsx index f4d5b24c27..0e75b2d797 100644 --- a/packages/neuron-ui/src/stories/Navbar.stories.tsx +++ b/packages/neuron-ui/src/stories/Navbar.stories.tsx @@ -3,7 +3,7 @@ import Navbar from 'containers/Navbar' import { withRouter } from 'storybook-addon-react-router-v6' import { initStates } from 'states' -const wallets: State.WalletIdentity[] = [{ id: 'wallet id', name: 'wallet name' }] +const wallets: State.WalletIdentity[] = [{ id: 'wallet id', name: 'wallet name', extendedKey: '' }] const meta: Meta = { component: Navbar, diff --git a/packages/neuron-ui/src/stories/PasswordRequest.stories.tsx b/packages/neuron-ui/src/stories/PasswordRequest.stories.tsx index 0b28b821f6..1bf2f6f8ed 100644 --- a/packages/neuron-ui/src/stories/PasswordRequest.stories.tsx +++ b/packages/neuron-ui/src/stories/PasswordRequest.stories.tsx @@ -22,7 +22,7 @@ const states: { [title: string]: State.AppWithNeuronWallet } = { ...initStates, settings: { ...initStates.settings, - wallets: [{ id: '1', name: 'test wallet' }], + wallets: [{ id: '1', name: 'test wallet', extendedKey: '' }], }, app: { ...initStates.app, @@ -36,7 +36,7 @@ const states: { [title: string]: State.AppWithNeuronWallet } = { ...initStates, settings: { ...initStates.settings, - wallets: [{ id: '1', name: 'test wallet' }], + wallets: [{ id: '1', name: 'test wallet', extendedKey: '' }], }, app: { ...initStates.app, @@ -50,7 +50,7 @@ const states: { [title: string]: State.AppWithNeuronWallet } = { ...initStates, settings: { ...initStates.settings, - wallets: [{ id: '1', name: 'test wallet' }], + wallets: [{ id: '1', name: 'test wallet', extendedKey: '' }], }, app: { ...initStates.app, @@ -64,7 +64,7 @@ const states: { [title: string]: State.AppWithNeuronWallet } = { ...initStates, settings: { ...initStates.settings, - wallets: [{ id: '1', name: 'test wallet' }], + wallets: [{ id: '1', name: 'test wallet', extendedKey: '' }], }, app: { ...initStates.app, @@ -78,7 +78,7 @@ const states: { [title: string]: State.AppWithNeuronWallet } = { ...initStates, settings: { ...initStates.settings, - wallets: [{ id: '1', name: 'test wallet' }], + wallets: [{ id: '1', name: 'test wallet', extendedKey: '' }], }, app: { ...initStates.app, @@ -92,7 +92,7 @@ const states: { [title: string]: State.AppWithNeuronWallet } = { ...initStates, settings: { ...initStates.settings, - wallets: [{ id: '1', name: 'test wallet' }], + wallets: [{ id: '1', name: 'test wallet', extendedKey: '' }], }, app: { ...initStates.app, diff --git a/packages/neuron-ui/src/stories/WalletSetting.stories.tsx b/packages/neuron-ui/src/stories/WalletSetting.stories.tsx index 219204eb88..208a16bc7c 100644 --- a/packages/neuron-ui/src/stories/WalletSetting.stories.tsx +++ b/packages/neuron-ui/src/stories/WalletSetting.stories.tsx @@ -9,10 +9,12 @@ const states: { [title: string]: State.WalletIdentity[] } = { { id: '1', name: 'Wallet 1', + extendedKey: '', }, { id: '2', name: 'Wallet 2', + extendedKey: '', }, ], } diff --git a/packages/neuron-ui/src/styles/mixin.scss b/packages/neuron-ui/src/styles/mixin.scss index 1d8ea27ba7..9dfa2f6de3 100644 --- a/packages/neuron-ui/src/styles/mixin.scss +++ b/packages/neuron-ui/src/styles/mixin.scss @@ -161,6 +161,7 @@ color: $normalColor !important; font-weight: 500; font-size: 14px; + line-height: 20px; cursor: pointer; display: flex; align-items: center; diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 3916fb842d..3812ee89bd 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -201,6 +201,7 @@ declare namespace State { device?: DeviceInfo isHD?: boolean isWatchOnly?: boolean + extendedKey: string } interface DeviceInfo { diff --git a/packages/neuron-ui/src/types/Controller/index.d.ts b/packages/neuron-ui/src/types/Controller/index.d.ts index 012dd89cac..36278417b9 100644 --- a/packages/neuron-ui/src/types/Controller/index.d.ts +++ b/packages/neuron-ui/src/types/Controller/index.d.ts @@ -48,6 +48,11 @@ declare namespace Controller { password: string } + interface ReplaceWalletParams { + id: string + tmpId: string + } + interface BackupWalletParams { id: string password: string diff --git a/packages/neuron-ui/src/utils/enums.ts b/packages/neuron-ui/src/utils/enums.ts index ce71adb71e..934a74a86c 100644 --- a/packages/neuron-ui/src/utils/enums.ts +++ b/packages/neuron-ui/src/utils/enums.ts @@ -107,6 +107,7 @@ export enum ErrorCode { DeviceInSleep = 501, // active warning WaitForFullySynced = 600, + ImportingExitingWallet = 118, } export enum SyncStatus { diff --git a/packages/neuron-ui/src/widgets/Icons/Detect.svg b/packages/neuron-ui/src/widgets/Icons/Detect.svg new file mode 100644 index 0000000000..30a972e89e --- /dev/null +++ b/packages/neuron-ui/src/widgets/Icons/Detect.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/neuron-ui/src/widgets/Icons/icon.tsx b/packages/neuron-ui/src/widgets/Icons/icon.tsx index e58d9aa183..ecdd2b8c2e 100644 --- a/packages/neuron-ui/src/widgets/Icons/icon.tsx +++ b/packages/neuron-ui/src/widgets/Icons/icon.tsx @@ -52,6 +52,10 @@ import { ReactComponent as LockSvg } from './Lock.svg' import { ReactComponent as LockCellSvg } from './LockCell.svg' import { ReactComponent as UnLockSvg } from './Unlock.svg' import { ReactComponent as ConsumeSvg } from './Consume.svg' +import { ReactComponent as DetectSvg } from './Detect.svg' +import { ReactComponent as DeleteSvg } from './Delete.svg' +import { ReactComponent as ImportKeystoreSvg } from './SoftWalletImportKeystore.svg' +import { ReactComponent as ImportHardwareSvg } from './HardWalletImport.svg' import styles from './icon.module.scss' @@ -120,3 +124,7 @@ export const Lock = WrapSvg(LockSvg, styles.withTheme) export const LockCell = WrapSvg(LockCellSvg) export const UnLock = WrapSvg(UnLockSvg) export const Consume = WrapSvg(ConsumeSvg) +export const Detect = WrapSvg(DetectSvg) +export const Delete = WrapSvg(DeleteSvg) +export const ImportKeystore = WrapSvg(ImportKeystoreSvg) +export const ImportHardware = WrapSvg(ImportHardwareSvg) diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index 45f5f4c8f2..e0597c4762 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -349,6 +349,10 @@ export default class ApiController { return this.#walletsController.delete({ id, password }) }) + handle('replace-wallet', async (_, { id = '', tmpId = '' }) => { + return this.#walletsController.replaceWallet(id, tmpId) + }) + handle('backup-wallet', async (_, { id = '', password = '' }) => { return this.#walletsController.backup({ id, password }) }) diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts index 1870a1e25c..8e151777f7 100644 --- a/packages/neuron-wallet/src/controllers/wallets.ts +++ b/packages/neuron-wallet/src/controllers/wallets.ts @@ -42,7 +42,7 @@ export default class WalletsController { } return { status: ResponseCode.Success, - result: wallets.map(({ name, id, device }) => ({ name, id, device })), + result: wallets.map(({ name, id, device, extendedKey }) => ({ name, id, device, extendedKey })), } } @@ -642,6 +642,15 @@ export default class WalletsController { } } + public async replaceWallet(id: string, tmpId: string): Promise> { + const walletsService = WalletsService.getInstance() + await walletsService.replace(id, tmpId) + + return { + status: ResponseCode.Success, + } + } + // Important: Check password before calling this, unless it's backing up a watch only wallet. private async backupWallet(id: string): Promise> { const walletsService = WalletsService.getInstance() diff --git a/packages/neuron-wallet/src/exceptions/wallet.ts b/packages/neuron-wallet/src/exceptions/wallet.ts index fc69196c86..b400f7410d 100644 --- a/packages/neuron-wallet/src/exceptions/wallet.ts +++ b/packages/neuron-wallet/src/exceptions/wallet.ts @@ -78,6 +78,13 @@ export class WalletFunctionNotSupported extends Error { } } +export class ImportingExitingWallet extends Error { + public code = 118 + constructor(errorStr: string) { + super(errorStr) + } +} + export default { WalletNotFound, CurrentWalletNotSet, @@ -88,4 +95,5 @@ export default { LiveCapacityNotEnough, CapacityNotEnoughForChange, InvalidKeystore, + ImportingExitingWallet, } diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index ac993ce4d0..85ed98e4cd 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -1,5 +1,5 @@ import { v4 as uuid } from 'uuid' -import { WalletNotFound, IsRequired, UsedName, WalletFunctionNotSupported } from '../exceptions' +import { WalletNotFound, IsRequired, UsedName, WalletFunctionNotSupported, ImportingExitingWallet } from '../exceptions' import Store from '../models/store' import Keystore from '../models/keys/keystore' import WalletDeletedSubject from '../models/subjects/wallet-deleted-subject' @@ -277,6 +277,7 @@ export default class WalletService { private listStore: Store // Save wallets (meta info except keystore, which is persisted separately) private walletsKey = 'wallets' private currentWalletKey = 'current' + private tmpWallet: Wallet | undefined public static getInstance = () => { if (!WalletService.instance) { @@ -374,7 +375,14 @@ export default class WalletService { throw new UsedName('Wallet') } - const wallet = this.fromJSON({ ...props, id: uuid() }) + const id = uuid() + + const wallet = this.fromJSON({ ...props, id }) + + if (this.getAll().find(item => item.extendedKey === props.extendedKey)) { + this.tmpWallet = wallet + throw new ImportingExitingWallet(JSON.stringify({ extendedKey: props.extendedKey, id })) + } if (!wallet.isHardware()) { wallet.saveKeystore(props.keystore!) @@ -386,6 +394,41 @@ export default class WalletService { return wallet } + public replace = async (id: string, tmpId: string) => { + const wallet = this.get(id) + if (!wallet || !this.tmpWallet) { + throw new WalletNotFound(id) + } + + const tmp = this.tmpWallet?.toJSON() + if (tmpId !== tmp.id) { + throw new WalletNotFound(id) + } + if (wallet.toJSON().extendedKey !== tmp.extendedKey) { + throw new Error('The wallets are not the same and cannot be replaced.') + } + + const wallets = this.getAll() + + this.listStore.writeSync(this.walletsKey, [...wallets, tmp]) + + const current = this.getCurrent() + const currentID = current ? current.id : '' + + if (currentID === id) { + this.setCurrent(tmp.id) + } + + await AddressService.deleteByWalletId(id) + + const newWallets = wallets.filter(w => w.id !== id) + this.listStore.writeSync(this.walletsKey, [...newWallets, tmp]) + + if (!wallet.isHardware()) { + wallet.deleteKeystore() + } + } + public update = (id: string, props: Omit) => { const wallets = this.getAll() const index = wallets.findIndex((w: WalletProperties) => w.id === id) From 8d4cd139a1ec44a747ef35d6f7ae7fc3e9de017d Mon Sep 17 00:00:00 2001 From: devchenyan Date: Sun, 24 Dec 2023 10:36:44 +0800 Subject: [PATCH 02/13] fix --- .../src/components/DetectDuplicateWalletDialog/index.tsx | 6 +++--- .../src/components/ReplaceDuplicateWalletDialog/index.tsx | 6 +++--- packages/neuron-ui/src/components/WalletSetting/index.tsx | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx index 28b7557368..50765c0ddc 100644 --- a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx @@ -32,8 +32,8 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { }, [wallets]) const handleGroupChange = useCallback( - value => { - const [extendedKey, id] = value.split('_') + (checked: string | number) => { + const [extendedKey, id] = (checked as string).split('_') const list: string[] = [] wallets.forEach(item => { if (item.extendedKey === extendedKey) { @@ -98,7 +98,7 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => {
{groups.map(group => ( wallets.filter(item => item.extendedKey === extendedKey), [wallets, extendedKey]) const handleGroupChange = useCallback( - value => { - setSelectedId(value) + (checked: string | number) => { + setSelectedId(checked as string) }, [setSelectedId] ) @@ -107,7 +107,7 @@ const ReplaceDuplicateWalletDialog = ({

{t('settings.wallet-manager.importing-existing.detail')}

{ + const hasDuplicateWallets = useMemo(() => { const extendedKeys = wallets.map(item => item.extendedKey) const extendedKeySet = new Set(extendedKeys) return extendedKeys.length > extendedKeySet.size @@ -192,7 +192,7 @@ const WalletSetting = ({ - {hasDupliacteWallets ? ( + {hasDuplicateWallets ? ( From b292c8df9fe0c33d311ef95f6282ac7ecd197723 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Sun, 24 Dec 2023 12:15:51 +0800 Subject: [PATCH 03/13] fix --- .../components/DetectDuplicateWalletDialog/index.tsx | 5 ++--- packages/neuron-wallet/src/services/wallets.ts | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx index 50765c0ddc..3f6c49117c 100644 --- a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx @@ -69,7 +69,7 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { }) } - let ids = deletableWallets + let ids = [...deletableWallets] if (deletableWallets.includes(currentID)) { ids = ids.filter(item => item !== currentID) ids.push(currentID) @@ -80,9 +80,8 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { // eslint-disable-next-line no-await-in-loop await getRequest(id) } - onClose() - }, [deletableWallets, requestPassword, onClose, dispatch]) + }, [wallets, deletableWallets, requestPassword, onClose, dispatch]) return ( <> diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 85ed98e4cd..19be776ade 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -379,15 +379,15 @@ export default class WalletService { const wallet = this.fromJSON({ ...props, id }) + if (!wallet.isHardware()) { + wallet.saveKeystore(props.keystore!) + } + if (this.getAll().find(item => item.extendedKey === props.extendedKey)) { this.tmpWallet = wallet throw new ImportingExitingWallet(JSON.stringify({ extendedKey: props.extendedKey, id })) } - if (!wallet.isHardware()) { - wallet.saveKeystore(props.keystore!) - } - this.listStore.writeSync(this.walletsKey, [...this.getAll(), wallet.toJSON()]) this.setCurrent(wallet.id) @@ -402,7 +402,7 @@ export default class WalletService { const tmp = this.tmpWallet?.toJSON() if (tmpId !== tmp.id) { - throw new WalletNotFound(id) + throw new WalletNotFound(tmpId) } if (wallet.toJSON().extendedKey !== tmp.extendedKey) { throw new Error('The wallets are not the same and cannot be replaced.') From 3e2093272f1fba2774bc09b118bdb261f1889038 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Sun, 24 Dec 2023 14:21:19 +0800 Subject: [PATCH 04/13] fix: remove unused files --- .../WalletImportConflictDialog/hooks.ts | 81 ------------------- .../WalletImportConflictDialog/index.tsx | 80 ------------------ .../walletEditorDialog.module.scss | 3 - 3 files changed, 164 deletions(-) delete mode 100644 packages/neuron-ui/src/components/WalletImportConflictDialog/hooks.ts delete mode 100644 packages/neuron-ui/src/components/WalletImportConflictDialog/index.tsx delete mode 100755 packages/neuron-ui/src/components/WalletImportConflictDialog/walletEditorDialog.module.scss diff --git a/packages/neuron-ui/src/components/WalletImportConflictDialog/hooks.ts b/packages/neuron-ui/src/components/WalletImportConflictDialog/hooks.ts deleted file mode 100644 index 0a344b51cf..0000000000 --- a/packages/neuron-ui/src/components/WalletImportConflictDialog/hooks.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { useState, useMemo, useCallback } from 'react' -import { StateDispatch, updateWalletProperty } from 'states' -import { ErrorCode, ResponseCode, CONSTANTS } from 'utils' -import i18n from 'utils/i18n' - -const { MAX_WALLET_NAME_LENGTH } = CONSTANTS - -export const useWalletEditor = () => { - const [name, setName] = useState('') - const initialize = useCallback( - (initName: string = '') => { - setName(initName) - }, - [setName] - ) - return { - initialize, - name: { - value: name, - onChange: (e: React.SyntheticEvent) => { - const { value } = e.target as HTMLInputElement - setName(value) - }, - }, - } -} - -export const useInputs = ({ name }: ReturnType) => { - return useMemo( - () => [ - { - ...name, - label: i18n.t('settings.wallet-manager.edit-wallet.wallet-name'), - placeholder: i18n.t('settings.wallet-manager.edit-wallet.wallet-name'), - maxLength: MAX_WALLET_NAME_LENGTH, - }, - ], - [name] - ) -} - -export const useOnSubmit = ( - name: string, - id: string, - dispatch: StateDispatch, - disabled: boolean, - callback: () => void -) => { - return useCallback(() => { - if (disabled) { - return - } - updateWalletProperty({ - id, - name, - })(dispatch).then(status => { - if (status === ResponseCode.SUCCESS) { - callback() - } - }) - }, [name, id, dispatch, disabled]) -} - -export const useHint = (name: string, usedNames: string[], t: (key: string, opts: object) => string): string | null => { - return useMemo(() => { - if (name === '') { - return t(`messages.codes.${ErrorCode.FieldRequired}`, { fieldName: 'name' }) - } - if (usedNames.includes(name)) { - return t(`messages.codes.${ErrorCode.FieldUsed}`, { fieldName: 'name', fieldValue: name }) - } - return null - }, [name, usedNames, t]) -} - -export default { - useWalletEditor, - useInputs, - useOnSubmit, - useHint, -} diff --git a/packages/neuron-ui/src/components/WalletImportConflictDialog/index.tsx b/packages/neuron-ui/src/components/WalletImportConflictDialog/index.tsx deleted file mode 100644 index d32385f233..0000000000 --- a/packages/neuron-ui/src/components/WalletImportConflictDialog/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useEffect, useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import TextField from 'widgets/TextField' -import Dialog from 'widgets/Dialog' - -import { useState as useGlobalState, useDispatch } from 'states' - -import { ErrorCode } from 'utils' -import styles from './walletEditorDialog.module.scss' - -import { useHint, useOnSubmit, useInputs, useWalletEditor } from './hooks' - -const WalletNotFound = () => { - const [t] = useTranslation() - return ( -
-

{t(`messages.codes.${ErrorCode.FieldNotFound}`, { fieldName: 'wallet' })}

-
- ) -} - -const WalletEditorDialog = ({ - show, - onCancel, - onSuccess, - id, -}: { - show: boolean - onCancel: () => void - onSuccess: () => void - id: string -}) => { - const { - settings: { wallets = [] }, - } = useGlobalState() - const dispatch = useDispatch() - const [t] = useTranslation() - const wallet = useMemo(() => wallets.find(w => w.id === id), [id, wallets]) || { id: '', name: '' } - const usedNames = wallets.map(w => w.name).filter(w => w !== wallet.name) - - const editor = useWalletEditor() - const { initialize } = editor - - useEffect(() => { - initialize(wallet.name) - }, [initialize, wallet.name]) - - const inputs = useInputs(editor) - const hint = useHint(editor.name.value, usedNames, t) - const disabled = hint !== null || editor.name.value === wallet.name - - const onSubmit = useOnSubmit(editor.name.value, wallet.id, dispatch, disabled, onSuccess) - - return ( - - <> - {wallet.id ? ( -
- {inputs.map(item => ( - - ))} -
- ) : ( - - )} - -
- ) -} - -WalletEditorDialog.displayName = 'WalletEditorDialog' - -export default WalletEditorDialog diff --git a/packages/neuron-ui/src/components/WalletImportConflictDialog/walletEditorDialog.module.scss b/packages/neuron-ui/src/components/WalletImportConflictDialog/walletEditorDialog.module.scss deleted file mode 100755 index 78357b3428..0000000000 --- a/packages/neuron-ui/src/components/WalletImportConflictDialog/walletEditorDialog.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.content { - width: 648px; -} From 78e472630ebf7e261acd3afe7e4a48d6e7230cc7 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Sun, 24 Dec 2023 16:53:51 +0800 Subject: [PATCH 05/13] fix: test --- .../DetectDuplicateWalletDialog/index.tsx | 2 +- .../tests/services/wallets.test.ts | 57 ++++++++++++++++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx index 3f6c49117c..96df267317 100644 --- a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx @@ -52,7 +52,7 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { const onConfirm = useCallback(async () => { const getRequest = (id: string) => { - if (wallets.find(item => item.id === id && item.device)) { + if (wallets.find(item => (item.id === id && item.device) || item.isWatchOnly)) { return requestPassword({ walletID: id, action: 'delete-wallet' }) } return new Promise(resolve => { diff --git a/packages/neuron-wallet/tests/services/wallets.test.ts b/packages/neuron-wallet/tests/services/wallets.test.ts index 73600bbcf5..65fff97cf0 100644 --- a/packages/neuron-wallet/tests/services/wallets.test.ts +++ b/packages/neuron-wallet/tests/services/wallets.test.ts @@ -29,7 +29,7 @@ import Keystore from '../../src/models/keys/keystore' import initConnection from '../../src/database/chain/ormconfig' import { getConnection } from 'typeorm' import { when } from 'jest-when' -import { WalletFunctionNotSupported } from '../../src/exceptions/wallet' +import { WalletFunctionNotSupported, ImportingExitingWallet } from '../../src/exceptions/wallet' import { AddressType } from '../../src/models/keys/address' import { Manufacturer } from '../../src/services/hardware/common' import WalletService, { WalletProperties, Wallet } from '../../src/services/wallets' @@ -53,6 +53,7 @@ describe('wallet service', () => { let wallet2: WalletProperties let wallet3: WalletProperties let wallet4: WalletProperties + let wallet5: WalletProperties const fakePublicKey = 'keykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykeykey' const fakeChainCode = 'codecodecodecodecodecodecodecodecodecodecodecodecodecodecodecode' @@ -121,7 +122,7 @@ describe('wallet service', () => { wallet3 = { name: 'wallet-test3', id: '', - extendedKey: 'a', + extendedKey: 'b', keystore: new Keystore( { cipher: 'wallet3', @@ -155,6 +156,29 @@ describe('wallet service', () => { addressType: AddressType.Receiving, }, } + + wallet5 = { + name: 'wallet-test5', + id: '', + extendedKey: 'a', + keystore: new Keystore( + { + cipher: 'wallet5', + cipherparams: { iv: 'wallet1' }, + ciphertext: 'wallet5', + kdf: '5', + kdfparams: { + dklen: 1, + n: 1, + r: 1, + p: 1, + salt: '1', + }, + mac: '5', + }, + '5' + ), + } }) afterEach(() => { @@ -424,7 +448,7 @@ describe('wallet service', () => { }) expect(stubbedGenerateAndSaveForExtendedKeyQueue).toHaveBeenCalledWith({ walletId: createdWallet3.id, - extendedKey: expect.objectContaining({ publicKey: 'a' }), + extendedKey: expect.objectContaining({ publicKey: 'b' }), isImporting: false, receivingAddressCount: 20, changeAddressCount: 10, @@ -483,4 +507,31 @@ describe('wallet service', () => { expect(checkAndGenerateAddressesMock).toHaveBeenCalledTimes(1) }) }) + + describe('ImportingExitingWallet', () => { + beforeEach(() => { + walletService.create(wallet2) + }) + it('create an exiting wallet', () => { + expect(() => walletService.create(wallet5)).toThrowError(ImportingExitingWallet) + }) + }) + + describe('ReplaceWallet', () => { + let createdWallet2: any + beforeEach(() => { + createdWallet2 = walletService.create(wallet2) + }) + it('replace an exiting wallet', async () => { + try { + walletService.create(wallet5) + } catch (error) { + const { extendedKey, id } = JSON.parse(error.message) + await walletService.replace(createdWallet2.id, id) + expect(extendedKey).toBe('a') + expect(() => walletService.get(createdWallet2.id)).toThrowError() + expect(walletService.get(id).name).toBe(wallet5.name) + } + }) + }) }) From 4b4c1320989e657131da5b206117d76f55c24d2d Mon Sep 17 00:00:00 2001 From: devchenyan Date: Tue, 2 Jan 2024 00:39:00 +0800 Subject: [PATCH 06/13] fix: Same wallet import optimization --- .../DetectDuplicateWalletDialog/index.tsx | 54 +++++++++---------- .../ReplaceDuplicateWalletDialog/index.tsx | 20 +++---- .../neuron-ui/src/types/Controller/index.d.ts | 4 +- .../src/widgets/RadioGroup/index.tsx | 4 +- packages/neuron-wallet/src/controllers/api.ts | 4 +- .../neuron-wallet/src/controllers/wallets.ts | 4 +- .../neuron-wallet/src/services/wallets.ts | 35 ++++++------ 7 files changed, 58 insertions(+), 67 deletions(-) diff --git a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx index 96df267317..efd5277ed3 100644 --- a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx @@ -4,7 +4,6 @@ import Dialog from 'widgets/Dialog' import RadioGroup from 'widgets/RadioGroup' import { useState as useGlobalState, useDispatch, AppActions } from 'states' import { requestPassword } from 'services/remote' -import PasswordRequest from 'components/PasswordRequest' import styles from './detectDuplicateWalletDialog.module.scss' const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { @@ -84,35 +83,32 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { }, [wallets, deletableWallets, requestPassword, onClose, dispatch]) return ( - <> - -
-

{t('settings.wallet-manager.detected-duplicate.detail')}

-
- {groups.map(group => ( - ({ - value: `${wallet.extendedKey}_${wallet.id}`, - label: {wallet.name}, - }))} - /> - ))} -
+ +
+

{t('settings.wallet-manager.detected-duplicate.detail')}

+
+ {groups.map(group => ( + ({ + value: `${wallet.extendedKey}_${wallet.id}`, + label: {wallet.name}, + }))} + /> + ))}
-
- - +
+
) } diff --git a/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx index 263cd35920..0ba08b2ba3 100644 --- a/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx @@ -10,11 +10,11 @@ import styles from './replaceDuplicateWalletDialog.module.scss' const useReplaceDuplicateWallet = () => { const [extendedKey, setExtendedKey] = useState('') - const [tmpId, setTmpId] = useState('') + const [importedWalletId, setImportedWalletId] = useState('') const onClose = useCallback(() => { - setTmpId('') - }, [setTmpId]) + setImportedWalletId('') + }, [setImportedWalletId]) const onImportingExitingWalletError = ( message: @@ -29,14 +29,14 @@ const useReplaceDuplicateWallet = () => { if (msg) { const obj = JSON.parse(msg) setExtendedKey(obj.extendedKey) - setTmpId(obj.id) + setImportedWalletId(obj.id) } } catch (error) { onClose() } } - const show = useMemo(() => !!extendedKey && !!tmpId, [tmpId, extendedKey]) + const show = useMemo(() => !!extendedKey && !!importedWalletId, [importedWalletId, extendedKey]) return { onImportingExitingWalletError, @@ -44,7 +44,7 @@ const useReplaceDuplicateWallet = () => { show, onClose, extendedKey, - tmpId, + importedWalletId, }, } } @@ -53,12 +53,12 @@ const ReplaceDuplicateWalletDialog = ({ show, onClose, extendedKey, - tmpId, + importedWalletId, }: { show: boolean onClose: () => void extendedKey: string - tmpId: string + importedWalletId: string }) => { const { settings: { wallets = [] }, @@ -79,8 +79,8 @@ const ReplaceDuplicateWalletDialog = ({ const onConfirm = useCallback(async () => { replaceWallet({ - id: selectedId, - tmpId, + existingWalletId: selectedId, + importedWalletId, }) .then(res => { if (isSuccessResponse(res)) { diff --git a/packages/neuron-ui/src/types/Controller/index.d.ts b/packages/neuron-ui/src/types/Controller/index.d.ts index 31d3b380d3..816a0b6591 100644 --- a/packages/neuron-ui/src/types/Controller/index.d.ts +++ b/packages/neuron-ui/src/types/Controller/index.d.ts @@ -49,8 +49,8 @@ declare namespace Controller { } interface ReplaceWalletParams { - id: string - tmpId: string + existingWalletId: string + importedWalletId: string } interface BackupWalletParams { diff --git a/packages/neuron-ui/src/widgets/RadioGroup/index.tsx b/packages/neuron-ui/src/widgets/RadioGroup/index.tsx index 0355b90693..0268abc203 100644 --- a/packages/neuron-ui/src/widgets/RadioGroup/index.tsx +++ b/packages/neuron-ui/src/widgets/RadioGroup/index.tsx @@ -12,8 +12,8 @@ export interface RadioGroupOptions { export interface RadioGroupProps { options: RadioGroupOptions[] onChange?: (arg: string) => void - defaultValue?: string | number - value?: string | number + defaultValue?: string + value?: string itemClassName?: string className?: string inputIdPrefix?: string diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index 127ff43dc6..2cdeec6aa5 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -349,8 +349,8 @@ export default class ApiController { return this.#walletsController.delete({ id, password }) }) - handle('replace-wallet', async (_, { id = '', tmpId = '' }) => { - return this.#walletsController.replaceWallet(id, tmpId) + handle('replace-wallet', async (_, { existingWalletId = '', importedWalletId = '' }) => { + return this.#walletsController.replaceWallet(existingWalletId, importedWalletId) }) handle('backup-wallet', async (_, { id = '', password = '' }) => { diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts index 1c867519d3..61b972a0b4 100644 --- a/packages/neuron-wallet/src/controllers/wallets.ts +++ b/packages/neuron-wallet/src/controllers/wallets.ts @@ -632,9 +632,9 @@ export default class WalletsController { } } - public async replaceWallet(id: string, tmpId: string): Promise> { + public async replaceWallet(existingWalletId: string, importedWalletId: string): Promise> { const walletsService = WalletsService.getInstance() - await walletsService.replace(id, tmpId) + await walletsService.replace(existingWalletId, importedWalletId) return { status: ResponseCode.Success, diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 19be776ade..8dc3900402 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -277,7 +277,7 @@ export default class WalletService { private listStore: Store // Save wallets (meta info except keystore, which is persisted separately) private walletsKey = 'wallets' private currentWalletKey = 'current' - private tmpWallet: Wallet | undefined + private importedWallet: Wallet | undefined public static getInstance = () => { if (!WalletService.instance) { @@ -384,7 +384,7 @@ export default class WalletService { } if (this.getAll().find(item => item.extendedKey === props.extendedKey)) { - this.tmpWallet = wallet + this.importedWallet = wallet throw new ImportingExitingWallet(JSON.stringify({ extendedKey: props.extendedKey, id })) } @@ -394,35 +394,30 @@ export default class WalletService { return wallet } - public replace = async (id: string, tmpId: string) => { - const wallet = this.get(id) - if (!wallet || !this.tmpWallet) { - throw new WalletNotFound(id) + public replace = async (existingWalletId: string, importedWalletId: string) => { + const wallet = this.get(existingWalletId) + if (!wallet || !this.importedWallet) { + throw new WalletNotFound(existingWalletId) } - const tmp = this.tmpWallet?.toJSON() - if (tmpId !== tmp.id) { - throw new WalletNotFound(tmpId) + const newWallet = this.importedWallet?.toJSON() + if (importedWalletId !== newWallet.id) { + throw new WalletNotFound(importedWalletId) } - if (wallet.toJSON().extendedKey !== tmp.extendedKey) { + if (wallet.toJSON().extendedKey !== newWallet.extendedKey) { throw new Error('The wallets are not the same and cannot be replaced.') } const wallets = this.getAll() - this.listStore.writeSync(this.walletsKey, [...wallets, tmp]) + this.listStore.writeSync(this.walletsKey, [...wallets, newWallet]) - const current = this.getCurrent() - const currentID = current ? current.id : '' + this.setCurrent(newWallet.id) - if (currentID === id) { - this.setCurrent(tmp.id) - } - - await AddressService.deleteByWalletId(id) + await AddressService.deleteByWalletId(existingWalletId) - const newWallets = wallets.filter(w => w.id !== id) - this.listStore.writeSync(this.walletsKey, [...newWallets, tmp]) + const newWallets = wallets.filter(w => w.id !== existingWalletId) + this.listStore.writeSync(this.walletsKey, [...newWallets, newWallet]) if (!wallet.isHardware()) { wallet.deleteKeystore() From 7e8c2761c59398cfc23b3bbb774eb5abc99df439 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Tue, 2 Jan 2024 00:41:39 +0800 Subject: [PATCH 07/13] fix --- .../src/components/DetectDuplicateWalletDialog/index.tsx | 6 +++--- .../src/components/ReplaceDuplicateWalletDialog/index.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx index efd5277ed3..e4b280436f 100644 --- a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx @@ -16,9 +16,9 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { const [t] = useTranslation() const groups = useMemo(() => { - const obj = {} as { + const obj: { [key: string]: State.WalletIdentity[] - } + } = {} wallets.forEach(item => { if (item.extendedKey in obj) { obj[item.extendedKey].push(item) @@ -31,7 +31,7 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { }, [wallets]) const handleGroupChange = useCallback( - (checked: string | number) => { + (checked: string) => { const [extendedKey, id] = (checked as string).split('_') const list: string[] = [] wallets.forEach(item => { diff --git a/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx index 0ba08b2ba3..2cbe6c85dc 100644 --- a/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx @@ -71,7 +71,7 @@ const ReplaceDuplicateWalletDialog = ({ const group = useMemo(() => wallets.filter(item => item.extendedKey === extendedKey), [wallets, extendedKey]) const handleGroupChange = useCallback( - (checked: string | number) => { + (checked: string) => { setSelectedId(checked as string) }, [setSelectedId] From 5c7d854f5f6b731278bd80940ea190b2e103b4ce Mon Sep 17 00:00:00 2001 From: devchenyan Date: Tue, 16 Jan 2024 22:16:47 +0800 Subject: [PATCH 08/13] fix: comments --- .../DetectDuplicateWalletDialog/index.tsx | 16 ++++++++-------- .../components/ImportHardware/name-wallet.tsx | 2 +- .../src/components/ImportKeystore/index.tsx | 2 +- .../src/components/WalletWizard/index.tsx | 2 +- packages/neuron-ui/src/utils/enums.ts | 2 +- packages/neuron-wallet/src/exceptions/wallet.ts | 4 ++-- packages/neuron-wallet/src/services/wallets.ts | 4 ++-- .../neuron-wallet/tests/services/wallets.test.ts | 6 +++--- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx index e4b280436f..64d0a1bdc8 100644 --- a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx @@ -12,7 +12,7 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { settings: { wallets = [] }, } = useGlobalState() const dispatch = useDispatch() - const [deletableWallets, setDeletableWallets] = useState([]) + const [duplicatedWallets, setDuplicatedWallets] = useState([]) const [t] = useTranslation() const groups = useMemo(() => { @@ -39,14 +39,14 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { if (item.id !== id) { list.push(item.id) } - } else if (deletableWallets.includes(item.id)) { + } else if (duplicatedWallets.includes(item.id)) { list.push(item.id) } }) - setDeletableWallets(list) + setDuplicatedWallets(list) }, - [wallets, deletableWallets, setDeletableWallets] + [wallets, duplicatedWallets, setDuplicatedWallets] ) const onConfirm = useCallback(async () => { @@ -68,8 +68,8 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { }) } - let ids = [...deletableWallets] - if (deletableWallets.includes(currentID)) { + let ids = [...duplicatedWallets] + if (duplicatedWallets.includes(currentID)) { ids = ids.filter(item => item !== currentID) ids.push(currentID) } @@ -80,7 +80,7 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { await getRequest(id) } onClose() - }, [wallets, deletableWallets, requestPassword, onClose, dispatch]) + }, [wallets, duplicatedWallets, requestPassword, onClose, dispatch]) return ( void }) => { title={t('settings.wallet-manager.detected-duplicate.title')} onCancel={onClose} onConfirm={onConfirm} - disabled={deletableWallets.length === 0} + disabled={duplicatedWallets.length === 0} >

{t('settings.wallet-manager.detected-duplicate.detail')}

diff --git a/packages/neuron-ui/src/components/ImportHardware/name-wallet.tsx b/packages/neuron-ui/src/components/ImportHardware/name-wallet.tsx index b8951337ae..4c555b2cce 100644 --- a/packages/neuron-ui/src/components/ImportHardware/name-wallet.tsx +++ b/packages/neuron-ui/src/components/ImportHardware/name-wallet.tsx @@ -48,7 +48,7 @@ const NameWallet = ({ importedWalletDialogShown.setStatus(res.result.id, true) } } else { - if (res.status === ErrorCode.ImportingExitingWallet) { + if (res.status === ErrorCode.DuplicateImportWallet) { onImportingExitingWalletError(res.message) return } diff --git a/packages/neuron-ui/src/components/ImportKeystore/index.tsx b/packages/neuron-ui/src/components/ImportKeystore/index.tsx index 732aeecb37..e3b71e74ac 100644 --- a/packages/neuron-ui/src/components/ImportKeystore/index.tsx +++ b/packages/neuron-ui/src/components/ImportKeystore/index.tsx @@ -117,7 +117,7 @@ const ImportKeystore = () => { throw new PasswordIncorrectException() } - if (res.status === ErrorCode.ImportingExitingWallet) { + if (res.status === ErrorCode.DuplicateImportWallet) { onImportingExitingWalletError(res.message) return } diff --git a/packages/neuron-ui/src/components/WalletWizard/index.tsx b/packages/neuron-ui/src/components/WalletWizard/index.tsx index 4956ce1f47..384492854a 100644 --- a/packages/neuron-ui/src/components/WalletWizard/index.tsx +++ b/packages/neuron-ui/src/components/WalletWizard/index.tsx @@ -50,7 +50,7 @@ const importWalletWithMnemonic = (params: Controller.ImportMnemonicParams) => (n importedWalletDialogShown.setStatus(res.result.id, true) navigate(RoutePath.Overview) } else if (res.status > 0) { - if (res.status === ErrorCode.ImportingExitingWallet) { + if (res.status === ErrorCode.DuplicateImportWallet) { throw res } diff --git a/packages/neuron-ui/src/utils/enums.ts b/packages/neuron-ui/src/utils/enums.ts index 934a74a86c..4453c6cdd4 100644 --- a/packages/neuron-ui/src/utils/enums.ts +++ b/packages/neuron-ui/src/utils/enums.ts @@ -107,7 +107,7 @@ export enum ErrorCode { DeviceInSleep = 501, // active warning WaitForFullySynced = 600, - ImportingExitingWallet = 118, + DuplicateImportWallet = 118, } export enum SyncStatus { diff --git a/packages/neuron-wallet/src/exceptions/wallet.ts b/packages/neuron-wallet/src/exceptions/wallet.ts index b400f7410d..25962d77a3 100644 --- a/packages/neuron-wallet/src/exceptions/wallet.ts +++ b/packages/neuron-wallet/src/exceptions/wallet.ts @@ -78,7 +78,7 @@ export class WalletFunctionNotSupported extends Error { } } -export class ImportingExitingWallet extends Error { +export class DuplicateImportWallet extends Error { public code = 118 constructor(errorStr: string) { super(errorStr) @@ -95,5 +95,5 @@ export default { LiveCapacityNotEnough, CapacityNotEnoughForChange, InvalidKeystore, - ImportingExitingWallet, + DuplicateImportWallet, } diff --git a/packages/neuron-wallet/src/services/wallets.ts b/packages/neuron-wallet/src/services/wallets.ts index 8dc3900402..a5079a553a 100644 --- a/packages/neuron-wallet/src/services/wallets.ts +++ b/packages/neuron-wallet/src/services/wallets.ts @@ -1,5 +1,5 @@ import { v4 as uuid } from 'uuid' -import { WalletNotFound, IsRequired, UsedName, WalletFunctionNotSupported, ImportingExitingWallet } from '../exceptions' +import { WalletNotFound, IsRequired, UsedName, WalletFunctionNotSupported, DuplicateImportWallet } from '../exceptions' import Store from '../models/store' import Keystore from '../models/keys/keystore' import WalletDeletedSubject from '../models/subjects/wallet-deleted-subject' @@ -385,7 +385,7 @@ export default class WalletService { if (this.getAll().find(item => item.extendedKey === props.extendedKey)) { this.importedWallet = wallet - throw new ImportingExitingWallet(JSON.stringify({ extendedKey: props.extendedKey, id })) + throw new DuplicateImportWallet(JSON.stringify({ extendedKey: props.extendedKey, id })) } this.listStore.writeSync(this.walletsKey, [...this.getAll(), wallet.toJSON()]) diff --git a/packages/neuron-wallet/tests/services/wallets.test.ts b/packages/neuron-wallet/tests/services/wallets.test.ts index 65fff97cf0..b8f7833329 100644 --- a/packages/neuron-wallet/tests/services/wallets.test.ts +++ b/packages/neuron-wallet/tests/services/wallets.test.ts @@ -29,7 +29,7 @@ import Keystore from '../../src/models/keys/keystore' import initConnection from '../../src/database/chain/ormconfig' import { getConnection } from 'typeorm' import { when } from 'jest-when' -import { WalletFunctionNotSupported, ImportingExitingWallet } from '../../src/exceptions/wallet' +import { WalletFunctionNotSupported, DuplicateImportWallet } from '../../src/exceptions/wallet' import { AddressType } from '../../src/models/keys/address' import { Manufacturer } from '../../src/services/hardware/common' import WalletService, { WalletProperties, Wallet } from '../../src/services/wallets' @@ -508,12 +508,12 @@ describe('wallet service', () => { }) }) - describe('ImportingExitingWallet', () => { + describe('DuplicateImportWallet', () => { beforeEach(() => { walletService.create(wallet2) }) it('create an exiting wallet', () => { - expect(() => walletService.create(wallet5)).toThrowError(ImportingExitingWallet) + expect(() => walletService.create(wallet5)).toThrowError(DuplicateImportWallet) }) }) From 2895c5de746edbb7107406d14815d7200cbfb327 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Thu, 18 Jan 2024 21:13:23 +0800 Subject: [PATCH 09/13] fix: comments --- .../components/DetectDuplicateWalletDialog/index.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx index 64d0a1bdc8..be6e2cdb7a 100644 --- a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx @@ -68,14 +68,12 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { }) } - let ids = [...duplicatedWallets] - if (duplicatedWallets.includes(currentID)) { - ids = ids.filter(item => item !== currentID) - ids.push(currentID) - } + const requestToDeleteIds = duplicatedWallets + .filter(item => item !== currentID) + .concat(duplicatedWallets.includes(currentID) ? [currentID] : []) // eslint-disable-next-line no-restricted-syntax - for (const id of ids) { + for (const id of requestToDeleteIds) { // eslint-disable-next-line no-await-in-loop await getRequest(id) } From f93c149cab58953c8e1f168f08bf9ed4a0908b5a Mon Sep 17 00:00:00 2001 From: devchenyan Date: Thu, 18 Jan 2024 22:04:33 +0800 Subject: [PATCH 10/13] fix: comments --- .../DetectDuplicateWalletDialog/index.tsx | 19 +++++++++---------- .../ReplaceDuplicateWalletDialog/index.tsx | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx index be6e2cdb7a..cc98e15f20 100644 --- a/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/DetectDuplicateWalletDialog/index.tsx @@ -32,17 +32,16 @@ const DetectDuplicateWalletDialog = ({ onClose }: { onClose: () => void }) => { const handleGroupChange = useCallback( (checked: string) => { - const [extendedKey, id] = (checked as string).split('_') - const list: string[] = [] - wallets.forEach(item => { - if (item.extendedKey === extendedKey) { - if (item.id !== id) { - list.push(item.id) + const [extendedKey, id] = checked.split('_') + + const list = wallets + .filter(item => { + if (item.extendedKey === extendedKey) { + return item.id !== id } - } else if (duplicatedWallets.includes(item.id)) { - list.push(item.id) - } - }) + return duplicatedWallets.includes(item.id) + }) + .map(item => item.id) setDuplicatedWallets(list) }, diff --git a/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx index 2cbe6c85dc..c644a496e9 100644 --- a/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx +++ b/packages/neuron-ui/src/components/ReplaceDuplicateWalletDialog/index.tsx @@ -77,7 +77,7 @@ const ReplaceDuplicateWalletDialog = ({ [setSelectedId] ) - const onConfirm = useCallback(async () => { + const onConfirm = useCallback(() => { replaceWallet({ existingWalletId: selectedId, importedWalletId, From 8e2e9f8b4c589ff4ca856ceb5c704d2f9b4e657d Mon Sep 17 00:00:00 2001 From: devchenyan Date: Fri, 19 Jan 2024 21:12:36 +0800 Subject: [PATCH 11/13] fix: comments --- packages/neuron-wallet/tests/services/wallets.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/neuron-wallet/tests/services/wallets.test.ts b/packages/neuron-wallet/tests/services/wallets.test.ts index b8f7833329..a500df9c32 100644 --- a/packages/neuron-wallet/tests/services/wallets.test.ts +++ b/packages/neuron-wallet/tests/services/wallets.test.ts @@ -160,7 +160,7 @@ describe('wallet service', () => { wallet5 = { name: 'wallet-test5', id: '', - extendedKey: 'a', + extendedKey: 'wallet1.extendedKey', keystore: new Keystore( { cipher: 'wallet5', @@ -528,7 +528,7 @@ describe('wallet service', () => { } catch (error) { const { extendedKey, id } = JSON.parse(error.message) await walletService.replace(createdWallet2.id, id) - expect(extendedKey).toBe('a') + expect(extendedKey).toBe('wallet1.extendedKey') expect(() => walletService.get(createdWallet2.id)).toThrowError() expect(walletService.get(id).name).toBe(wallet5.name) } From 120c833e632cad7c4bd96248b872f81b7030b1de Mon Sep 17 00:00:00 2001 From: devchenyan Date: Fri, 19 Jan 2024 21:17:57 +0800 Subject: [PATCH 12/13] fix: Merge --- packages/neuron-ui/src/components/PasswordRequest/hooks.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/neuron-ui/src/components/PasswordRequest/hooks.ts b/packages/neuron-ui/src/components/PasswordRequest/hooks.ts index db51eb2835..6f8991b5d6 100644 --- a/packages/neuron-ui/src/components/PasswordRequest/hooks.ts +++ b/packages/neuron-ui/src/components/PasswordRequest/hooks.ts @@ -164,7 +164,9 @@ export default ({ } case 'delete': { await deleteWallet({ id: walletID, password })(dispatch).then(status => { - if (status === ErrorCode.PasswordIncorrect) { + if (isSuccessResponse({ status })) { + onSuccess?.() + } else if (status === ErrorCode.PasswordIncorrect) { throw new PasswordIncorrectException() } }) From 1ca5c991fbe0f1a1f8765e576f3d56dc2055a1a0 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Fri, 19 Jan 2024 21:31:54 +0800 Subject: [PATCH 13/13] fix --- packages/neuron-wallet/tests/services/wallets.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/neuron-wallet/tests/services/wallets.test.ts b/packages/neuron-wallet/tests/services/wallets.test.ts index a500df9c32..b8f7833329 100644 --- a/packages/neuron-wallet/tests/services/wallets.test.ts +++ b/packages/neuron-wallet/tests/services/wallets.test.ts @@ -160,7 +160,7 @@ describe('wallet service', () => { wallet5 = { name: 'wallet-test5', id: '', - extendedKey: 'wallet1.extendedKey', + extendedKey: 'a', keystore: new Keystore( { cipher: 'wallet5', @@ -528,7 +528,7 @@ describe('wallet service', () => { } catch (error) { const { extendedKey, id } = JSON.parse(error.message) await walletService.replace(createdWallet2.id, id) - expect(extendedKey).toBe('wallet1.extendedKey') + expect(extendedKey).toBe('a') expect(() => walletService.get(createdWallet2.id)).toThrowError() expect(walletService.get(id).name).toBe(wallet5.name) }