Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions packages/neuron-ui/src/components/ImportKeystore/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ import { importWalletWithKeystore } from 'states/stateProvider/actionCreators'
import { StateWithDispatch } from 'states/stateProvider/reducer'
import { useGoBack } from 'utils/hooks'
import generateWalletName from 'utils/generateWalletName'
import { ErrorCode, MAX_WALLET_NAME_LENGTH, MAX_PASSWORD_LENGTH } from 'utils/const'

const defaultFields = {
interface KeystoreFields {
path: string
name: string | undefined
password: string
}

const defaultFields: KeystoreFields = {
path: '',
name: '',
name: undefined,
password: '',
}

Expand All @@ -25,7 +32,7 @@ const ImportKeystore = (props: React.PropsWithoutRef<StateWithDispatch & RouteCo
const goBack = useGoBack(history)

useEffect(() => {
if (fields.name === '') {
if (fields.name === undefined) {
const name = generateWalletName(wallets, wallets.length + 1, t)
setFields({
...fields,
Expand Down Expand Up @@ -60,7 +67,7 @@ const ImportKeystore = (props: React.PropsWithoutRef<StateWithDispatch & RouteCo

const onSubmit = useCallback(() => {
importWalletWithKeystore({
name: fields.name,
name: fields.name || '',
keystorePath: fields.path,
password: fields.password,
})(dispatch, history)
Expand All @@ -70,6 +77,12 @@ const ImportKeystore = (props: React.PropsWithoutRef<StateWithDispatch & RouteCo
<Stack verticalFill verticalAlign="center" tokens={{ childrenGap: 15 }}>
<Stack tokens={{ childrenGap: 15 }}>
{Object.entries(fields).map(([key, value]) => {
let maxLength: number | undefined
if (key === 'name') {
maxLength = MAX_WALLET_NAME_LENGTH
} else if (key === 'password') {
maxLength = MAX_PASSWORD_LENGTH
}
return (
<TextField
key={key}
Expand All @@ -78,14 +91,15 @@ const ImportKeystore = (props: React.PropsWithoutRef<StateWithDispatch & RouteCo
placeholder={t(`import-keystore.placeholder.${key}`)}
type={key === 'password' ? 'password' : 'text'}
readOnly={key === 'path'}
maxLength={maxLength}
value={value}
validateOnLoad={false}
onGetErrorMessage={(text?: string) => {
if (text === '') {
return t('messages.is-required', { field: t(`import-keystore.label.${key}`) })
return t(`messages.codes.${ErrorCode.FieldRequired}`, { fieldName: `keystore-${key}` })
}
if (key === 'name' && isNameUsed) {
return t('messages.is-used', { field: t(`import-keystore.label.${key}`) })
return t(`messages.codes.${ErrorCode.FieldUsed}`, { fieldName: `name`, fieldValue: text })
}
return ''
}}
Expand Down
140 changes: 90 additions & 50 deletions packages/neuron-ui/src/components/NetworkEditor/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { useState, useEffect, useMemo, useCallback } from 'react'
import { StateDispatch } from 'states/stateProvider/reducer'
import { createNetwork, updateNetwork, addNotification } from 'states/stateProvider/actionCreators'

import { Message, MAX_NETWORK_NAME_LENGTH } from 'utils/const'
import { MAX_NETWORK_NAME_LENGTH, ErrorCode } from 'utils/const'

import i18n from 'utils/i18n'
import { verifyNetworkName, verifyURL } from 'utils/validators'

enum PlaceHolder {
Name = 'My Custom Node',
Expand Down Expand Up @@ -69,8 +70,12 @@ export const useInitialize = (
initialize(network)
} else {
addNotification({
type: 'warning',
content: i18n.t('messages.network-is-not-found'),
type: 'warning' as State.MessageType,
timestamp: +new Date(),
code: ErrorCode.FieldNotFound,
meta: {
fieldName: 'network',
},
})
}
}
Expand All @@ -86,14 +91,9 @@ export const useInputs = (editor: EditorType, usedNetworkNames: string[], t: any
tooltip: TooltipText.URL,
placeholder: PlaceHolder.URL,
onGetErrorMessage: (url: string) => {
if (!url) {
return t('messages.url-required')
}
if (!/^https?:\/\//.test(url)) {
return t('messages.rpc-url-should-have-protocol')
}
if (/\s/.test(url)) {
return t('messages.rpc-url-should-have-no-whitespaces')
const res = verifyURL(url)
if (typeof res === 'object') {
return t(`messages.codes.${res.code}`, { fieldName: 'remote', fieldValue: url })
}
return ''
},
Expand All @@ -104,11 +104,13 @@ export const useInputs = (editor: EditorType, usedNetworkNames: string[], t: any
tooltip: TooltipText.Name,
placeholder: PlaceHolder.Name,
onGetErrorMessage: (name: string) => {
if (!name) {
return t('messages.name-required')
}
if (usedNetworkNames.includes(name)) {
return t('messages.network-name-used')
const res = verifyNetworkName(name, usedNetworkNames)
if (typeof res === 'object') {
return t(`messages.codes.${res.code}`, {
fieldName: 'name',
fieldValue: name,
length: MAX_NETWORK_NAME_LENGTH,
})
}
return ''
},
Expand All @@ -118,13 +120,21 @@ export const useInputs = (editor: EditorType, usedNetworkNames: string[], t: any
)
}

export const useIsInputsValid = (editor: EditorType, cachedNetwork: State.Network | undefined) => {
const [errors, setErrors] = useState([!cachedNetwork && !editor.name.value, !cachedNetwork && !editor.remote.value])
export const useIsInputsValid = (
editor: EditorType,
usedNetworkNames: string[],
cachedNetwork: State.Network | undefined
) => {
const hasError = useMemo(() => {
const nameRes = verifyNetworkName(editor.name.value, usedNetworkNames)
const URLRes = verifyURL(editor.remote.value)
return !(nameRes === true && URLRes === true)
}, [editor.name.value, editor.remote.value, usedNetworkNames])
const notModified = useMemo(
() => cachedNetwork && (cachedNetwork.name === editor.name.value && cachedNetwork.remote === editor.remote.value),
[cachedNetwork, editor.name.value, editor.remote.value]
)
return { errors, setErrors, notModified }
return { hasError, notModified }
}

export const useHandleSubmit = (
Expand All @@ -136,55 +146,85 @@ export const useHandleSubmit = (
dispatch: StateDispatch
) =>
useCallback(async () => {
const warning = {
type: 'warning' as 'warning',
timestamp: Date.now(),
content: '',
}
let errorMessage: State.Message<ErrorCode, { fieldName: string; fieldValue?: string; length?: string }> | undefined
if (!name) {
return addNotification({
...warning,
content: i18n.t(Message.NameRequired),
})(dispatch)
errorMessage = {
type: 'warning',
timestamp: +new Date(),
code: ErrorCode.FieldRequired,
meta: {
fieldName: 'name',
},
}
return addNotification(errorMessage)(dispatch)
}
if (name.length > MAX_NETWORK_NAME_LENGTH) {
return addNotification({
...warning,
content: i18n.t(Message.LengthOfNameShouldBeLessThanOrEqualTo, {
length: MAX_NETWORK_NAME_LENGTH,
}),
})(dispatch)
errorMessage = {
type: 'warning',
timestamp: +new Date(),
code: ErrorCode.FieldTooLong,
meta: {
fieldName: 'name',
fieldValue: name,
length: `${MAX_NETWORK_NAME_LENGTH}`,
},
}
return addNotification(errorMessage)(dispatch)
}
if (!remote) {
return addNotification({
...warning,
content: i18n.t(Message.URLRequired),
})(dispatch)
errorMessage = {
type: 'warning',
timestamp: +new Date(),
code: ErrorCode.FieldRequired,
meta: {
fieldName: 'remote',
},
}
return addNotification(errorMessage)(dispatch)
}
if (!remote.startsWith('http')) {
return addNotification({
...warning,
content: i18n.t(Message.ProtocolRequired),
})(dispatch)
errorMessage = {
type: 'warning',
timestamp: +new Date(),
code: ErrorCode.ProtocolRequired,
meta: {
fieldName: 'remote',
fieldValue: remote,
},
}
return addNotification(errorMessage)(dispatch)
}
// verification, for now, only name is unique
if (id === 'new') {
if (networks.some(network => network.name === name)) {
return addNotification({
...warning,
content: i18n.t(Message.NetworkNameUsed),
})(dispatch)
errorMessage = {
type: 'warning',
timestamp: +new Date(),
code: ErrorCode.FieldUsed,
meta: {
fieldName: 'name',
fieldValue: name,
},
}
return addNotification(errorMessage)(dispatch)
}
return createNetwork({
name,
remote,
})(dispatch, history)
}

if (networks.some(network => network.name === name && network.id !== id)) {
return addNotification({
...warning,
content: i18n.t(Message.NetworkNameUsed),
})(dispatch)
errorMessage = {
type: 'warning',
timestamp: +new Date(),
code: ErrorCode.FieldUsed,
meta: {
fieldName: 'name',
fieldValue: name,
},
}
return addNotification(errorMessage)(dispatch)
}
return updateNetwork({
networkID: id!,
Expand Down
18 changes: 4 additions & 14 deletions packages/neuron-ui/src/components/NetworkEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,22 @@ const NetworkEditor = ({
const goBack = useGoBack(history)
useInitialize(id, networks, editor.initialize, dispatch)

const { errors, setErrors, notModified } = useIsInputsValid(editor, cachedNetwork)
const { hasError, notModified } = useIsInputsValid(editor, usedNetworkNames, cachedNetwork)
const handleSubmit = useHandleSubmit(id, editor.name.value, editor.remote.value, networks, history, dispatch)

return (
<Stack tokens={{ childrenGap: 15 }}>
<h1>{t('settings.network.edit-network.title')}</h1>
<Stack tokens={{ childrenGap: 15 }}>
{inputs.map((inputProps, idx) => (
{inputs.map(inputProps => (
<Stack.Item key={inputProps.label}>
<TextField
{...inputProps}
key={inputProps.label}
required
validateOnLoad={false}
onNotifyValidationResult={(msg: any) => {
const errs = [...errors]
errs.splice(idx, 1, msg !== '')
setErrors(errs)
}}
/>
<TextField {...inputProps} key={inputProps.label} required validateOnLoad={false} />
</Stack.Item>
))}
</Stack>
<Stack horizontal horizontalAlign="end" tokens={{ childrenGap: 10 }}>
<DefaultButton onClick={goBack} text={t('common.cancel')} />
<PrimaryButton disabled={errors.includes(true) || notModified} onClick={handleSubmit} text={t('common.save')} />
<PrimaryButton disabled={hasError || notModified} onClick={handleSubmit} text={t('common.save')} />
</Stack>
</Stack>
)
Expand Down
9 changes: 6 additions & 3 deletions packages/neuron-ui/src/components/Overview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { updateTransactionList, addPopup } from 'states/stateProvider/actionCrea
import { showTransactionDetails, showErrorMessage } from 'services/remote'

import { localNumberFormatter, shannonToCKBFormatter, uniformTimeFormatter as timeFormatter } from 'utils/formatters'
import { PAGE_SIZE, Routes, CONFIRMATION_THRESHOLD } from 'utils/const'
import { PAGE_SIZE, Routes, CONFIRMATION_THRESHOLD, ErrorCode } from 'utils/const'
import { backToTop } from 'utils/animations'

const TITLE_FONT_SIZE = 'xxLarge'
Expand Down Expand Up @@ -284,7 +284,10 @@ const Overview = ({
hideMinerInfo()
addPopup('lock-arg-copied')(dispatch)
} else {
showErrorMessage(t('messages.error'), t('messages.can-not-find-the-default-address'))
showErrorMessage(
t(`messages.error`),
t(`messages.codes.${ErrorCode.FieldNotFound}`, { fieldName: `default-address` })
)
}
}, [defaultAddress, t, hideMinerInfo, dispatch])

Expand Down Expand Up @@ -409,7 +412,7 @@ const Overview = ({
</Stack>
) : (
<MessageBar messageBarType={MessageBarType.error}>
{t('messages.can-not-find-the-default-address')}
{t(`messages.codes.${ErrorCode.FieldNotFound}`, { fieldName: `default-address` })}
</MessageBar>
)}
</Stack>
Expand Down
Loading