Skip to content

Commit d3a917c

Browse files
committed
feat(neuron-ui): add import keystore
add import wallet with keystore action creator add import keystore menuitem in the application menu
1 parent 4e73cac commit d3a917c

21 files changed

Lines changed: 274 additions & 26 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { useState, useCallback, useMemo } from 'react'
2+
import { RouteComponentProps } from 'react-router-dom'
3+
import { Stack, DefaultButton, PrimaryButton, TextField } from 'office-ui-fabric-react'
4+
import { useTranslation } from 'react-i18next'
5+
import { showOpenDialog } from 'services/remote'
6+
import { importWalletWithKeystore } from 'states/stateProvider/actionCreators'
7+
import { StateWithDispatch } from 'states/stateProvider/reducer'
8+
import { useGoBack } from 'utils/hooks'
9+
10+
const defaultFields = {
11+
path: '',
12+
name: '',
13+
password: '',
14+
}
15+
16+
const ImportKeystore = (props: React.PropsWithoutRef<StateWithDispatch & RouteComponentProps>) => {
17+
const [t] = useTranslation()
18+
const {
19+
history,
20+
dispatch,
21+
settings: { wallets },
22+
} = props
23+
const [fields, setFields] = useState(defaultFields)
24+
const goBack = useGoBack(history)
25+
26+
const exsitingNames = useMemo(() => {
27+
return wallets.map(w => w.name)
28+
}, [wallets])
29+
30+
const onFileClick = useCallback(() => {
31+
showOpenDialog({
32+
title: 'import keystore',
33+
onUpload: (filePaths: string[]) => {
34+
if (!filePaths || filePaths.length === 0) {
35+
return
36+
}
37+
const filePath = filePaths[0]
38+
const filename = filePath.split('/').pop() || 'Imported wallet'
39+
setFields({
40+
...fields,
41+
path: filePath,
42+
name: filename,
43+
})
44+
},
45+
})
46+
}, [fields])
47+
48+
const onSubmit = useCallback(() => {
49+
importWalletWithKeystore({
50+
name: fields.name,
51+
keystorePath: fields.path,
52+
password: fields.password,
53+
})(dispatch, history)
54+
}, [fields.name, fields.password, fields.path, history, dispatch])
55+
56+
return (
57+
<Stack tokens={{ childrenGap: 15 }}>
58+
<Stack tokens={{ childrenGap: 15 }}>
59+
{Object.entries(fields).map(([key, value]) => {
60+
return (
61+
<TextField
62+
key={key}
63+
onClick={key === 'path' ? onFileClick : undefined}
64+
label={t(`import-keystore.label.${key}`)}
65+
placeholder={t(`import-keystore.placeholder.${key}`)}
66+
type={key === 'password' ? 'password' : 'text'}
67+
readOnly={key === 'path'}
68+
value={value}
69+
validateOnLoad={false}
70+
onGetErrorMessage={(text?: string) => {
71+
if (text === '') {
72+
return t('messages.is-required', { field: t(`import-keystore.label.${key}`) })
73+
}
74+
if (key === 'name' && exsitingNames.includes(text || '')) {
75+
return t('messages.is-used', { field: t(`import-keystore.label.${key}`) })
76+
}
77+
return ''
78+
}}
79+
onChange={(_e: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
80+
if (newValue !== undefined) {
81+
setFields({
82+
...fields,
83+
[key]: newValue,
84+
})
85+
}
86+
}}
87+
/>
88+
)
89+
})}
90+
</Stack>
91+
<Stack horizontal horizontalAlign="end" tokens={{ childrenGap: 15 }}>
92+
<DefaultButton onClick={goBack}>{t('import-keystore.button.back')}</DefaultButton>
93+
<PrimaryButton disabled={!(fields.name && fields.path && fields.password)} onClick={onSubmit}>
94+
{t('import-keystore.button.submit')}
95+
</PrimaryButton>
96+
</Stack>
97+
</Stack>
98+
)
99+
}
100+
101+
ImportKeystore.displayName = 'ImportKeystore'
102+
export default ImportKeystore

