From 981ce7458486e5da91e2acbadf568552655acb2b Mon Sep 17 00:00:00 2001 From: devchenyan Date: Wed, 19 Jun 2024 01:09:39 +0800 Subject: [PATCH 1/8] fix: Optimized Chinese Translation (#3190) --- packages/neuron-ui/src/locales/zh-tw.json | 10 +++++----- packages/neuron-ui/src/locales/zh.json | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 712e024b59..90a8d7670f 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -1227,8 +1227,8 @@ "description": "備註", "action": "操作" }, - "locked": "鎖定", - "unlocked": "解鎖", + "locked": "已鎖定", + "unlocked": "已解鎖", "default-description": "無" }, "cell-detail-dialog": { @@ -1257,8 +1257,8 @@ "capacity": "解鎖所選 Cell {{capacity}} CKB" }, "cell-consume-dialog": { - "title": "消耗 Cell", - "warn-consume": "消耗Cell不會保留任何數據,並將花費全部的CKB,請確保沒有重要數據需要保留。" + "title": "花費 Cell", + "warn-consume": "花費Cell不會保留任何數據,並將花費全部的CKB,請確保沒有重要數據需要保留。" }, "cell-consolidate-dialog": { "title": "合並", @@ -1268,7 +1268,7 @@ "password-placeholder": "請輸入錢包密碼", "lock": "鎖定", "unlock": "解鎖", - "consume": "消耗", + "consume": "花費", "verify": "驗證", "consolidate": "合並" }, diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index b181a569f6..8e19bf5c8e 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -1226,8 +1226,8 @@ "description": "备注", "action": "操作" }, - "locked": "锁定", - "unlocked": "解锁", + "locked": "已锁定", + "unlocked": "已解锁", "default-description": "无" }, "cell-detail-dialog": { @@ -1260,8 +1260,8 @@ "capacity": "解锁所选 Cell {{capacity}} CKB" }, "cell-consume-dialog": { - "title": "消耗 Cell", - "warn-consume": "消耗Cell不会保留任何数据,并将花费全部的CKB,请确保没有重要数据需要保留。" + "title": "花费 Cell", + "warn-consume": "花费Cell不会保留任何数据,并将花费全部的CKB,请确保没有重要数据需要保留。" }, "cell-consolidate-dialog": { "title": "合并 Cells", @@ -1271,7 +1271,7 @@ "password-placeholder": "请输入钱包密码", "lock": "锁定", "unlock": "解锁", - "consume": "消耗", + "consume": "花费", "verify": "验证", "consolidate": "合并" }, From 8cbe25e976b2cb8780b2b812771c9552ec9837b2 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Fri, 21 Jun 2024 09:52:54 +0800 Subject: [PATCH 2/8] fix: Warning background in pop-up window (#3189) * fix: Warning background in pop-up window * fix: comment * fix: comment --- .../components/SUDTReceiveDialog/sUDTReceiveDialog.module.scss | 1 - packages/neuron-ui/src/widgets/Dialog/dialog.module.scss | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/neuron-ui/src/components/SUDTReceiveDialog/sUDTReceiveDialog.module.scss b/packages/neuron-ui/src/components/SUDTReceiveDialog/sUDTReceiveDialog.module.scss index 0f96d5495a..38f63ef659 100644 --- a/packages/neuron-ui/src/components/SUDTReceiveDialog/sUDTReceiveDialog.module.scss +++ b/packages/neuron-ui/src/components/SUDTReceiveDialog/sUDTReceiveDialog.module.scss @@ -52,7 +52,6 @@ justify-content: center; align-items: center; word-break: break-word; - margin: 0 4px; background-color: var(--warn-background-color); svg { diff --git a/packages/neuron-ui/src/widgets/Dialog/dialog.module.scss b/packages/neuron-ui/src/widgets/Dialog/dialog.module.scss index e18b0d40e4..75d3640c14 100644 --- a/packages/neuron-ui/src/widgets/Dialog/dialog.module.scss +++ b/packages/neuron-ui/src/widgets/Dialog/dialog.module.scss @@ -49,7 +49,7 @@ padding: 20px 16px; position: relative; max-height: calc(100vh - 260px); - overflow-y: scroll; + overflow-y: auto; } .footerWrap { From fb57415045d10705fb65f4e5c20a0248e4f9c947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Tue, 25 Jun 2024 05:44:59 +0800 Subject: [PATCH 3/8] fix: Show the tip of looking for valid target (#3195) --- packages/neuron-wallet/.env | 3 +- .../neuron-wallet/src/controllers/sync-api.ts | 16 +++++- .../neuron-wallet/src/services/ckb-runner.ts | 16 ------ .../tests/controllers/sync-api.test.ts | 26 +++++++-- .../tests/services/ckb-runner.test.ts | 57 +------------------ scripts/update-wallet-env.js | 6 +- 6 files changed, 43 insertions(+), 81 deletions(-) diff --git a/packages/neuron-wallet/.env b/packages/neuron-wallet/.env index 3c4d9296b8..b5240ac1d7 100644 --- a/packages/neuron-wallet/.env +++ b/packages/neuron-wallet/.env @@ -118,4 +118,5 @@ MULTISIG_CODE_HASH=0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc963 # CKB NODE OPTIONS CKB_NODE_ASSUME_VALID_TARGET='0x6dd077b407d019a0bce0cbad8c34e69a524ae4b2599b9feda2c7491f3559d32c' -CKB_NODE_DATA_SIZE=54 +CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER=13007704 +CKB_NODE_DATA_SIZE=56 diff --git a/packages/neuron-wallet/src/controllers/sync-api.ts b/packages/neuron-wallet/src/controllers/sync-api.ts index 69b9603f76..d3571d2770 100644 --- a/packages/neuron-wallet/src/controllers/sync-api.ts +++ b/packages/neuron-wallet/src/controllers/sync-api.ts @@ -1,15 +1,17 @@ import EventEmiter from 'events' import { debounceTime } from 'rxjs/operators' +import env from '../env' import RpcService from '../services/rpc-service' import SyncedBlockNumber from '../models/synced-block-number' import SyncStateSubject from '../models/subjects/sync-state-subject' import { CurrentNetworkIDSubject } from '../models/subjects/networks' import MultisigService from '../services/multisig' -import { getLookingValidTargetStatus } from '../services/ckb-runner' import NetworksService from '../services/networks' +import { NetworkType } from '../models/network' const TEN_MINS = 600000 const MAX_TIP_BLOCK_DELAY = 180000 +const { app } = env export enum SyncStatus { SyncNotStart, @@ -137,6 +139,16 @@ export default class SyncApiController { const remainingBlocksToCache = bestKnownBlockNumber - cacheTipNumber const remainingBlocksToIndex = bestKnownBlockNumber - indexerTipNumber + const setAssumeValidTargetBlockNumber = + app.isPackaged && + process.env.CKB_NODE_ASSUME_VALID_TARGET && + process.env.CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER + ? +process.env.CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER + : undefined + const isLookingValidTarget = + network.type === NetworkType.Default && + !!setAssumeValidTargetBlockNumber && + bestKnownBlockNumber < setAssumeValidTargetBlockNumber const newSyncState: SyncState = { nodeUrl: network.remote, @@ -149,7 +161,7 @@ export default class SyncApiController { cacheRate: undefined, estimate: undefined, status: SyncStatus.Syncing, - isLookingValidTarget: getLookingValidTargetStatus(), + isLookingValidTarget, validTarget: process.env.CKB_NODE_ASSUME_VALID_TARGET, } diff --git a/packages/neuron-wallet/src/services/ckb-runner.ts b/packages/neuron-wallet/src/services/ckb-runner.ts index 5eae7a91ef..0df5f1a3c7 100644 --- a/packages/neuron-wallet/src/services/ckb-runner.ts +++ b/packages/neuron-wallet/src/services/ckb-runner.ts @@ -91,10 +91,6 @@ const initCkb = async () => { }) } -let isLookingValidTarget: boolean = false -let lastLogTime: number -export const getLookingValidTargetStatus = () => isLookingValidTarget - export const getNodeUrl = () => `${BUNDLED_URL_PREFIX}${rpcPort}` const removeOldIndexerIfRunSuccess = () => { @@ -144,21 +140,10 @@ export const startCkbNode = async () => { logger.error('CKB:\trun fail:', dataString) return } - if ( - dataString.includes( - `can't find assume valid target temporarily, hash: Byte32(${process.env.CKB_NODE_ASSUME_VALID_TARGET})` - ) - ) { - isLookingValidTarget = true - lastLogTime = Date.now() - } else if (lastLogTime && Date.now() - lastLogTime > 10000) { - isLookingValidTarget = false - } }) currentProcess.on('error', error => { logger.error('CKB:\trun fail:', error) - isLookingValidTarget = false if (Object.is(ckb, currentProcess)) { ckb = null } @@ -166,7 +151,6 @@ export const startCkbNode = async () => { currentProcess.on('close', code => { logger.info(`CKB:\tprocess closed with code ${code}`) - isLookingValidTarget = false if (Object.is(ckb, currentProcess)) { ckb = null } diff --git a/packages/neuron-wallet/tests/controllers/sync-api.test.ts b/packages/neuron-wallet/tests/controllers/sync-api.test.ts index 6c35b57135..40dec1ce51 100644 --- a/packages/neuron-wallet/tests/controllers/sync-api.test.ts +++ b/packages/neuron-wallet/tests/controllers/sync-api.test.ts @@ -44,15 +44,17 @@ jest.doMock('models/subjects/networks', () => { }, } }) -jest.doMock('services/ckb-runner', () => ({ - getLookingValidTargetStatus: jest.fn(), -})) jest.mock('undici', () => ({ request: () => jest.fn()(), })) jest.mock('services/multisig', () => ({ syncMultisigOutput: () => jest.fn(), })) +jest.mock('env', () => ({ + app: { + isPackaged: true, + }, +})) describe('SyncApiController', () => { const emitter = new Emitter() @@ -131,7 +133,7 @@ describe('SyncApiController', () => { bestKnownBlockNumber: bestKnownBlockNumber.toString(16), bestKnownBlockTimestamp: `0x${bestKnownBlockTimestamp.toString(16)}`, }) - getCurrentNetworkMock.mockReturnValue({ remote: fakeNodeUrl }) + getCurrentNetworkMock.mockReturnValue({ remote: fakeNodeUrl, type: 0 }) stubbedGetTipHeader.mockResolvedValue({ timestamp: '180000' }) }) describe('on cache-tip-block-updated', () => { @@ -148,9 +150,15 @@ describe('SyncApiController', () => { timestamp: '187000', } beforeEach(async () => { + process.env.CKB_NODE_ASSUME_VALID_TARGET = '0x' + process.env.CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER = '100000' await sendFakeCacheBlockTipEvent(fakeState1) await sendFakeCacheBlockTipEvent(fakeState2) }) + afterAll(() => { + delete process.env['CKB_NODE_ASSUME_VALID_TARGET'] + delete process.env['CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER'] + }) it('broadcast event of synced', () => { expect(stubbedSyncStateSubjectNext).toHaveBeenCalledWith({ nodeUrl: fakeNodeUrl, @@ -163,6 +171,8 @@ describe('SyncApiController', () => { cacheRate: undefined, estimate: undefined, status: 3, + isLookingValidTarget: true, + validTarget: '0x', }) }) it('#getSyncStatus returns synced', async () => { @@ -203,6 +213,7 @@ describe('SyncApiController', () => { cacheRate: undefined, estimate: undefined, status: 2, + isLookingValidTarget: false, }) }) it('#getSyncStatus returns syncing', async () => { @@ -240,6 +251,7 @@ describe('SyncApiController', () => { cacheRate: undefined, estimate: undefined, status: 1, + isLookingValidTarget: false, }) }) it('#getSyncStatus returns sync pending', async () => { @@ -271,6 +283,7 @@ describe('SyncApiController', () => { cacheRate: undefined, estimate: undefined, status: 2, + isLookingValidTarget: false, }) }) it('stores next block number', () => { @@ -304,6 +317,7 @@ describe('SyncApiController', () => { cacheRate: undefined, estimate: Math.round((bestKnownBlockNumber - parseInt(fakeState2.indexerTipNumber)) / indexRate), status: 2, + isLookingValidTarget: false, } beforeEach(async () => { await sendFakeCacheBlockTipEvent(fakeState1) @@ -399,6 +413,7 @@ describe('SyncApiController', () => { cacheRate: undefined, estimate: undefined, status: 2, + isLookingValidTarget: false, }) }) it('stores next block number', () => { @@ -446,6 +461,7 @@ describe('SyncApiController', () => { indexerTipNumber: parseInt(fakeState3.indexerTipNumber), estimate: Math.round((bestKnownBlockNumber - parseInt(fakeState3.indexerTipNumber)) / indexRate), status: 2, + isLookingValidTarget: false, }) }) it('stores next block number', () => { @@ -472,6 +488,7 @@ describe('SyncApiController', () => { indexerTipNumber: parseInt(fakeState3.indexerTipNumber), estimate: undefined, status: 2, + isLookingValidTarget: false, }) }) it('#getSyncStatus returns syncing', async () => { @@ -516,6 +533,7 @@ describe('SyncApiController', () => { cacheRate: undefined, estimate: undefined, status: 2, + isLookingValidTarget: false, }) }) it('#getSyncStatus returns syncing', async () => { diff --git a/packages/neuron-wallet/tests/services/ckb-runner.test.ts b/packages/neuron-wallet/tests/services/ckb-runner.test.ts index 067137a946..7e904a4637 100644 --- a/packages/neuron-wallet/tests/services/ckb-runner.test.ts +++ b/packages/neuron-wallet/tests/services/ckb-runner.test.ts @@ -1,6 +1,5 @@ import { EventEmitter } from 'typeorm/platform/PlatformTools' import path from 'path' -import { scheduler } from 'timers/promises' const stubbedChildProcess = jest.fn() const stubbedSpawn = jest.fn() @@ -92,13 +91,7 @@ jest.doMock('../../src/utils/toml', () => ({ jest.doMock('../../src/utils/get-usable-port', () => ({ getUsablePort: getUsablePortMock, })) -const { - startCkbNode, - stopCkbNode, - getLookingValidTargetStatus, - migrateCkbData, - getNodeUrl, -} = require('../../src/services/ckb-runner') +const { startCkbNode, stopCkbNode, migrateCkbData, getNodeUrl } = require('../../src/services/ckb-runner') describe('ckb runner', () => { let stubbedCkb: any = new EventEmitter() @@ -199,54 +192,6 @@ describe('ckb runner', () => { }) }) - describe('with assume valid target', () => { - beforeEach(async () => { - stubbedProcess.platform = platform - app.isPackaged = true - stubbedProcess.env = { CKB_NODE_ASSUME_VALID_TARGET: '0x' + '0'.repeat(64) } - stubbedExistsSync.mockReturnValue(true) - await startCkbNode() - }) - afterEach(async () => { - app.isPackaged = false - stubbedProcess.env = {} - const promise = stopCkbNode() - stubbedCkb.emit('close') - await promise - }) - it('runs ckb binary', () => { - expect(stubbedSpawn).toHaveBeenCalledWith( - expect.stringContaining(path.join('bin', 'ckb')), - ['run', '-C', ckbDataPath, '--indexer', '--assume-valid-target', '0x' + '0'.repeat(64)], - { stdio: ['ignore', 'pipe', 'pipe'] } - ) - }) - it('is Looking valid target', () => { - stubbedCkb.stdout.emit( - 'data', - `can't find assume valid target temporarily, hash: Byte32(0x${'0'.repeat(64)})` - ) - expect(getLookingValidTargetStatus()).toBeTruthy() - }) - it('is Looking valid target', async () => { - stubbedCkb.stdout.emit( - 'data', - `can't find assume valid target temporarily, hash: Byte32(0x${'0'.repeat(64)})` - ) - await scheduler.wait(11000) - stubbedCkb.stdout.emit('data', `had find valid target`) - expect(getLookingValidTargetStatus()).toBeFalsy() - }, 15000) - it('ckb has closed', async () => { - stubbedCkb.stdout.emit( - 'data', - `can't find assume valid target temporarily, hash: Byte32(0x${'0'.repeat(64)})` - ) - stubbedCkb.emit('close') - expect(getLookingValidTargetStatus()).toBeFalsy() - }) - }) - it('port is not usable', async () => { stubbedExistsSync.mockReturnValue(true) getUsablePortMock.mockReset() diff --git a/scripts/update-wallet-env.js b/scripts/update-wallet-env.js index f0a9dee2c0..dcc6e940fd 100644 --- a/scripts/update-wallet-env.js +++ b/scripts/update-wallet-env.js @@ -24,12 +24,13 @@ async function rpcRequest(method, params = []) { const ESTIMATE_BLOCK_COUNT_PER_DAY = 8_000 const envFilePath = path.resolve(__dirname, '../packages/neuron-wallet/.env') const validTargetReg = /(CKB_NODE_ASSUME_VALID_TARGET=)[\S]*/ +const blockNumberReg = /(CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER=)[\S]*/ const ckbNodeDataSizeReg = /(CKB_NODE_DATA_SIZE=)[\S]*/ async function getValidTarget() { const tipBlockNumber = (await rpcRequest('get_tip_block_number')).result const validTargetBlockNumber = `0x${(BigInt(tipBlockNumber) - BigInt(ESTIMATE_BLOCK_COUNT_PER_DAY)).toString(16)}` - return (await rpcRequest('get_block_hash', [validTargetBlockNumber])).result + return [validTargetBlockNumber, (await rpcRequest('get_block_hash', [validTargetBlockNumber])).result] } async function getCKBNodeSize() { @@ -41,12 +42,13 @@ async function getCKBNodeSize() { ;(async function () { try { console.info('start updating env file') - const blockHash = await getValidTarget() + const [blockNumber, blockHash] = await getValidTarget() const ckbNodeDataSize = await getCKBNodeSize() const originEnvContent = fs.readFileSync(envFilePath).toString('utf-8') fs.writeFileSync( envFilePath, originEnvContent.replace(validTargetReg, `CKB_NODE_ASSUME_VALID_TARGET='${blockHash}'`) + .replace(blockNumberReg, `CKB_NODE_ASSUME_VALID_TARGET_BLOCK_NUMBER=${+blockNumber}`) .replace(ckbNodeDataSizeReg, `CKB_NODE_DATA_SIZE=${ckbNodeDataSize}`) ) console.info('write success') From 878da3c48cb0c594d3e6e45950fce1cecf1984ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Tue, 25 Jun 2024 14:43:18 +0800 Subject: [PATCH 4/8] feat: Add guide for multisig address (#3194) Co-authored-by: Chen Yu --- .../src/components/MultisigAddress/index.tsx | 53 ++++++++++++++++-- .../multisigAddress.module.scss | 56 +++++++++++++++++++ packages/neuron-ui/src/locales/en.json | 2 + packages/neuron-ui/src/locales/es.json | 2 + packages/neuron-ui/src/locales/fr.json | 2 + packages/neuron-ui/src/locales/zh-tw.json | 2 + packages/neuron-ui/src/locales/zh.json | 2 + .../neuron-ui/src/widgets/Tooltip/index.tsx | 6 +- 8 files changed, 119 insertions(+), 6 deletions(-) diff --git a/packages/neuron-ui/src/components/MultisigAddress/index.tsx b/packages/neuron-ui/src/components/MultisigAddress/index.tsx index f0f3f4bbf0..d788190cf2 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/index.tsx +++ b/packages/neuron-ui/src/components/MultisigAddress/index.tsx @@ -1,17 +1,18 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Trans, useTranslation } from 'react-i18next' import { useOnLocaleChange, isMainnet as isMainnetUtil, shannonToCKBFormatter, useExitOnWalletChange, useGoBack, + useOnWindowResize, } from 'utils' import { useState as useGlobalState } from 'states' import MultisigAddressCreateDialog from 'components/MultisigAddressCreateDialog' import MultisigAddressInfo from 'components/MultisigAddressInfo' import SendFromMultisigDialog from 'components/SendFromMultisigDialog' -import { MultisigConfig, changeMultisigSyncStatus } from 'services/remote' +import { MultisigConfig, changeMultisigSyncStatus, openExternal } from 'services/remote' import ApproveMultisigTxDialog from 'components/ApproveMultisigTxDialog' import Dialog from 'widgets/Dialog' import Table from 'widgets/Table' @@ -28,6 +29,7 @@ import { Upload, Edit, Confirming, + Question, } from 'widgets/Icons/icon' import AttentionCloseDialog from 'widgets/Icons/Attention.png' import { HIDE_BALANCE, NetworkType } from 'utils/const' @@ -35,6 +37,7 @@ import { onEnter } from 'utils/inputDevice' import getMultisigSignStatus from 'utils/getMultisigSignStatus' import Button from 'widgets/Button' import SetStartBlockNumberDialog from 'components/SetStartBlockNumberDialog' +import { type TFunction } from 'i18next' import { useSearch, useConfigManage, @@ -67,6 +70,19 @@ const tableActions = [ }, ] +const LearnMore = React.memo(({ t }: { t: TFunction }) => ( + +)) + const MultisigAddress = () => { const [t, i18n] = useTranslation() useOnLocaleChange(i18n) @@ -192,12 +208,41 @@ const MultisigAddress = () => { } } }, [isLightClient]) + const titleRef = useRef(null) + const [tipPosition, setTipPosition] = useState<{ left?: number; top?: number }>({}) + const updateTipPosition = useCallback(() => { + if (titleRef.current) { + const boundingClientRect = titleRef.current.getBoundingClientRect() + setTipPosition({ + left: boundingClientRect.left - 18, + top: boundingClientRect.top - boundingClientRect.height, + }) + } + }, [titleRef.current, setTipPosition]) + useEffect(() => { + updateTipPosition() + }, [updateTipPosition]) + useOnWindowResize(updateTipPosition) return (
+ {t('multisig-address.window-title')} + ]} />} + placement="top" + showTriangle + tipClassName={styles.multiGuide} + tipStyles={tipPosition} + > + + +
+ } onCancel={isLightClient ? onCancelWithLight : onBack} showFooter={false} > diff --git a/packages/neuron-ui/src/components/MultisigAddress/multisigAddress.module.scss b/packages/neuron-ui/src/components/MultisigAddress/multisigAddress.module.scss index 7508e4f9a1..f93d2bea0d 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/multisigAddress.module.scss +++ b/packages/neuron-ui/src/components/MultisigAddress/multisigAddress.module.scss @@ -228,3 +228,59 @@ fill: var(--primary-color); } } + +.title { + display: flex; + align-items: center; +} + +.multiGuideTip { + display: inline-flex; + margin-left: 4px; + + & > svg { + g { + fill: none; + } + path { + fill: none; + stroke: var(--secondary-text-color); + } + &:hover, + &:active { + path { + stroke: var(--primary-color); + } + } + } + .multiGuide { + position: fixed !important; + transform: translateX(0) translateY(-100%) !important; + width: 270px; + white-space: break-spaces !important; + + button { + border: none; + background-color: transparent; + color: var(--primary-color); + padding: 0; + cursor: pointer; + } + + &::after { + content: ''; + width: 270px; + height: 50px; + background-color: rebeccapurple; + display: block; + position: absolute; + background-color: transparent; + } + } + + &:hover { + .multiGuide { + display: table !important; + } + } +} diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index ad0395e53d..1d50063f11 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -1083,6 +1083,8 @@ }, "multisig-address": { "window-title": "Multisig Addresses", + "guide-tip": "Refer to the Multisig documentation to <0>learn more.", + "learn-more": "learn more", "search": { "placeholder": "search by multisig address,alias" }, diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index a032e20bec..6b6a185a3d 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -1066,6 +1066,8 @@ }, "multisig-address": { "window-title": "Direcciones de Multifirma", + "guide-tip": "Para los usuarios nuevos, pueden consultar la documentación para obtener <0>más información.", + "learn-more": "más información", "search": { "placeholder": "buscar por dirección de multifirma, alias" }, diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index e38beafccc..0817d30c83 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -1073,6 +1073,8 @@ }, "multisig-address": { "window-title": "Adresses Multisig", + "guide-tip": "Pour les nouveaux utilisateurs, vous pouvez consulter la documentation pour <0>en savoir plus.", + "learn-more": "en savoir plus", "search": { "placeholder": "recherche par adresse multisig, alias" }, diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 90a8d7670f..8d68fe1ee5 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -1076,6 +1076,8 @@ }, "multisig-address": { "window-title": "多簽地址", + "guide-tip": "對於首次使用您可以查看文檔以 <0>了解更多。", + "learn-more": "了解更多", "search": { "placeholder": "使用地址或者别名搜索" }, diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 8e19bf5c8e..dc258e3c92 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -1075,6 +1075,8 @@ }, "multisig-address": { "window-title": "多签地址", + "guide-tip": "对于首次使用您可以查看文档以 <0>了解更多。", + "learn-more": "了解更多", "search": { "placeholder": "使用地址或者别名搜索" }, diff --git a/packages/neuron-ui/src/widgets/Tooltip/index.tsx b/packages/neuron-ui/src/widgets/Tooltip/index.tsx index 8b6563cba4..8bc66d8f57 100644 --- a/packages/neuron-ui/src/widgets/Tooltip/index.tsx +++ b/packages/neuron-ui/src/widgets/Tooltip/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState, useRef } from 'react' +import React, { useCallback, useState, useRef, type CSSProperties } from 'react' import { useDidMount } from 'utils' import styles from './tooltip.module.scss' @@ -8,6 +8,7 @@ interface TooltipProps { tip: React.ReactNode className?: string tipClassName?: string + tipStyles?: CSSProperties placement?: Placement center?: boolean trigger?: 'hover' | 'click' @@ -21,6 +22,7 @@ const Tooltip: React.FC> = ({ tip, className = '', tipClassName = '', + tipStyles, placement = 'bottom', center = true, trigger = 'hover', @@ -88,7 +90,7 @@ const Tooltip: React.FC> = ({ data-has-trigger={showTriangle} data-trigger-next-to-child={isTriggerNextToChild} > -
+
{tip} {showTriangle &&
}
From 4493d795e9bd7bd172376529e88bb8518405e12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E5=9B=BD=E5=AE=87?= <841185308@qq.com> Date: Wed, 26 Jun 2024 16:07:02 +0800 Subject: [PATCH 5/8] fix: For windows os the user need to start the Neuron manually after installed. (#3196) --- packages/neuron-ui/src/components/GeneralSetting/index.tsx | 3 ++- packages/neuron-ui/src/locales/en.json | 3 ++- packages/neuron-ui/src/locales/es.json | 3 ++- packages/neuron-ui/src/locales/fr.json | 3 ++- packages/neuron-ui/src/locales/zh-tw.json | 3 ++- packages/neuron-ui/src/locales/zh.json | 3 ++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/neuron-ui/src/components/GeneralSetting/index.tsx b/packages/neuron-ui/src/components/GeneralSetting/index.tsx index 45216d4d9b..046c90c4b1 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/index.tsx +++ b/packages/neuron-ui/src/components/GeneralSetting/index.tsx @@ -97,6 +97,7 @@ const UpdateDownloadStatus = ({ } if (downloaded) { + const isWin = process.platform === 'win32' return ( Date: Mon, 1 Jul 2024 09:52:12 +0800 Subject: [PATCH 6/8] feat: Support reset lock window password (#3197) --- .../src/components/CellManagement/hooks.ts | 2 +- .../GeneralSetting/LockWindowDialog/hooks.ts | 4 +- .../GeneralSetting/LockWindowDialog/index.tsx | 2 +- .../src/containers/LockWindow/index.tsx | 70 +++++++-- .../LockWindow/lockWindow.module.scss | 16 ++ .../containers/LockWindow/verifyWallet.tsx | 121 ++++++++++++++ packages/neuron-ui/src/locales/en.json | 4 +- packages/neuron-ui/src/locales/es.json | 4 +- packages/neuron-ui/src/locales/fr.json | 4 +- packages/neuron-ui/src/locales/zh-tw.json | 4 +- packages/neuron-ui/src/locales/zh.json | 4 +- .../neuron-ui/src/types/Controller/index.d.ts | 2 +- .../neuron-ui/src/widgets/Dialog/index.tsx | 8 +- .../src/widgets/SplitPasswordInput/index.tsx | 147 +++++++++++------- packages/neuron-wallet/src/controllers/api.ts | 2 + .../src/controllers/cell-management.ts | 7 +- .../src/controllers/sign-message.ts | 10 +- .../src/services/sign-message.ts | 14 +- .../neuron-wallet/src/types/controller.d.ts | 2 +- .../tests/controllers/cell-management.test.ts | 10 +- .../tests/services/sign-message.test.ts | 21 ++- 21 files changed, 364 insertions(+), 94 deletions(-) create mode 100644 packages/neuron-ui/src/containers/LockWindow/verifyWallet.tsx diff --git a/packages/neuron-ui/src/components/CellManagement/hooks.ts b/packages/neuron-ui/src/components/CellManagement/hooks.ts index f0a746c5cb..1823cc98c0 100644 --- a/packages/neuron-ui/src/components/CellManagement/hooks.ts +++ b/packages/neuron-ui/src/components/CellManagement/hooks.ts @@ -398,7 +398,7 @@ export const usePassword = () => { } export const useHardWallet = ({ wallet, t }: { wallet: State.WalletIdentity; t: TFunction }) => { - const isWin32 = useMemo(() => { + const isWin32 = useMemo(() => { return getPlatform() === 'win32' }, []) const [error, setError] = useState() diff --git a/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/hooks.ts b/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/hooks.ts index a1213dc389..fe2e933eec 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/hooks.ts +++ b/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/hooks.ts @@ -71,7 +71,7 @@ export const useRepeatPassword = ({ password: string t: TFunction encryptedPassword?: string - onCancel: () => void + onCancel: (success: boolean) => void }) => { const dispatch = useDispatch() const [errMsg, setErrMsg] = useState('') @@ -89,7 +89,7 @@ export const useRepeatPassword = ({ updateLockWindowInfo( encryptedPassword ? { password: updatedRepeatPassword } : { password: updatedRepeatPassword, locked: true } )(dispatch) - onCancel() + onCancel(true) } } else { setErrMsg('') diff --git a/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/index.tsx b/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/index.tsx index 629eda59f6..c9f0226413 100644 --- a/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/index.tsx +++ b/packages/neuron-ui/src/components/GeneralSetting/LockWindowDialog/index.tsx @@ -13,7 +13,7 @@ const LockWindowDialog = ({ encryptedPassword, }: { show: boolean - onCancel: () => void + onCancel: (success?: boolean) => void encryptedPassword?: string }) => { const [t] = useTranslation() diff --git a/packages/neuron-ui/src/containers/LockWindow/index.tsx b/packages/neuron-ui/src/containers/LockWindow/index.tsx index 3afb787ed2..cc39bf31db 100644 --- a/packages/neuron-ui/src/containers/LockWindow/index.tsx +++ b/packages/neuron-ui/src/containers/LockWindow/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable jsx-a11y/media-has-caption */ -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import { AppActions, getLockWindowInfo, useDispatch, useState as useGlobalState } from 'states' import Spinner from 'widgets/Spinner' import Locked from 'widgets/Icons/Locked.png' @@ -8,10 +8,13 @@ import UnLockMp4 from 'widgets/Icons/unlock.mp4' import SplitPasswordInput from 'widgets/SplitPasswordInput' import { useTranslation } from 'react-i18next' import { clsx, isSuccessResponse } from 'utils' -import { isDark, unlockWindow } from 'services/remote' +import { isDark, signMessage, unlockWindow } from 'services/remote' import { retryUnlockWindow } from 'services/localCache' import { MILLISECS_PER_HOUR, MILLISECS_PER_MIN, MILLISECS_PER_SEC } from 'utils/getSyncLeftTime' +import { ControllerResponse } from 'services/remote/remoteApiWrapper' +import LockWindowDialog from 'components/GeneralSetting/LockWindowDialog' import styles from './lockWindow.module.scss' +import VerifyWallet from './verifyWallet' const passwordLen = 4 const wrongEnterTimes = 3 @@ -24,12 +27,6 @@ const formatterLockMillisecs = (lockMillisecs: number) => { const getWaitMillisecs = (retryTimes: number) => { if (retryTimes % wrongEnterTimes === 0) { - if (retryTimes >= 3 * wrongEnterTimes) { - return 24 * MILLISECS_PER_HOUR - } - if (retryTimes > wrongEnterTimes) { - return 30 * MILLISECS_PER_MIN - } return 5 * MILLISECS_PER_MIN } return undefined @@ -41,7 +38,7 @@ const LockWindow = ({ children }: { children: React.ReactNode }) => { useEffect(() => { getLockWindowInfo(dispatch) }, []) - const { app } = useGlobalState() + const { app, wallet } = useGlobalState() const [password, setPassword] = useState(new Array(passwordLen).fill('')) const [errMsg, setErrMsg] = useState('') const [retryUnlockInfo, setRetryUnlockInfo] = useState(retryUnlockWindow.get()) @@ -130,6 +127,25 @@ const LockWindow = ({ children }: { children: React.ReactNode }) => { } return () => clearInterval(interval) }, [retryUnlockInfo]) + const splitPasswordInputRef = useRef<{ focus: () => void } | null>(null) + const [isVerifyWalletDialogShow, setIsVerifyWalletDialogShow] = useState(false) + const [isResetPasswordDialogShow, setIsResetPasswordDialogShow] = useState(false) + const onVerifyWallet = useCallback( + async (walletPassword?: string) => { + const res: ControllerResponse = await signMessage({ + walletID: wallet?.id ?? '', + message: 'verify wallet for reset lock window password', + password: walletPassword ?? '', + }) + if (isSuccessResponse(res)) { + setIsVerifyWalletDialogShow(false) + setIsResetPasswordDialogShow(true) + } else { + throw new Error(typeof res.message === 'string' ? res.message : res.message.content) + } + }, + [setIsResetPasswordDialogShow, setIsVerifyWalletDialogShow, wallet] + ) if (!app.lockWindowInfo) { return (
@@ -156,11 +172,47 @@ const LockWindow = ({ children }: { children: React.ReactNode }) => { disabled={retryUnlockInfo.retryTimes % wrongEnterTimes === 0 && !!retryUnlockInfo.lastRetryTime} values={password} onChange={onUpdatePassword} + ref={splitPasswordInputRef} />
{errMsg || t('lock-window.enter-lock-password')} + {wallet.isWatchOnly ? null : ( + + )}
+ { + setIsVerifyWalletDialogShow(false) + setTimeout(() => { + // wait for dialog close + splitPasswordInputRef.current?.focus() + }, 10) + }} + onConfirm={onVerifyWallet} + /> + { + setIsResetPasswordDialogShow(false) + if (success) { + setPassword(new Array(passwordLen).fill('')) + } + setTimeout(() => { + // wait for dialog close + splitPasswordInputRef.current?.focus() + }, 10) + }} + />
) } diff --git a/packages/neuron-ui/src/containers/LockWindow/lockWindow.module.scss b/packages/neuron-ui/src/containers/LockWindow/lockWindow.module.scss index a35391d16b..f6eb32bddb 100644 --- a/packages/neuron-ui/src/containers/LockWindow/lockWindow.module.scss +++ b/packages/neuron-ui/src/containers/LockWindow/lockWindow.module.scss @@ -20,6 +20,13 @@ &[data-has-err='true'] { color: var(--error-color); } + + & > button { + border: none; + color: var(--primary-color); + background-color: transparent; + cursor: pointer; + } } .passwordContainer { @@ -53,3 +60,12 @@ height: 88px; } } + +.verifyWallet { + min-width: 600px; + + .hardwalletErr { + justify-content: center; + margin-top: 12px; + } +} diff --git a/packages/neuron-ui/src/containers/LockWindow/verifyWallet.tsx b/packages/neuron-ui/src/containers/LockWindow/verifyWallet.tsx new file mode 100644 index 0000000000..f5822e8535 --- /dev/null +++ b/packages/neuron-ui/src/containers/LockWindow/verifyWallet.tsx @@ -0,0 +1,121 @@ +import React, { useCallback, useEffect, useState } from 'react' +import Dialog from 'widgets/Dialog' +import Hardware from 'widgets/Icons/Hardware.png' +import Button from 'widgets/Button' +import { useHardWallet, usePassword } from 'components/CellManagement/hooks' +import Alert from 'widgets/Alert' +import TextField from 'widgets/TextField' +import { useTranslation } from 'react-i18next' +import styles from './lockWindow.module.scss' + +const VerifyWallet = ({ + wallet, + show, + onCancel, + onConfirm, +}: { + wallet: State.Wallet + show: boolean + onCancel: () => void + onConfirm: (password?: string) => Promise +}) => { + const [t] = useTranslation() + const [loading, setLoading] = useState(false) + const { + isReconnecting, + isNotAvailable, + reconnect, + verifyDeviceStatus, + errorMessage: hardwalletError, + setError: setHardwalletError, + } = useHardWallet({ + wallet, + t, + }) + const { password, error, onPasswordChange, setError, resetPassword } = usePassword() + useEffect(() => { + if (show) { + resetPassword() + } + }, [show, resetPassword]) + useEffect(() => { + if (show && wallet.device) { + verifyDeviceStatus() + } + }, [show, wallet.device, verifyDeviceStatus]) + const onConfirmWrapper = useCallback(() => { + setLoading(true) + onConfirm(wallet.device ? undefined : password) + .catch(err => { + if (wallet.device) { + setHardwalletError(err.message) + } else { + setError(err.message) + } + }) + .finally(() => { + setLoading(false) + }) + }, [wallet.device, onConfirm, setLoading, setHardwalletError, setError, password]) + if (wallet.device) { + return ( + +
+ hard-wallet +
+
+ +
+ {hardwalletError ? ( + + {hardwalletError} + + ) : null} +
+ ) + } + return ( + + + + ) +} + +VerifyWallet.displayName = 'VerifyWallet' + +export default VerifyWallet diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index e37c643389..e62b080695 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -1301,8 +1301,10 @@ "lock-window": { "neuron-is-locked": "Neuron's window has been locked", "enter-lock-password": "Enter lock password", + "forget-password": "Forget password?", "lock-password-error": "Lock windows password error", - "failed-times": "Failed more than {{frequency}} times, please retry after {{time}}" + "failed-times": "Failed more than {{frequency}} times, please retry after {{time}}", + "verify-wallet": "Verify" } } } diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 292de516e2..4d4f9f3e2c 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -1281,8 +1281,10 @@ "lock-window": { "neuron-is-locked": "La ventana de Neuron está bloqueada", "enter-lock-password": "Ingresar contraseña de bloqueo de pantalla", + "forget-password": "Olvidé mi contraseña?", "lock-password-error": "Contraseña de bloqueo de pantalla incorrecta", - "failed-times": "Fallo más de {{frequency}} veces, por favor inténtalo de nuevo después de {{time}}" + "failed-times": "Fallo más de {{frequency}} veces, por favor inténtalo de nuevo después de {{time}}", + "verify-wallet": "Verificar" } } } diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index 680b411a03..5bf2ce6d0d 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -1291,8 +1291,10 @@ "lock-window": { "neuron-is-locked": "a fenêtre de Neuron est verrouillée", "enter-lock-password": "Entrer le mot de passe de verrouillage d'écran", + "forget-password": "Mot de passe oublié?", "lock-password-error": "Mot de passe de verrouillage d'écran incorrect", - "failed-times": "Échec plus de {{frequency}} fois, veuillez réessayer après {{time}}" + "failed-times": "Échec plus de {{frequency}} fois, veuillez réessayer après {{time}}", + "verify-wallet": "Vérifier" } } } diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 899bfc41e1..b354a95306 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -1290,8 +1290,10 @@ "lock-window": { "neuron-is-locked": "Neuron 窗口已鎖定", "enter-lock-password": "輸入鎖屏密碼", + "forget-password": "忘記密碼?", "lock-password-error": "鎖屏密碼錯誤", - "failed-times": "失敗超過 {{frequency}} 次, 請在 {{time}} 後重試" + "failed-times": "失敗超過 {{frequency}} 次, 請在 {{time}} 後重試", + "verify-wallet": "驗證" } } } diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 28d1944f35..284b245276 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -1293,8 +1293,10 @@ "lock-window": { "neuron-is-locked": "Neuron 窗口已锁定", "enter-lock-password": "输入锁屏密码", + "forget-password": "忘记密码?", "lock-password-error": "锁屏密码错误", - "failed-times": "失败超过 {{frequency}} 次, 请在 {{time}} 后重试" + "failed-times": "失败超过 {{frequency}} 次, 请在 {{time}} 后重试", + "verify-wallet": "验证" } } } diff --git a/packages/neuron-ui/src/types/Controller/index.d.ts b/packages/neuron-ui/src/types/Controller/index.d.ts index 3f3d1a1940..83d7cab827 100644 --- a/packages/neuron-ui/src/types/Controller/index.d.ts +++ b/packages/neuron-ui/src/types/Controller/index.d.ts @@ -167,7 +167,7 @@ declare namespace Controller { interface SignMessageParams { walletID: string - address: string + address?: string password: string message: string } diff --git a/packages/neuron-ui/src/widgets/Dialog/index.tsx b/packages/neuron-ui/src/widgets/Dialog/index.tsx index 607bad0d91..7a0056c25e 100644 --- a/packages/neuron-ui/src/widgets/Dialog/index.tsx +++ b/packages/neuron-ui/src/widgets/Dialog/index.tsx @@ -77,7 +77,13 @@ const Dialog = ({ (e.key === 'Escape' && enableCloseWithEsc ? onCancel : undefined)} + onKeyDown={e => { + if (e.key === 'Escape' && enableCloseWithEsc) { + onCancel?.() + } else if (e.key === 'Enter' && showFooter && showConfirm) { + handleConfirm(e) + } + }} role="none" > {showHeader ? ( diff --git a/packages/neuron-ui/src/widgets/SplitPasswordInput/index.tsx b/packages/neuron-ui/src/widgets/SplitPasswordInput/index.tsx index 71a1e56c76..64637d2a1f 100644 --- a/packages/neuron-ui/src/widgets/SplitPasswordInput/index.tsx +++ b/packages/neuron-ui/src/widgets/SplitPasswordInput/index.tsx @@ -1,65 +1,96 @@ -import React, { useCallback, useEffect, useRef } from 'react' +import React, { + type ForwardedRef, + forwardRef, + useCallback, + useEffect, + useImperativeHandle, + useRef, + useState, +} from 'react' import styles from './splitPasswordInput.module.scss' -const SplitPasswordInput = ({ - values, - inputCount, - onChange, - disabled, -}: { - values: (string | undefined)[] - inputCount?: number - onChange: (value: string, idx: number) => void - disabled?: boolean -}) => { - const ref = useRef(null) - const onChangeInput = useCallback>( - e => { - const { dataset, value } = e.currentTarget - if (value.length > 1 || !dataset.idx) return - onChange(value, +dataset.idx) - if (ref.current && value.length > 0) { - const nextInput = ref.current.querySelector(`input:nth-child(${+dataset.idx + 2})`) as HTMLInputElement - nextInput?.focus() - } +const SplitPasswordInput = forwardRef( + ( + { + values, + inputCount, + onChange, + disabled, + }: { + values: (string | undefined)[] + inputCount?: number + onChange: (value: string, idx: number) => void + disabled?: boolean }, - [ref, values] - ) - const onKeyDown = useCallback>( - e => { - const { dataset, value } = e.currentTarget - if (e.key === 'Backspace' && dataset.idx && +dataset.idx > 0 && ref.current && value.length === 0) { - const lastInput = ref.current.querySelector(`input:nth-child(${+dataset.idx})`) as HTMLInputElement - lastInput?.focus() - onChange('', +dataset.idx - 1) + handlerRef: ForwardedRef<{ focus: () => void }> + ) => { + const ref = useRef(null) + const [focusIdx, setFocusIdx] = useState() + useImperativeHandle( + handlerRef, + () => { + return { + focus() { + if (ref.current && focusIdx !== undefined) { + const currentInput = ref.current.querySelector(`input:nth-child(${focusIdx})`) as HTMLInputElement + currentInput?.focus() + } + }, + } + }, + [focusIdx, ref] + ) + const onChangeInput = useCallback>( + e => { + const { dataset, value } = e.currentTarget + if (value.length > 1 || !dataset.idx) return + onChange(value, +dataset.idx) + if (ref.current && value.length > 0) { + const nextInput = ref.current.querySelector(`input:nth-child(${+dataset.idx + 2})`) as HTMLInputElement + nextInput?.focus() + setFocusIdx(+dataset.idx + 2) + } + }, + [ref, values] + ) + const onKeyDown = useCallback>( + e => { + const { dataset, value } = e.currentTarget + if (e.key === 'Backspace' && dataset.idx && +dataset.idx > 0 && ref.current && value.length === 0) { + const lastInput = ref.current.querySelector(`input:nth-child(${+dataset.idx})`) as HTMLInputElement + lastInput?.focus() + onChange('', +dataset.idx - 1) + setFocusIdx(+dataset.idx) + } + }, + [ref, values, setFocusIdx] + ) + useEffect(() => { + if (ref.current && values.join('').length === 0) { + const firstInput = ref.current.querySelector(`input:nth-child(1)`) as HTMLInputElement + firstInput?.focus() + setFocusIdx(1) } - }, - [ref, values] - ) - useEffect(() => { - if (ref.current && values.join('').length === 0) { - const firstInput = ref.current.querySelector(`input:nth-child(1)`) as HTMLInputElement - firstInput?.focus() - } - }, [values]) - return ( -
- {Array.from({ length: inputCount ?? values.length }).map((_, idx) => ( - - ))} -
- ) -} + }, [values, setFocusIdx]) + return ( +
+ {Array.from({ length: inputCount ?? values.length }).map((_, idx) => ( + + ))} +
+ ) + } +) SplitPasswordInput.displayName = 'SplitPasswordInput' export default SplitPasswordInput diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index 911baa4d96..7f5b8b6aed 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -65,6 +65,7 @@ import CellManagement from './cell-management' import { UpdateCellLocalInfo } from '../database/chain/entities/cell-local-info' import { CKBLightRunner } from '../services/light-runner' import { OutPoint } from '@ckb-lumos/base' +import { updateApplicationMenu } from './app/menu' export type Command = 'export-xpubkey' | 'import-xpubkey' | 'delete-wallet' | 'backup-wallet' // Handle channel messages from renderer process and user actions. @@ -317,6 +318,7 @@ export default class ApiController { handle('update-lock-window-info', async (_, params: { locked?: boolean; password?: string }) => { SettingsService.getInstance().updateLockWindowInfo(params) + updateApplicationMenu(BrowserWindow.getFocusedWindow()) return { status: ResponseCode.Success, result: SettingsService.getInstance().lockWindowInfo, diff --git a/packages/neuron-wallet/src/controllers/cell-management.ts b/packages/neuron-wallet/src/controllers/cell-management.ts index 4e0ee8ce20..9bf158a65f 100644 --- a/packages/neuron-wallet/src/controllers/cell-management.ts +++ b/packages/neuron-wallet/src/controllers/cell-management.ts @@ -69,7 +69,12 @@ export default class CellManagement { const addresses = new Set((await AddressService.getAddressesByWalletId(currentWallet.id)).map(v => v.address)) const isMainnet = NetworksService.getInstance().isMainnet() if (!lockScripts.every(v => addresses.has(scriptToAddress(v, isMainnet)))) throw new AddressNotFound() - await SignMessage.sign(currentWallet.id, scriptToAddress(lockScripts[0], isMainnet), password, 'verify cell owner') + await SignMessage.sign({ + walletID: currentWallet.id, + password, + message: 'verify cell owner', + address: scriptToAddress(lockScripts[0], isMainnet), + }) return CellLocalInfoService.updateLiveCellLockStatus(outPoints, locked) } diff --git a/packages/neuron-wallet/src/controllers/sign-message.ts b/packages/neuron-wallet/src/controllers/sign-message.ts index a81916b7f6..cd2d9d0233 100644 --- a/packages/neuron-wallet/src/controllers/sign-message.ts +++ b/packages/neuron-wallet/src/controllers/sign-message.ts @@ -4,12 +4,10 @@ import { ResponseCode } from '../utils/const' export default class SignMessageController { public async sign(params: Controller.Params.SignParams): Promise> { - const signature: string = await SignMessage.sign( - params.walletID, - params.address.trim(), - params.password, - params.message - ) + const signature: string = await SignMessage.sign({ + ...params, + address: params.address?.trim(), + }) if (!signature) { throw new ServiceHasNoResponse('Sign') } diff --git a/packages/neuron-wallet/src/services/sign-message.ts b/packages/neuron-wallet/src/services/sign-message.ts index aeaf32dca7..31882eba15 100644 --- a/packages/neuron-wallet/src/services/sign-message.ts +++ b/packages/neuron-wallet/src/services/sign-message.ts @@ -13,10 +13,20 @@ export default class SignMessage { private static ec = new EC('secp256k1') private static magicString = 'Nervos Message:' - public static async sign(walletID: string, address: string, password: string, message: string): Promise { + public static async sign({ + walletID, + password, + message, + address, + }: { + walletID: string + password: string + message: string + address?: string + }): Promise { const wallet = WalletService.getInstance().get(walletID) const addresses = await AddressService.getAddressesByWalletId(walletID) - let addr = addresses.find(addr => addr.address === address) + let addr = address ? addresses.find(addr => addr.address === address) : addresses[0] if (!addr) { throw new AddressNotFound() } diff --git a/packages/neuron-wallet/src/types/controller.d.ts b/packages/neuron-wallet/src/types/controller.d.ts index a59ea9fa0a..09ec01d15d 100644 --- a/packages/neuron-wallet/src/types/controller.d.ts +++ b/packages/neuron-wallet/src/types/controller.d.ts @@ -65,7 +65,7 @@ declare namespace Controller { interface SignParams { walletID: string - address: string + address?: string password: string message: string } diff --git a/packages/neuron-wallet/tests/controllers/cell-management.test.ts b/packages/neuron-wallet/tests/controllers/cell-management.test.ts index eb5a357e29..c41d19326f 100644 --- a/packages/neuron-wallet/tests/controllers/cell-management.test.ts +++ b/packages/neuron-wallet/tests/controllers/cell-management.test.ts @@ -57,8 +57,7 @@ jest.mock('../../src/services/addresses', () => ({ })) jest.mock('../../src/services/sign-message', () => ({ - sign: (walletID: string, address: string, password: string, message: string) => - signMock(walletID, address, password, message), + sign: (params: { walletID: string; address?: string; password: string; message: string }) => signMock(params), })) jest.mock('../../src/services/cell-local-info', () => ({ @@ -155,7 +154,12 @@ describe('CellManage', () => { getAddressesByWalletIdMock.mockResolvedValueOnce([{ address }]) const outPoints = [new OutPoint(`0x${'00'.repeat(32)}`, '0')] await CellManagement.updateLiveCellsLockStatus(outPoints, true, [lockScript], 'password') - expect(signMock).toBeCalledWith('walletId1', address, 'password', 'verify cell owner') + expect(signMock).toBeCalledWith({ + walletID: 'walletId1', + password: 'password', + message: 'verify cell owner', + address, + }) expect(updateLiveCellLockStatusMock).toBeCalledWith(outPoints, true) }) }) diff --git a/packages/neuron-wallet/tests/services/sign-message.test.ts b/packages/neuron-wallet/tests/services/sign-message.test.ts index 50bc48c7e9..d093abab06 100644 --- a/packages/neuron-wallet/tests/services/sign-message.test.ts +++ b/packages/neuron-wallet/tests/services/sign-message.test.ts @@ -78,14 +78,24 @@ describe(`SignMessage`, () => { it('not match wallet address', async () => { getAddressesByWalletIdMock.mockReturnValueOnce([]) await expect( - SignMessage.sign('walletId', signInfo.address, extendedKeyInfo.password, signInfo.message) + SignMessage.sign({ + walletID: 'walletId', + password: extendedKeyInfo.password, + message: signInfo.message, + address: signInfo.address, + }) ).rejects.toThrow(new AddressNotFound()) }) it('with generate', async () => { getAddressesByWalletIdMock.mockReturnValueOnce([{ address: signInfo.address }]) getPrivateKeyMock.mockReturnValueOnce(signInfo.privateKey) - const res = await SignMessage.sign('walletId', signInfo.address, extendedKeyInfo.password, signInfo.message) + const res = await SignMessage.sign({ + walletID: 'walletId', + password: extendedKeyInfo.password, + message: signInfo.message, + address: signInfo.address, + }) expect(res).toEqual(signInfo.signature) }) @@ -94,7 +104,12 @@ describe(`SignMessage`, () => { walletMock.mockReturnValueOnce({ isHardware: () => true }) const signMessage = jest.fn() hardWalletMock.mockReturnValueOnce({ signMessage }) - await SignMessage.sign('walletId', signInfo.address, extendedKeyInfo.password, signInfo.message) + await SignMessage.sign({ + walletID: 'walletId', + password: extendedKeyInfo.password, + message: signInfo.message, + address: signInfo.address, + }) expect(signMessage).toHaveBeenCalled() }) }) From d7018b3e9a31e7a38a3879e6dd1c939af42830e3 Mon Sep 17 00:00:00 2001 From: devchenyan Date: Mon, 1 Jul 2024 14:41:03 +0800 Subject: [PATCH 7/8] feat: Add a prompt when importing wallets from ckb-cli (#3192) * feat: Add a prompt when importing wallets from ckb-cli * fix: comments * fix: comments * fix --------- Co-authored-by: Chen Yu --- .../importFailureDialog.module.scss | 23 +++++++++++ .../components/ImportFailureDialog/index.tsx | 38 +++++++++++++++++++ .../src/components/ImportKeystore/index.tsx | 26 ++++++++++++- packages/neuron-ui/src/locales/en.json | 2 + packages/neuron-ui/src/locales/es.json | 2 + packages/neuron-ui/src/locales/fr.json | 2 + packages/neuron-ui/src/locales/zh-tw.json | 2 + packages/neuron-ui/src/locales/zh.json | 2 + packages/neuron-ui/src/utils/enums.ts | 1 + .../neuron-wallet/src/controllers/wallets.ts | 16 ++++++++ .../neuron-wallet/src/exceptions/wallet.ts | 8 ++++ packages/neuron-wallet/src/locales/en.ts | 1 + packages/neuron-wallet/src/locales/es.ts | 2 + packages/neuron-wallet/src/locales/fr.ts | 2 + 14 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 packages/neuron-ui/src/components/ImportFailureDialog/importFailureDialog.module.scss create mode 100644 packages/neuron-ui/src/components/ImportFailureDialog/index.tsx diff --git a/packages/neuron-ui/src/components/ImportFailureDialog/importFailureDialog.module.scss b/packages/neuron-ui/src/components/ImportFailureDialog/importFailureDialog.module.scss new file mode 100644 index 0000000000..1162951ae5 --- /dev/null +++ b/packages/neuron-ui/src/components/ImportFailureDialog/importFailureDialog.module.scss @@ -0,0 +1,23 @@ +@import '../../styles/mixin.scss'; + +.container { + width: 680px; + + .content { + max-width: 60%; + text-align: center; + margin: 0 auto; + font-size: 14px; + line-height: 24px; + color: var(--main-text-color); + + button { + border: none; + background: none; + color: var(--primary-color); + text-decoration: underline; + text-underline-offset: 4px; + cursor: pointer; + } + } +} diff --git a/packages/neuron-ui/src/components/ImportFailureDialog/index.tsx b/packages/neuron-ui/src/components/ImportFailureDialog/index.tsx new file mode 100644 index 0000000000..b67b984db5 --- /dev/null +++ b/packages/neuron-ui/src/components/ImportFailureDialog/index.tsx @@ -0,0 +1,38 @@ +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import Dialog from 'widgets/Dialog' +import { openExternal } from 'services/remote' +import styles from './importFailureDialog.module.scss' + +const ImportFailureDialog = ({ show, onClose }: { show: boolean; onClose: () => void }) => { + const [t] = useTranslation() + + const onBtnClick = useCallback(() => { + openExternal( + 'https://github.com/nervosnetwork/ckb-cli/wiki/Import-ckb-cli-keystore-from%26to-Neuron-wallet#ckb-cli-and-neuron-use-the-keystore-in-different-way' + ) + }, []) + + return ( + +
+

+ {t('import-keystore.import-failure-msg')} + +

+
+
+ ) +} + +ImportFailureDialog.displayName = 'ImportFailureDialog' + +export default ImportFailureDialog diff --git a/packages/neuron-ui/src/components/ImportKeystore/index.tsx b/packages/neuron-ui/src/components/ImportKeystore/index.tsx index e3b71e74ac..3e7f211fd1 100644 --- a/packages/neuron-ui/src/components/ImportKeystore/index.tsx +++ b/packages/neuron-ui/src/components/ImportKeystore/index.tsx @@ -19,6 +19,7 @@ import ReplaceDuplicateWalletDialog, { useReplaceDuplicateWallet } from 'compone import { FinishCreateLoading, CreateFirstWalletNav } from 'components/WalletWizard' import TextField from 'widgets/TextField' import { importedWalletDialogShown } from 'services/localCache' +import ImportFailureDialog from '../ImportFailureDialog' import styles from './importKeystore.module.scss' const { MAX_WALLET_NAME_LENGTH, MAX_PASSWORD_LENGTH } = CONSTANTS @@ -49,6 +50,7 @@ const ImportKeystore = () => { const navigate = useNavigate() const [fields, setFields] = useState(defaultFields) const [openingFile, setOpeningFile] = useState(false) + const [isImportFailureDialogShow, setIsImportFailureDialogShow] = useState(false) const { onImportingExitingWalletError, dialogProps } = useReplaceDuplicateWallet() const goBack = useGoBack() @@ -122,6 +124,11 @@ const ImportKeystore = () => { return } + if (res.status === ErrorCode.UnsupportedCkbCliKeystore) { + setIsImportFailureDialogShow(true) + return + } + if (res.message) { const msg = typeof res.message === 'string' ? res.message : res.message.content || '' if (msg) { @@ -140,7 +147,18 @@ const ImportKeystore = () => { closeDialog() }) }, - [fields.name, fields.password, fields.path, navigate, openDialog, closeDialog, disabled, setFields, t] + [ + fields.name, + fields.password, + fields.path, + navigate, + openDialog, + closeDialog, + disabled, + setFields, + t, + setIsImportFailureDialogShow, + ] ) const handleChange = useCallback( @@ -199,6 +217,10 @@ const ImportKeystore = () => { [setFields, wallets, t] ) + const onCloseImportFailureDialog = useCallback(() => { + setIsImportFailureDialogShow(false) + }, [setIsImportFailureDialogShow]) + return ( <>
@@ -248,6 +270,8 @@ const ImportKeystore = () => {
+ + ) } diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index e62b080695..29e7fa9bac 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -202,6 +202,8 @@ "import-keystore": { "title": "Import Keystore File", "select-file": "Choose File", + "import-failure": "Import Failure", + "import-failure-msg": "Neuron does not support importing ckb-cli's keystore file due to different keystore file derivation rules.", "label": { "path": "Keystore File", "name": "Wallet Name", diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 4d4f9f3e2c..a634753a27 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -194,6 +194,8 @@ "import-keystore": { "title": "Importar Keystore", "select-file": "Seleccionar Archivo", + "import-failure": "Error de importación", + "import-failure-msg": "Neuron no soporta la importación del archivo keystore de ckb-cli debido a diferentes reglas de derivación del archivo keystore.", "label": { "path": "Archivo de Claves", "name": "Nombre de la Billetera", diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index 5bf2ce6d0d..0746927a31 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -201,6 +201,8 @@ "import-keystore": { "title": "Importer le fichier Keystore", "select-file": "Choisir le fichier", + "import-failure": "Échec de l'importation", + "import-failure-msg": "Neuron ne prend pas en charge l'importation du fichier Keystore de ckb-cli en raison de différentes règles de dérivation du fichier Keystore.", "label": { "path": "Fichier Keystore", "name": "Nom du Wallet", diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index b354a95306..8d285e9be7 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -196,6 +196,8 @@ "import-keystore": { "title": "導入 Keystore 文件", "select-file": "選擇文件", + "import-failure": "導入失敗", + "import-failure-msg": "由於不同的 Keystore 文件衍生規則,Neuron 不支持導入 ckb-cli 的 Keystore 文件。", "label": { "path": "Keystore 檔案", "name": "錢包名稱", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 284b245276..ef43079205 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -195,6 +195,8 @@ "import-keystore": { "title": "导入 Keystore 文件", "select-file": "选择文件", + "import-failure": "导入失败", + "import-failure-msg": "由于不同的 Keystore 文件派生规则,Neuron 不支持导入 ckb-cli 的 Keystore 文件。", "label": { "path": "Keystore 文件", "name": "钱包名称", diff --git a/packages/neuron-ui/src/utils/enums.ts b/packages/neuron-ui/src/utils/enums.ts index 747c8398a5..23c0b96b6f 100644 --- a/packages/neuron-ui/src/utils/enums.ts +++ b/packages/neuron-ui/src/utils/enums.ts @@ -112,6 +112,7 @@ export enum ErrorCode { // active warning WaitForFullySynced = 600, DuplicateImportWallet = 118, + UnsupportedCkbCliKeystore = 119, } export enum SyncStatus { diff --git a/packages/neuron-wallet/src/controllers/wallets.ts b/packages/neuron-wallet/src/controllers/wallets.ts index db66030bfa..98566937b4 100644 --- a/packages/neuron-wallet/src/controllers/wallets.ts +++ b/packages/neuron-wallet/src/controllers/wallets.ts @@ -22,6 +22,7 @@ import { UsedName, MainnetAddressRequired, TestnetAddressRequired, + UnsupportedCkbCliKeystore, } from '../exceptions' import AddressService from '../services/addresses' import TransactionSender from '../services/transaction-sender' @@ -166,6 +167,21 @@ export default class WalletsController { throw new InvalidJSON() } const keystoreObject = Keystore.fromJson(keystore) + + if (keystoreObject.isFromCkbCli()) { + throw new UnsupportedCkbCliKeystore() + } + + try { + keystoreObject.extendedPrivateKey(password) + } catch (error) { + if (error.message === 'Incorrect password!') { + throw new IncorrectPassword() + } else { + throw error + } + } + const masterPrivateKey = keystoreObject.extendedPrivateKey(password) const masterKeychain = new Keychain( Buffer.from(bytes.bytify(masterPrivateKey.privateKey)), diff --git a/packages/neuron-wallet/src/exceptions/wallet.ts b/packages/neuron-wallet/src/exceptions/wallet.ts index 25962d77a3..82af7d255f 100644 --- a/packages/neuron-wallet/src/exceptions/wallet.ts +++ b/packages/neuron-wallet/src/exceptions/wallet.ts @@ -85,6 +85,13 @@ export class DuplicateImportWallet extends Error { } } +export class UnsupportedCkbCliKeystore extends Error { + public code = 119 + constructor() { + super(t('messages.unsupported-ckb-cli-keystore')) + } +} + export default { WalletNotFound, CurrentWalletNotSet, @@ -96,4 +103,5 @@ export default { CapacityNotEnoughForChange, InvalidKeystore, DuplicateImportWallet, + UnsupportedCkbCliKeystore, } diff --git a/packages/neuron-wallet/src/locales/en.ts b/packages/neuron-wallet/src/locales/en.ts index 5c87164364..6a7dad241b 100644 --- a/packages/neuron-wallet/src/locales/en.ts +++ b/packages/neuron-wallet/src/locales/en.ts @@ -122,6 +122,7 @@ export default { 'connect-device-failed': 'The device cannot be connected, please check your connection.', 'unsupported-manufacturer': 'Devices from {{manufacturer}} are not yet supported.', 'wallet-not-supported-function': 'This wallet does not support {name} function.', + 'unsupported-ckb-cli-keystore': "Neuron does not support importing ckb-cli's keystore file.", 'invalid-transaction-file': 'Invalid transaction file.', 'offline-sign-failed': 'Signing failed, please check if you are signing with the correct wallet.', 'multisig-script-prefix-error': 'The multisig config is error', diff --git a/packages/neuron-wallet/src/locales/es.ts b/packages/neuron-wallet/src/locales/es.ts index 60134165fd..001e571013 100644 --- a/packages/neuron-wallet/src/locales/es.ts +++ b/packages/neuron-wallet/src/locales/es.ts @@ -121,6 +121,8 @@ export default { 'connect-device-failed': 'No se puede conectar el dispositivo, por favor, verifica tu conexión.', 'unsupported-manufacturer': 'Los dispositivos de {{manufacturer}} aún no son compatibles.', 'wallet-not-supported-function': 'Esta billetera no admite la función {name}.', + 'unsupported-ckb-cli-keystore': + 'Neuron no soporta la importación del archivo de almacenamiento de claves de ckb-cli.', 'invalid-transaction-file': 'Archivo de transacción no válido.', 'offline-sign-failed': 'Firma fallida, por favor, verifica si estás firmando con la billetera correcta.', 'multisig-script-prefix-error': 'Error en la configuración multifirma', diff --git a/packages/neuron-wallet/src/locales/fr.ts b/packages/neuron-wallet/src/locales/fr.ts index 12c9cf30a9..bcc1a30316 100644 --- a/packages/neuron-wallet/src/locales/fr.ts +++ b/packages/neuron-wallet/src/locales/fr.ts @@ -124,6 +124,8 @@ export default { 'connect-device-failed': "L'appareil ne peut pas être connecté, veuillez vérifier votre connexion.", 'unsupported-manufacturer': 'Les appareils de {{manufacturer}} ne sont pas encore pris en charge.', 'wallet-not-supported-function': 'Ce Wallet ne prend pas en charge la fonction {name}.', + 'unsupported-ckb-cli-keystore': + "Neuron ne prend pas en charge l'importation du fichier de stockage de clés de ckb-cli.", 'invalid-transaction-file': 'Fichier de transaction non valide.', 'offline-sign-failed': 'Échec de la signature, veuillez vérifier si vous signez avec le bon Wallet.', 'multisig-script-prefix-error': 'La configuration multisig est erronée', From fe2082c38999e16b2d09d4f417d7b07d5d1f16ea Mon Sep 17 00:00:00 2001 From: devchenyan Date: Thu, 4 Jul 2024 16:24:53 +0800 Subject: [PATCH 8/8] fix: Multisig address sync tip language (#3198) --- packages/neuron-ui/src/components/MultisigAddress/index.tsx | 6 ++---- packages/neuron-ui/src/locales/en.json | 2 ++ packages/neuron-ui/src/locales/es.json | 2 ++ packages/neuron-ui/src/locales/fr.json | 2 ++ packages/neuron-ui/src/locales/zh-tw.json | 2 ++ packages/neuron-ui/src/locales/zh.json | 2 ++ 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/neuron-ui/src/components/MultisigAddress/index.tsx b/packages/neuron-ui/src/components/MultisigAddress/index.tsx index d788190cf2..4f7dc2eb1a 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/index.tsx +++ b/packages/neuron-ui/src/components/MultisigAddress/index.tsx @@ -492,10 +492,8 @@ const MultisigAddress = () => { confirmProps={{ type: 'cancel', className: styles.confirmBtn }} > Synchronization Abort -

Synchronization Abort

-

- Leaving the current window will cause the multisig synchronization to be aborted, so please confirm to leave. -

+

{t('multisig-address.synchronization-abort')}

+

{t('multisig-address.synchronization-abort-msg')}

{sendAction.sendFromMultisig && sendAction.isDialogOpen ? ( diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 29e7fa9bac..65b3341a08 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -1139,6 +1139,8 @@ "export": "Export Tx" }, "multi-details": "Multisig address details", + "synchronization-abort": "Synchronization Abort", + "synchronization-abort-msg": "Leaving the current window will cause the multisig synchronization to be aborted, so please confirm to leave.", "create-dialog": { "title": "Create multisig address", "preview-title": "Multisig address has been generated", diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index a634753a27..b8561c404f 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -1122,6 +1122,8 @@ "export": "Exportar Tx" }, "multi-details": "Detalles de la dirección multifirma", + "synchronization-abort": "Abortar sincronización", + "synchronization-abort-msg": "Salir de la ventana actual provocará que se aborte la sincronización de multisig, por favor confirme si desea salir.", "create-dialog": { "title": "Crear dirección multifirma", "preview-title": "La dirección multifirma se ha generado", diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index 0746927a31..985df1b7c9 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -1129,6 +1129,8 @@ "export": "Exporter Tx" }, "multi-details": "Détails de l'adresse multisig", + "synchronization-abort": "Abandon de la synchronisation", + "synchronization-abort-msg": "Quitter la fenêtre actuelle entraînera l'abandon de la synchronisation multisig, veuillez confirmer si vous souhaitez quitter.", "create-dialog": { "title": "Créer une adresse multisig", "preview-title": "L'adresse multisig a été générée", diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 8d285e9be7..d12dd36228 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -1132,6 +1132,8 @@ "export": "導出交易" }, "multi-details": "多簽地址詳情", + "synchronization-abort": "同步中止", + "synchronization-abort-msg": "離開當前窗口將導致多簽同步中止,請確認是否離開。", "create-dialog": { "title": "創建多重簽名地址", "preview-title": "簽名地址已生成", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index ef43079205..0d5bba3a30 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -1131,6 +1131,8 @@ "export": "导出交易" }, "multi-details": "多签地址详情", + "synchronization-abort": "同步中止", + "synchronization-abort-msg": "离开当前窗口将导致多签同步中止,请确认是否离开。", "create-dialog": { "title": "创建多重签名地址", "preview-title": "多签地址已生成",