packages/neuron-ui/src/components/WalletSetting/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ const buttons = [
1717
url: `${Routes.WalletWizard}${WalletWizardPath.Mnemonic}/${MnemonicAction.Create}`,
1818
},
1919
{
20-
label: 'wizard.import-wallet',
20+
label: 'wizard.import-mnemonic',
2121
url: `${Routes.WalletWizard}${WalletWizardPath.Mnemonic}/${MnemonicAction.Import}`,
2222
},
23+
{
24+
label: 'wizard.import-keystore',
25+
url: Routes.ImportKeystore,
26+
},
2327
]
2428

2529
const WalletSetting = ({

packages/neuron-ui/src/components/WalletWizard/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,18 @@ const Welcome = ({ rootPath = '/wizard', wallets = [], history }: WizardElementP
7474
<Stack horizontal horizontalAlign="center" verticalAlign="center" tokens={{ childrenGap: 40 }}>
7575
<PrimaryButton
7676
styles={{ root: [{ height: '60px' }] }}
77-
text={t('wizard.import-wallet')}
77+
text={t('wizard.import-mnemonic')}
7878
onClick={next(`${rootPath}${WalletWizardPath.Mnemonic}/${MnemonicAction.Import}`)}
7979
iconProps={{ iconName: 'Import', styles: buttonGrommetIconStyles }}
8080
/>
8181
<span>{t('common.or')}</span>
82+
<PrimaryButton
83+
styles={{ root: [{ height: '60px' }] }}
84+
text={t('wizard.import-keystore')}
85+
onClick={next(Routes.ImportKeystore)}
86+
iconProps={{ iconName: 'Keystore', styles: buttonGrommetIconStyles }}
87+
/>
88+
<span>{t('common.or')}</span>
8289
<DefaultButton
8390
styles={{ root: [{ height: '60px' }] }}
8491
text={t('wizard.create-new-wallet')}

packages/neuron-ui/src/containers/Main/hooks.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,12 @@ export const useSubscription = ({
152152
break
153153
}
154154
case 'current-wallet': {
155-
updateCurrentWallet()(dispatch)
155+
updateCurrentWallet()(dispatch, history)
156156
break
157157
}
158158
case 'wallets': {
159-
updateWalletList()(dispatch)
160-
updateCurrentWallet()(dispatch)
159+
updateWalletList()(dispatch, history)
160+
updateCurrentWallet()(dispatch, history)
161161
break
162162
}
163163
default: {

packages/neuron-ui/src/containers/Main/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { StateDispatch } from 'states/stateProvider/reducer'
77

88
import Overview from 'components/Overview'
99
import WalletWizard from 'components/WalletWizard'
10+
import ImportKeystore from 'components/ImportKeystore'
1011
import Send from 'components/Send'
1112
import Receive from 'components/Receive'
1213
import History from 'components/History'
@@ -24,7 +25,7 @@ import { useSubscription, useSyncChainData, useOnCurrentWalletChange } from './h
2425

2526
export const mainContents: CustomRouter.Route[] = [
2627
{
27-
name: `launch`,
28+
name: `Launch`,
2829
path: Routes.Launch,
2930
exact: true,
3031
comp: LaunchScreen,
@@ -94,6 +95,12 @@ export const mainContents: CustomRouter.Route[] = [
9495
exact: false,
9596
comp: WalletWizard,
9697
},
98+
{
99+
name: `ImportKeystore`,
100+
path: Routes.ImportKeystore,
101+
exact: false,
102+
comp: ImportKeystore,
103+
},
97104
{
98105
name: `PasswordRequest`,
99106
path: '/',

packages/neuron-ui/src/locales/en.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
"welcome-to-nervos-neuron": "Welcome to Neuron",
4343
"create-or-import-your-first-wallet": "Create or import your first wallet",
4444
"create-new-wallet": "Create a Wallet",
45+
"import-mnemonic": "Import mnemonic words",
46+
"import-keystore": "Import a keystore file",
4547
"import-wallet": "Import a Wallet",
4648
"next": "Next",
4749
"back": "Back",
@@ -59,6 +61,22 @@
5961
"complex-password": "The password is a string of 8 to 50 characters consisting of three types of characters: uppercase letters, lowercase letters, numbers, and special symbols, and must start with a letter or a number.",
6062
"same-password": "The password and confirm password should be same"
6163
},
64+
"import-keystore": {
65+
"label": {
66+
"path": "Keystore File",
67+
"name": "Wallet Name",
68+
"password": "Password"
69+
},
70+
"placeholder": {
71+
"path": "Click to select the Keystore",
72+
"name": "Name for the new wallet",
73+
"password": "Password to verify the keystore"
74+
},
75+
"button": {
76+
"back": "Back",
77+
"submit": "Submit"
78+
}
79+
},
6280
"detail": {
6381
"more-transactions": "More"
6482
},
@@ -252,7 +270,9 @@
252270
"lock-arg-copied": "Lock Arg has been copied to the clipboard",
253271
"transaction-not-found": "The transaction is not found",
254272
"rpc-url-should-have-protocol": "The RPC URL should start with http(s)://",
255-
"rpc-url-should-have-no-whitespaces": "The RPC URL should have no whitespaces"
273+
"rpc-url-should-have-no-whitespaces": "The RPC URL should have no whitespaces",
274+
"is-required": "{{field}} is required",
275+
"is-used": "{{field}} is used"
256276
},
257277
"sync": {
258278
"syncing": "Syncing",

packages/neuron-ui/src/locales/zh.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
"welcome-to-nervos-neuron": "欢迎使用 Neuron",
4343
"create-or-import-your-first-wallet": "创建或导入您的第一个钱包",
4444
"create-new-wallet": "创建新钱包",
45+
"import-mnemonic": "导入助记词",
46+
"import-keystore": "导入 Keystore 文件",
4547
"import-wallet": "导入钱包",
4648
"next": "下一步",
4749
"back": "上一步",
@@ -59,6 +61,22 @@
5961
"complex-password": "密码为 8 至 50 位由大写字母、小写字母、数字、特殊符号中三类字符组成的字符串, 且必须以字母或者数字开头",
6062
"same-password": "两次输入密码应一致"
6163
},
64+
"import-keystore": {
65+
"label": {
66+
"path": "Keystore 文件",
67+
"name": "钱包名称",
68+
"password": "密码"
69+
},
70+
"placeholder": {
71+
"path": "选择 Keystore 文件",
72+
"name": "新钱包名称",
73+
"password": "输入密码以验证 Keystore"
74+
},
75+
"button": {
76+
"back": "返回",
77+
"submit": "提交"
78+
}
79+
},
6280
"detail": {
6381
"more-transactions": "更多交易记录"
6482
},
@@ -252,7 +270,9 @@
252270
"lock-arg-copied": "Lock Arg 已复制到剪贴板",
253271
"transaction-not-found": "未找到交易",
254272
"network-address-should-have-protocol": "RPC 地址应以 http(s)//: 开始",
255-
"network-address-should-have-no-whitespaces": "RPC 地址不能包含空格"
273+
"network-address-should-have-no-whitespaces": "RPC 地址不能包含空格",
274+
"is-required": "{{field}}是必须的",
275+
"is-used": "{{field}}已使用"
256276
},
257277
"sync": {
258278
"syncing": "同步中",

packages/neuron-ui/src/services/remote/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,25 @@ export const showErrorMessage = (title: string, content: string) => {
5555
}
5656
}
5757

58+
export const showOpenDialog = (opt: { title: string; message?: string; onUpload: Function }) => {
59+
if (!window.remote) {
60+
window.alert('remote is not supported')
61+
}
62+
const { onUpload, ...options } = opt
63+
return window.remote.require('electron').dialog.showOpenDialog(
64+
{
65+
...options,
66+
},
67+
onUpload
68+
)
69+
}
70+
5871
export default {
5972
getLocale,
6073
validateMnemonic,
6174
generateMnemonic,
6275
showMessage,
6376
showErrorMessage,
77+
showOpenDialog,
6478
getWinID,
6579
}

packages/neuron-ui/src/services/remote/wallets.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export const importMnemonic = controllerMethodWrapper(CONTROLLER_NAME)(
1111
controller => (params: Controller.ImportMnemonicParams) => controller.importMnemonic(params)
1212
)
1313

14+
export const importKeystore = controllerMethodWrapper(CONTROLLER_NAME)(
15+
controller => (params: Controller.ImportKeystoreParams) => controller.importKeystore(params)
16+
)
17+
1418
export const deleteWallet = controllerMethodWrapper(CONTROLLER_NAME)(
1519
controller => (params: Controller.DeleteWalletParams) => controller.delete(params)
1620
)
@@ -38,6 +42,7 @@ export default {
3842
updateWallet,
3943
getWalletList,
4044
importMnemonic,
45+
importKeystore,
4146
deleteWallet,
4247
backupWallet,
4348
getCurrentWallet,

packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { AppActions, StateDispatch } from 'states/stateProvider/reducer'
22
import {
33
getWalletList,
44
importMnemonic,
5+
importKeystore,
56
getCurrentWallet,
67
updateWallet,
78
setCurrentWallet as setRemoteCurrentWallet,
@@ -13,17 +14,21 @@ import {
1314
showErrorMessage,
1415
} from 'services/remote'
1516
import initStates from 'states/initStates'
17+
import { WalletWizardPath } from 'components/WalletWizard'
1618
import i18n from 'utils/i18n'
1719
import { wallets as walletsCache, currentWallet as currentWalletCache } from 'utils/localCache'
1820
import { Routes } from 'utils/const'
1921
import addressesToBalance from 'utils/addressesToBalance'
2022
import { NeuronWalletActions } from '../reducer'
2123
import { addNotification, addPopup } from './app'
2224

23-
export const updateCurrentWallet = () => (dispatch: StateDispatch) => {
25+
export const updateCurrentWallet = () => (dispatch: StateDispatch, history: any) => {
2426
getCurrentWallet().then(res => {
2527
if (res.status) {
2628
const payload = res.result || initStates.wallet
29+
if (!payload || !payload.id) {
30+
history.push(`${Routes.WalletWizard}${WalletWizardPath.Welcome}`)
31+
}
2732
dispatch({
2833
type: NeuronWalletActions.UpdateCurrentWallet,
2934
payload,
@@ -43,7 +48,7 @@ export const createWalletWithMnemonic = (params: Controller.ImportMnemonicParams
4348
if (res.status) {
4449
history.push(Routes.Overview)
4550
} else {
46-
showErrorMessage(i18n.t('error'), i18n.t(res.message.title))
51+
showErrorMessage(i18n.t('messages.error'), i18n.t(res.message.title))
4752
}
4853
})
4954
}
@@ -56,14 +61,31 @@ export const importWalletWithMnemonic = (params: Controller.ImportMnemonicParams
5661
if (res.status) {
5762
history.push(Routes.Overview)
5863
} else {
59-
showErrorMessage(i18n.t('error'), i18n.t(res.message.title))
64+
showErrorMessage(i18n.t('messages.error'), i18n.t(res.message.title))
6065
}
6166
})
6267
}
63-
export const updateWalletList = () => (dispatch: StateDispatch) => {
68+
69+
export const importWalletWithKeystore = (params: Controller.ImportKeystoreParams) => (
70+
_dispatch: StateDispatch,
71+
history: any
72+
) => {
73+
importKeystore(params).then(res => {
74+
if (res.status) {
75+
history.push(Routes.Overview)
76+
} else {
77+
showErrorMessage(i18n.t('messages.error'), i18n.t(res.message.title))
78+
}
79+
})
80+
}
81+
82+
export const updateWalletList = () => (dispatch: StateDispatch, history: any) => {
6483
getWalletList().then(res => {
6584
if (res.status) {
6685
const payload = res.result || []
86+
if (!payload.length) {
87+
history.push(`${Routes.WalletWizard}${WalletWizardPath.Welcome}`)
88+
}
6789
dispatch({
6890
type: NeuronWalletActions.UpdateWalletList,
6991
payload,

0 commit comments

Comments
 (0)