diff --git a/packages/neuron-ui/src/components/ImportKeystore/index.tsx b/packages/neuron-ui/src/components/ImportKeystore/index.tsx index 8361bb9ff4..bf094012f6 100644 --- a/packages/neuron-ui/src/components/ImportKeystore/index.tsx +++ b/packages/neuron-ui/src/components/ImportKeystore/index.tsx @@ -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: '', } @@ -25,7 +32,7 @@ const ImportKeystore = (props: React.PropsWithoutRef { - if (fields.name === '') { + if (fields.name === undefined) { const name = generateWalletName(wallets, wallets.length + 1, t) setFields({ ...fields, @@ -60,7 +67,7 @@ const ImportKeystore = (props: React.PropsWithoutRef { importWalletWithKeystore({ - name: fields.name, + name: fields.name || '', keystorePath: fields.path, password: fields.password, })(dispatch, history) @@ -70,6 +77,12 @@ const ImportKeystore = (props: React.PropsWithoutRef {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 ( { 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 '' }} diff --git a/packages/neuron-ui/src/components/NetworkEditor/hooks.ts b/packages/neuron-ui/src/components/NetworkEditor/hooks.ts index f65f54ee02..172926852c 100644 --- a/packages/neuron-ui/src/components/NetworkEditor/hooks.ts +++ b/packages/neuron-ui/src/components/NetworkEditor/hooks.ts @@ -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', @@ -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', + }, }) } } @@ -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 '' }, @@ -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 '' }, @@ -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 = ( @@ -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 | 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!, diff --git a/packages/neuron-ui/src/components/NetworkEditor/index.tsx b/packages/neuron-ui/src/components/NetworkEditor/index.tsx index d7b0db0889..6b84cead1d 100644 --- a/packages/neuron-ui/src/components/NetworkEditor/index.tsx +++ b/packages/neuron-ui/src/components/NetworkEditor/index.tsx @@ -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 (

{t('settings.network.edit-network.title')}

- {inputs.map((inputProps, idx) => ( + {inputs.map(inputProps => ( - { - const errs = [...errors] - errs.splice(idx, 1, msg !== '') - setErrors(errs) - }} - /> + ))} - +
) diff --git a/packages/neuron-ui/src/components/Overview/index.tsx b/packages/neuron-ui/src/components/Overview/index.tsx index f004c09610..cc7eca0a0d 100644 --- a/packages/neuron-ui/src/components/Overview/index.tsx +++ b/packages/neuron-ui/src/components/Overview/index.tsx @@ -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' @@ -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]) @@ -409,7 +412,7 @@ const Overview = ({
) : ( - {t('messages.can-not-find-the-default-address')} + {t(`messages.codes.${ErrorCode.FieldNotFound}`, { fieldName: `default-address` })} )} diff --git a/packages/neuron-ui/src/components/Send/hooks.ts b/packages/neuron-ui/src/components/Send/hooks.ts index 0d14c1955e..6bcb392f5a 100644 --- a/packages/neuron-ui/src/components/Send/hooks.ts +++ b/packages/neuron-ui/src/components/Send/hooks.ts @@ -1,68 +1,16 @@ -import React, { useCallback, useEffect } from 'react' -import { IDropdownOption } from 'office-ui-fabric-react' +import React, { useState, useCallback, useEffect, useMemo } from 'react' import { AppActions, StateDispatch } from 'states/stateProvider/reducer' import { calculateCycles } from 'services/remote/wallets' -import { Message, MAX_DECIMAL_DIGITS } from 'utils/const' -import { verifyAddress, verifyAmountRange } from 'utils/validators' -import { outputsToTotalCapacity } from 'utils/formatters' +import { outputsToTotalCapacity, priceToFee } from 'utils/formatters' +import { verifyAddress, verifyAmount, verifyAmountRange, verifyTransactionOutputs } from 'utils/validators' +import { ErrorCode } from 'utils/const' +import { MAX_DECIMAL_DIGITS } from '../../utils/const' import { TransactionOutput } from '.' let cyclesTimer: ReturnType -const validateTransactionParams = ({ items, dispatch }: { items: TransactionOutput[]; dispatch?: StateDispatch }) => { - const errorAction = { - type: AppActions.AddNotification, - payload: { - type: 'warning', - timestamp: Date.now(), - content: Message.AtLeastOneAddressNeeded, - meta: {}, - }, - } - if (!items.length || !items[0].address) { - if (dispatch) { - dispatch(errorAction) - } - return false - } - const invalid = items.some( - (item): boolean => { - const isAddressValid = verifyAddress(item.address) - if (typeof isAddressValid === 'string') { - errorAction.payload.content = Message.InvalidAddress - errorAction.payload.meta = { address: item.address } - return true - } - if (Number.isNaN(+item.amount) || +item.amount < 0) { - errorAction.payload.content = Message.InvalidAmount - errorAction.payload.meta = { amount: item.amount } - return true - } - const [, decimal = ''] = item.amount.split('.') - if (decimal.length > MAX_DECIMAL_DIGITS) { - errorAction.payload.content = Message.DecimalExceed - errorAction.payload.meta = { amount: item.amount } - return true - } - if (!verifyAmountRange(item.amount)) { - errorAction.payload.content = Message.AmountTooSmall - errorAction.payload.meta = { amount: item.amount } - return true - } - return false - } - ) - if (invalid) { - if (dispatch) { - dispatch(errorAction) - } - return false - } - return true -} - const useUpdateTransactionOutput = (dispatch: StateDispatch) => useCallback( (field: string) => (idx: number) => (value: string) => { @@ -71,7 +19,7 @@ const useUpdateTransactionOutput = (dispatch: StateDispatch) => payload: { idx, item: { - [field]: value.trim(), + [field]: value.replace(/\s/, ''), }, }, }) @@ -98,14 +46,23 @@ const useRemoveTransactionOutput = (dispatch: StateDispatch) => [dispatch] ) -const useOnTransactionChange = (walletID: string, items: TransactionOutput[], dispatch: StateDispatch) => { +const useOnTransactionChange = ( + walletID: string, + items: TransactionOutput[], + dispatch: StateDispatch, + setIsTransactionValid: Function, + setTotalAmount: Function +) => { useEffect(() => { clearTimeout(cyclesTimer) cyclesTimer = setTimeout(() => { - if (validateTransactionParams({ items })) { + if (verifyTransactionOutputs(items)) { + setIsTransactionValid(true) + const totalAmount = outputsToTotalCapacity(items) + setTotalAmount(totalAmount) calculateCycles({ walletID, - capacities: outputsToTotalCapacity(items), + capacities: totalAmount, }) .then(response => { if (response.status) { @@ -127,19 +84,20 @@ const useOnTransactionChange = (walletID: string, items: TransactionOutput[], di }) }) } else { + setIsTransactionValid(false) dispatch({ type: AppActions.UpdateSendCycles, payload: '0', }) } }, 300) - }, [walletID, items, dispatch]) + }, [walletID, items, dispatch, setIsTransactionValid, setTotalAmount]) } const useOnSubmit = (items: TransactionOutput[], dispatch: StateDispatch) => useCallback( (walletID: string = '') => () => { - if (validateTransactionParams({ items, dispatch })) { + if (verifyTransactionOutputs(items)) { dispatch({ type: AppActions.UpdateTransactionID, payload: null, @@ -164,7 +122,7 @@ const useOnItemChange = (updateTransactionOutput: Function) => ) => { if (undefined !== value) { if (field === 'amount') { - if (Number.isNaN(+value) || /[^\d.]/.test(value)) { + if (Number.isNaN(+value) || /[^\d.]/.test(value) || +value < 0) { return } updateTransactionOutput(field)(idx)(value) @@ -176,16 +134,6 @@ const useOnItemChange = (updateTransactionOutput: Function) => [updateTransactionOutput] ) -const useCapacityUnitChange = (updateTransactionOutput: Function) => - useCallback( - (idx: number = -1) => (_e: React.FormEvent, option?: IDropdownOption) => { - if (option) { - updateTransactionOutput('unit')(idx)(option.key) - } - }, - [updateTransactionOutput] - ) - const useUpdateTransactionPrice = (dispatch: StateDispatch) => useCallback( (_e: React.FormEvent, value?: string) => { @@ -223,40 +171,79 @@ const clear = (dispatch: StateDispatch) => { const useClear = (dispatch: StateDispatch) => useCallback(() => clear(dispatch), [dispatch]) export const useInitialize = ( - address: string, items: TransactionOutput[], + price: string, + cycles: string, dispatch: React.Dispatch, - history: any + t: any ) => { + const fee = useMemo(() => priceToFee(price, cycles), [price, cycles]) // in shannon + const [isTransactionValid, setIsTransactionValid] = useState(false) + const [totalAmount, setTotalAmount] = useState('0') + const updateTransactionOutput = useUpdateTransactionOutput(dispatch) const onItemChange = useOnItemChange(updateTransactionOutput) - const onCapacityUnitChange = useCapacityUnitChange(updateTransactionOutput) - const onSubmit = useOnSubmit(items, dispatch) const addTransactionOutput = useAddTransactionOutput(dispatch) const removeTransactionOutput = useRemoveTransactionOutput(dispatch) const updateTransactionPrice = useUpdateTransactionPrice(dispatch) const onDescriptionChange = useSendDescriptionChange(dispatch) + const onSubmit = useOnSubmit(items, dispatch) const onClear = useClear(dispatch) - useEffect(() => { - if (address) { - updateTransactionOutput('address')(0)(address) - } - return () => { - clear(dispatch) - } - }, [address, dispatch, history, updateTransactionOutput]) + const onGetAddressErrorMessage = useCallback( + (addr: string) => { + if (addr === '') { + return t(`messages.codes.${ErrorCode.AddressIsEmpty}`) + } + if (!verifyAddress(addr)) { + return t(`messages.codes.${ErrorCode.FieldInvalid}`, { + fieldName: 'address', + fieldValue: addr, + }) + } + return '' + }, + [t] + ) + + const onGetAmountErrorMessage = useCallback( + (text: string) => { + const amount = text || '0' + + const msg = verifyAmount(amount) + if (typeof msg === 'object') { + return t(`messages.codes.${msg.code}`, { + fieldName: 'amount', + fieldValue: amount, + length: MAX_DECIMAL_DIGITS, + }) + } + if (!verifyAmountRange(amount)) { + return t(`messages.codes.${ErrorCode.AmountTooSmall}`, { + amount, + }) + } + + return undefined + }, + [t] + ) return { + fee, + totalAmount, + setTotalAmount, + isTransactionValid, + setIsTransactionValid, useOnTransactionChange, - updateTransactionOutput, onItemChange, - onCapacityUnitChange, - onSubmit, addTransactionOutput, removeTransactionOutput, updateTransactionPrice, onDescriptionChange, + onGetAddressErrorMessage, + onGetAmountErrorMessage, + onSubmit, onClear, } } diff --git a/packages/neuron-ui/src/components/Send/index.tsx b/packages/neuron-ui/src/components/Send/index.tsx index 6f77bee3ee..12e4fc5f9f 100644 --- a/packages/neuron-ui/src/components/Send/index.tsx +++ b/packages/neuron-ui/src/components/Send/index.tsx @@ -20,9 +20,10 @@ import QRScanner from 'widgets/QRScanner' import { StateWithDispatch } from 'states/stateProvider/reducer' import appState from 'states/initStates/app' -import { PlaceHolders, CapacityUnit } from 'utils/const' -import { shannonToCKBFormatter, priceToFee } from 'utils/formatters' +import { PlaceHolders, CapacityUnit, ErrorCode } from 'utils/const' +import { shannonToCKBFormatter } from 'utils/formatters' +import { verifyTotalAmount } from 'utils/validators' import { useInitialize } from './hooks' export interface TransactionOutput { @@ -38,31 +39,30 @@ const Send = ({ }, wallet: { id: walletID = '', balance = '' }, dispatch, - history, - match: { - params: { address = '' }, - }, }: React.PropsWithoutRef>) => { const { t } = useTranslation() const { + fee, + totalAmount, + setTotalAmount, + isTransactionValid, + setIsTransactionValid, useOnTransactionChange, - updateTransactionOutput, onItemChange, onSubmit, addTransactionOutput, removeTransactionOutput, updateTransactionPrice, onDescriptionChange, + onGetAddressErrorMessage, + onGetAmountErrorMessage, onClear, - } = useInitialize(address, send.outputs, dispatch, history) - useOnTransactionChange(walletID, send.outputs, dispatch) + } = useInitialize(send.outputs, send.price, send.cycles, dispatch, t) + useOnTransactionChange(walletID, send.outputs, dispatch, setIsTransactionValid, setTotalAmount) const leftStackWidth = '70%' const labelWidth = '140px' - const actionSpacer = ( - - - - ) + + const isAffordable = verifyTotalAmount(totalAmount, fee, balance) return ( @@ -78,7 +78,7 @@ const Send = ({ @@ -91,15 +91,17 @@ const Send = ({ value={item.address || ''} onChange={onItemChange('address', idx)} required + validateOnLoad={false} + onGetErrorMessage={onGetAddressErrorMessage} /> - + updateTransactionOutput('address')(idx)(data)} + onConfirm={(data: string) => onItemChange('address', idx)(undefined as any, data)} /> - + @@ -116,7 +118,7 @@ const Send = ({ @@ -130,6 +132,8 @@ const Send = ({ onChange={onItemChange('amount', idx)} disabled={sending} required + validateOnLoad={false} + onGetErrorMessage={onGetAmountErrorMessage} /> @@ -155,20 +159,53 @@ const Send = ({ /> - + + 1 || !isAffordable ? 'flex' : 'none', + }, + }} + tokens={{ childrenGap: 20 }} + > + + + + + + + - + - {actionSpacer} ) : ( - + )} diff --git a/packages/neuron-ui/src/components/Transaction/index.tsx b/packages/neuron-ui/src/components/Transaction/index.tsx index 1655afa835..ecd72826d1 100644 --- a/packages/neuron-ui/src/components/Transaction/index.tsx +++ b/packages/neuron-ui/src/components/Transaction/index.tsx @@ -2,11 +2,12 @@ import React, { useEffect, useState, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Stack, DetailsList, Text, CheckboxVisibility, IColumn } from 'office-ui-fabric-react' import { currentWallet as currentWalletCache } from 'utils/localCache' -import { getTransaction } from 'services/remote' +import { getTransaction, showErrorMessage } from 'services/remote' import { transactionState } from 'states/initStates/chain' import { localNumberFormatter, uniformTimeFormatter, shannonToCKBFormatter } from 'utils/formatters' +import { ErrorCode } from 'utils/const' const MIN_CELL_WIDTH = 70 @@ -103,7 +104,11 @@ const Transaction = () => { if (res.status) { setTransaction(res.result) } else { - throw new Error(res.message.title) + showErrorMessage( + t(`messages.error`), + t(`messages.codes.${ErrorCode.FieldNotFound}`, { fieldName: 'transaction' }) + ) + window.close() } }) .catch((err: Error) => { @@ -113,7 +118,7 @@ const Transaction = () => { }) }) } - }, []) + }, [t]) useEffect(() => { window.addEventListener('storage', (e: StorageEvent) => { @@ -147,7 +152,7 @@ const Transaction = () => { if (error.code) { return ( - {error.message || t('messages.transaction-not-found')} + {error.message || t(`messages.codes.${ErrorCode.FieldNotFound}`, { fieldName: 'transaction' })} ) } diff --git a/packages/neuron-ui/src/components/WalletEditor/hooks.ts b/packages/neuron-ui/src/components/WalletEditor/hooks.ts index 38190028cb..8dd4bce01d 100644 --- a/packages/neuron-ui/src/components/WalletEditor/hooks.ts +++ b/packages/neuron-ui/src/components/WalletEditor/hooks.ts @@ -1,6 +1,7 @@ import { useState, useMemo, useCallback } from 'react' import { updateWalletProperty } from 'states/stateProvider/actionCreators' import { StateDispatch } from 'states/stateProvider/reducer' +import { ErrorCode, MAX_WALLET_NAME_LENGTH } from 'utils/const' import i18n from 'utils/i18n' export const useWalletEditor = () => { @@ -28,7 +29,7 @@ export const useInputs = ({ name }: ReturnType) => { ...name, label: i18n.t('settings.wallet-manager.edit-wallet.wallet-name'), placeholder: i18n.t('settings.wallet-manager.edit-wallet.wallet-name'), - maxLength: 20, + maxLength: MAX_WALLET_NAME_LENGTH, }, ], [name] @@ -44,15 +45,21 @@ export const useOnConfirm = (name: string = '', id: string = '', history: any, d }, [name, id, history, dispatch]) } -export const useAreParamsValid = (name: string) => { +export const useHint = (name: string, usedNames: string[], t: Function): string | null => { return useMemo(() => { - return !(name === '') - }, [name]) + if (name === '') { + return t(`messages.codes.${ErrorCode.FieldRequired}`, { fieldName: 'name' }) + } + if (usedNames.includes(name)) { + return t(`messages.codes.${ErrorCode.FieldUsed}`, { fieldName: 'name', fieldValue: name }) + } + return null + }, [name, usedNames, t]) } export default { useWalletEditor, useInputs, useOnConfirm, - useAreParamsValid, + useHint, } diff --git a/packages/neuron-ui/src/components/WalletEditor/index.tsx b/packages/neuron-ui/src/components/WalletEditor/index.tsx index ecc6844f07..09c71077c1 100644 --- a/packages/neuron-ui/src/components/WalletEditor/index.tsx +++ b/packages/neuron-ui/src/components/WalletEditor/index.tsx @@ -5,16 +5,16 @@ import { Stack, TextField, PrimaryButton, DefaultButton } from 'office-ui-fabric import { StateWithDispatch } from 'states/stateProvider/reducer' -import { Routes } from 'utils/const' +import { Routes, ErrorCode } from 'utils/const' import { useGoBack } from 'utils/hooks' -import { useAreParamsValid, useOnConfirm, useInputs, useWalletEditor } from './hooks' +import { useHint, useOnConfirm, useInputs, useWalletEditor } from './hooks' const WalletNotFound = () => { const [t] = useTranslation() return (
-

{t('messages.wallet-is-not-found')}

+

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

{`${t('navbar.settings')}-${t('settings.setting-tabs.wallets')}`} @@ -33,6 +33,7 @@ const WalletEditor = ({ const [t] = useTranslation() const wallet = useMemo(() => wallets.find(w => w.id === id), [id, wallets]) || { id: '', name: '' } + const usedNames = wallets.map(w => w.name).filter(n => n !== wallet.name) const editor = useWalletEditor() const { initialize } = editor @@ -42,7 +43,7 @@ const WalletEditor = ({ }, [id, initialize, wallet.name]) const inputs = useInputs(editor) - const areParamsValid = useAreParamsValid(editor.name.value) + const hint = useHint(editor.name.value, usedNames, t) const onConfirm = useOnConfirm(editor.name.value, wallet.id, history, dispatch) const goBack = useGoBack(history) @@ -56,13 +57,17 @@ const WalletEditor = ({ {inputs.map(inputProps => ( - + ))} - + ) diff --git a/packages/neuron-ui/src/components/WalletWizard/index.tsx b/packages/neuron-ui/src/components/WalletWizard/index.tsx index e8119349b5..4de577f8de 100644 --- a/packages/neuron-ui/src/components/WalletWizard/index.tsx +++ b/packages/neuron-ui/src/components/WalletWizard/index.tsx @@ -16,7 +16,7 @@ import withWizard, { WizardElementProps, WithWizardState } from 'components/with import { generateMnemonic, validateMnemonic, showErrorMessage } from 'services/remote' import { createWalletWithMnemonic, importWalletWithMnemonic } from 'states/stateProvider/actionCreators' -import { Routes, MnemonicAction } from 'utils/const' +import { Routes, MnemonicAction, ErrorCode, MAX_WALLET_NAME_LENGTH, MAX_PASSWORD_LENGTH } from 'utils/const' import { buttonGrommetIconStyles } from 'utils/icons' import { verifyPasswordComplexity } from 'utils/validators' import generateWalletName from 'utils/generateWalletName' @@ -36,15 +36,29 @@ const initState: WithWizardState = { } const submissionInputs = [ - { label: 'name', key: 'name', type: 'text', hint: 'wizard.set-wallet-name', autoFocus: false }, + { + label: 'name', + key: 'name', + type: 'text', + hint: 'wizard.set-wallet-name', + autoFocus: false, + maxLength: MAX_WALLET_NAME_LENGTH, + }, { label: 'password', key: 'password', type: 'password', hint: 'wizard.set-a-strong-password-to-protect-your-wallet', autoFocus: true, + maxLength: MAX_PASSWORD_LENGTH, + }, + { + label: 'confirm-password', + key: 'confirmPassword', + type: 'password', + autoFocus: false, + maxLength: MAX_PASSWORD_LENGTH, }, - { label: 'confirm-password', key: 'confirmPassword', type: 'password', autoFocus: false }, ] const Welcome = ({ rootPath = '/wizard', wallets = [], history }: WizardElementProps<{ rootPath: string }>) => { @@ -160,7 +174,7 @@ const Mnemonic = ({ }` ) } else { - showErrorMessage(t('messages.error'), t('messages.invalid-mnemonic')) + showErrorMessage(t(`messages.error`), t(`messages.codes.${ErrorCode.FieldInvalid}`, { fieldName: 'mnemonic' })) } } }, [isCreate, history, rootPath, type, imported, t, dispatch]) @@ -271,6 +285,7 @@ const Submission = ({ value={state[input.key]} onChange={onChange(input.key)} description={t(input.hint || '')} + maxLength={input.maxLength} />
))} diff --git a/packages/neuron-ui/src/containers/Notification/index.tsx b/packages/neuron-ui/src/containers/Notification/index.tsx index e7e5c83341..cce63b155e 100644 --- a/packages/neuron-ui/src/containers/Notification/index.tsx +++ b/packages/neuron-ui/src/containers/Notification/index.tsx @@ -83,7 +83,7 @@ export const NoticeContent = ({ dispatch }: React.PropsWithoutRef - {showTopAlert && notificationsInDesc.length ? ( + {showTopAlert && notification ? ( } > - {t(notification.content, notification.meta)} + {notification.code + ? t(`messages.codes.${notification.code}`, notification.meta) + : notification.content || t('messages.unknown-error')} ) : null} @@ -154,7 +156,11 @@ export const NoticeContent = ({ dispatch }: React.PropsWithoutRef - {t(n.content, n.meta)} + + {notification.code + ? t(`messages.codes.${notification.code}`, notification.meta) + : notification.content || t('messages.unknown-error')} + ) })} diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 434e14f877..54a6da2105 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -90,6 +90,7 @@ "input-password-to-confirm": "Input password to confirm", "this-transaction-will-send": "This transaction will send", "scan-to-get-address": "Scan QR code to read the address to send to", + "total-amount": "Total Amount", "description": "Description", "description-optional": "Description (optional)", "balance": "Balance", @@ -240,45 +241,49 @@ "success": "success" }, "messages": { - "at-least-one-address-needed": "At least one address needed", - "name-required": "Name is required", - "url-required": "URL is required", - "protocol-required": "Protocol is required", - "length-of-name-should-be-less-than-or-equal-to": "Length of name should be less than or equal to {{length}}", - "network-name-used": "Network name is used", - "is-unremovable": "{{target}} is unremovable", - "create-wallet-success": "You have created wallet '{{name}}' successfully", - "network-is-not-found": "Network is not found", - "failed-to-initiate,-please-reopen-Neuron": "Failed to initiate, please reopen Neuron", - "no-wallet": "No Wallet", - "wallet-imported-successfully": "{{name}} imported successfully", - "wallet-created-successfully": "{{name}} created successfully", - "wallet-updated-successfully": "{{name}} updated successfully", - "wallet-not-found": "Wallet not found", - "no-transactions": "No transactions", "error": "Error", - "invalid-mnemonic": "Invalid mnemonic words", - "camera-not-available-or-disabled": "Camera is unavailable or disabled", - "can-not-find-the-default-address": "Cannot find the default address", - "create-wallet-successfully": "Create a wallet successfully", - "import-wallet-successfully": "Import a wallet successfully", + "unknown-error": "Unknown error", "update-wallet-successfully": "Update the wallet successfully", "delete-wallet-successfully": "Delete the wallet successfully", "create-network-successfully": "Create a network successfully", "update-network-successfully": "Update the network successfully", - "delete-network-successfully": "Delete the network successfully", "addr-copied": "Address has been copied to the clipboard", "qrcode-copied": "QR Code has been copied to the clipboard", "lock-arg-copied": "Lock Arg has been copied to the clipboard", - "transaction-not-found": "The transaction is not found", - "rpc-url-should-have-protocol": "The RPC URL should start with http(s)://", - "rpc-url-should-have-no-whitespaces": "The RPC URL should have no whitespaces", - "is-required": "{{field}} is required", - "is-used": "{{field}} is used", - "invalid-address": "{{address}} is an invalid address", - "amount-decimal-exceed": "The amount {{amount}} CKB is invalid, please enter an amount with no more than 8 decimal places", - "invalid-amount": "The amount {{amount}} CKB is invalid", - "amount-too-small": "The amount {{amount}} CKB is too small, please enter an amount no less than 61 CKB" + "fields": { + "wallet": "Wallet", + "name": "Name", + "remote": "RPC URL", + "network": "Network", + "address": "Address", + "amount": "Amount", + "transaction": "Transaction", + "default-address": "Default Address", + "mnemonic": "Mnemonic", + "keystore-path": "Keystore File", + "keystore-name": "Wallet name", + "keystore-password": "Password" + }, + "codes": { + "-3": "", + "100": "Amount is not enough", + "101": "The amount {{amount}} CKB is too small, please enter an amount no less than 61 CKB", + "102": "$t(messages.fields.{{fieldName}}) is invalid", + "201": "$t(messages.fields.{{fieldName}}) is required", + "202": "$t(messages.fields.{{fieldName}}) {{fieldValue}} is used", + "203": "$t(messages.fields.{{fieldName}}) {{fieldValue}} is too long, it should be shorter than or equal to {{length}}", + "204": "$t(messages.fields.{{fieldName}}) {{fieldValue}} is too short, it should be longer than or equal to {{length}}", + "205": "$t(messages.fields.{{fieldName}}) {{fieldValue}} is invalid", + "206": "$t(messages.fields.{{fieldName}}) {{fieldValue}} is invalid, please enter the $t(messages.fields.{{fieldName}}) with no more than {{length}} decimal places", + "207": "$t(messages.fields.{{fieldName}}) {{fieldValue}} is invalid, it cannot be negative", + "208": "$t(messages.fields.{{fieldName}}) {{fieldValue}} is invalid, it should start with http(s)://", + "209": "$t(messages.fields.{{fieldName}}) should have no whitespaces", + "301": "$t(messages.fields.{{fieldName}}) {{fieldValue}} is irremovable", + "302": "Fail to launch the app", + "303": "$t(messages.fields.{{fieldName}}) is not found", + "304": "Camera is unavailable or disabled", + "305": "$t(messages.fields.address) cannot be empty" + } }, "sync": { "syncing": "Syncing", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 507f6ead45..c20c3828dd 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -90,6 +90,7 @@ "input-password-to-confirm": "输入密码以确认本次交易", "this-transaction-will-send": "本次交易将发送", "scan-to-get-address": "扫描二维码以获取地址", + "total-amount": "总金额", "description": "备注", "description-optional": "备注 (选填)", "balance": "余额", @@ -185,7 +186,7 @@ "edit-network": { "title": "添加或编辑网络", "rpc-url": "RPC地址", - "name": "名字" + "name": "名称" } } }, @@ -240,45 +241,49 @@ "success": "成功" }, "messages": { - "at-least-one-address-needed": "需要至少一个地址", - "name-required": "缺少名称", - "url-required": "缺少 URL", - "protocol-required": "请指定 URL 协议", - "length-of-name-should-be-less-than-or-equal-to": "名称长度应不大于 {{length}}", - "network-name-used": "节点名称已存在", - "is-unremovable": "{{target}}不可删除", - "create-wallet-success": "您已成功创建钱包 '{{name}}'", - "network-is-not-found": "未找到节点信息", - "failed-to-initiate,-please-reopen-Neuron": "初始化失败, 请新打开 Neuron", - "no-wallet": "没有钱包", - "wallet-imported-successfully": "{{name}} 导入成功", - "wallet-created-successfully": "{{name}} 创建成功", - "wallet-updated-successfully": "{{name}} 更新成功", - "wallet-not-found": "未找到钱包", - "no-transactions": "没有交易", "error": "错误", - "invalid-mnemonic": "助记词不合法", - "camera-not-available-or-disabled": "摄像头不可用或被禁用", - "can-not-find-the-default-address": "未获得默认地址", - "create-wallet-successfully": "新建钱包成功", - "import-wallet-successfully": "导入钱包成功", + "unknown-error": "未知错误", "update-wallet-successfully": "已更新钱包信息", "delete-wallet-successfully": "已删除钱包", "create-network-successfully": "新节点已添加", "update-network-successfully": "已更新节点信息", - "delete-network-successfully": "节点已删除", "addr-copied": "地址已复制到剪贴板", "qrcode-copied": "二维码已复制到剪贴板", "lock-arg-copied": "Lock Arg 已复制到剪贴板", - "transaction-not-found": "未找到交易", - "network-address-should-have-protocol": "RPC 地址应以 http(s)//: 开始", - "network-address-should-have-no-whitespaces": "RPC 地址不能包含空格", - "is-required": "{{field}}是必须的", - "is-used": "{{field}}已使用", - "invalid-address": "{{address}} 是无效的地址", - "amount-decimal-exceed": "金额 {{amount}} 是个无效的值, 其小数位应不超过 8 位", - "invalid-amount": "金额 {{amount} 是无效的数字", - "amount-too-small": "金额 {{amount}} 太小, 请输入一个不小于 61 CKB 的值" + "fields": { + "wallet": "钱包", + "name": "名称", + "remote": "RPC URL", + "network": "网络", + "address": "地址", + "amount": "金额", + "transaction": "交易", + "default-address": "默认地址", + "mnemonic": "助记词", + "keystore-path": "Keystore 文件", + "keystore-name": "钱包名称", + "keystore-password": "密码" + }, + "codes": { + "-3": "", + "100": "余额不足", + "101": "金额 {{amount}} CKB 太小, 请输入一个不小于 61 CKB 的值", + "102": "$t(messages.fields.{{fieldName}})无效", + "201": "缺少$t(messages.fields.{{fieldName}})", + "202": "$t(messages.fields.{{fieldName}}) {{fieldValue}} 已被使用", + "203": "$t(messages.fields.{{fieldName}}) {{fieldValue}} 太长, 其长度应不超过 {{length}}", + "204": "$t(messages.fields.{{fieldName}}) {{fieldValue}} 太短, 其长度应不小于 {{length}}", + "205": "$t(messages.fields.{{fieldName}}) {{fieldValue}} 无效", + "206": "$t(messages.fields.{{fieldName}}) {{fieldValue}} 无效, 其小数位应不超过 {{length}} 位", + "207": "$t(messages.fields.{{fieldName}}) {{fieldValue}} 无效, 其值不能为负数", + "208": "$t(messages.fields.{{fieldName}}) {{fieldValue}} 无效, 其值应以 http(s):// 开始", + "209": "$t(messages.fields.{{fieldName}})不能包含空格", + "301": "$t(messages.fields.{{fieldName}}) {{fieldValue}} 不可删除", + "302": "无法启动应用", + "303": "未找到$t(messages.fields.{{fieldName}})", + "304": "摄像头不可用或被禁用", + "305": "$t(messages.fields.address)不能为空" + } }, "sync": { "syncing": "同步中", diff --git a/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts b/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts index 6bef71266c..b44a7f8a6b 100644 --- a/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts +++ b/packages/neuron-ui/src/services/remote/controllerMethodWrapper.ts @@ -5,29 +5,39 @@ interface SuccessFromController { } interface FailureFromController { status: 0 - message: { - title: string - content?: string - } + message: + | string + | { + content?: string + meta?: { [key: string]: string } + } } export type ControllerResponse = SuccessFromController | FailureFromController export const RemoteNotLoadError = { status: 0 as 0, message: { - title: 'remote is not supported', + content: 'remote is not supported', }, } export const controllerNotLoaded = (controllerName: string) => ({ status: 0 as 0, message: { - title: `${controllerName} controller not loaded`, + content: `${controllerName} controller not loaded`, }, }) export const controllerMethodWrapper = (controllerName: string) => ( - callControllerMethod: (controller: any) => (params: any) => Promise<{ status: any; result: any; msg: string }> + callControllerMethod: ( + controller: any + ) => ( + params: any + ) => Promise<{ + status: any + result: any + message: { code?: number; content?: string; meta?: { [key: string]: string } } + }> ) => async (realParams?: any): Promise => { if (!window.remote) { return RemoteNotLoadError @@ -44,12 +54,14 @@ export const controllerMethodWrapper = (controllerName: string) => ( console.groupEnd() /* eslint-enable no-console */ } + if (!res) { return { status: 1, result: null, } } + if (res.status) { return { status: 1, @@ -57,16 +69,9 @@ export const controllerMethodWrapper = (controllerName: string) => ( } } - let title = '' - - if (typeof res === 'string') { - title = res - } else if (typeof res.msg === 'string') { - title = res.msg - } return { status: 0, - message: { title }, + message: typeof res.message === 'string' ? { content: res.message } : res.message || '', } } diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts index ff5aa97ce5..d7f6c4ffef 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts @@ -1,7 +1,7 @@ import { NeuronWalletActions, AppActions, StateDispatch } from 'states/stateProvider/reducer' import { getNeuronWalletState } from 'services/remote' import initStates from 'states/initStates' -import { Routes } from 'utils/const' +import { Routes, ErrorCode } from 'utils/const' import { WalletWizardPath } from 'components/WalletWizard' import { addressesToBalance } from 'utils/formatters' import { @@ -73,16 +73,10 @@ export const addPopup = (text: string) => (dispatch: StateDispatch) => { }, 8000) } -export const addNotification = ({ type, content }: { type: 'alert' | 'warning'; content: string }) => ( - dispatch: StateDispatch -) => { +export const addNotification = (message: State.Message) => (dispatch: StateDispatch) => { dispatch({ type: AppActions.AddNotification, - payload: { - type, - content, - timestamp: Date.now(), - }, + payload: message, }) } export const dismissNotification = (timestamp: number) => (dispatch: StateDispatch) => { diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/settings.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/settings.ts index 2fc4283507..6279aa3103 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/settings.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/settings.ts @@ -1,6 +1,7 @@ import { createNetwork as createRemoteNetwork, updateNetwork as updateRemoteNetwork } from 'services/remote' import { addressBook } from 'utils/localCache' import { Routes } from 'utils/const' +import { failureResToNotification } from 'utils/formatters' import { addNotification, addPopup } from './app' import { AppActions, StateDispatch } from '../reducer' @@ -15,26 +16,22 @@ export const toggleAddressBook = () => { export const createNetwork = (params: Controller.CreateNetworkParams) => (dispatch: StateDispatch, history: any) => { createRemoteNetwork(params).then(res => { - if (res.status) { - dispatch({ - type: AppActions.Ignore, - payload: null, - }) + if (res.status === 1) { addPopup('create-network-successfully')(dispatch) history.push(Routes.SettingsNetworks) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) } export const updateNetwork = (params: Controller.UpdateNetworkParams) => (dispatch: StateDispatch, history: any) => { updateRemoteNetwork(params).then(res => { - if (res.status) { + if (res.status === 1) { addPopup('update-network-successfully')(dispatch) history.push(Routes.SettingsNetworks) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) } diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts index 8aed69669c..491e0c12e4 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/transactions.ts @@ -4,6 +4,7 @@ import { getTransactionList, updateTransactionDescription as updateRemoteTransactionDescription, } from 'services/remote' +import { failureResToNotification } from 'utils/formatters' import { addNotification } from './app' export const updateTransactionList = (params: GetTransactionListParams) => (dispatch: StateDispatch) => { @@ -14,7 +15,7 @@ export const updateTransactionList = (params: GetTransactionListParams) => (disp payload: res.result, }) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) } @@ -39,7 +40,7 @@ export const updateTransactionDescription = (params: Controller.UpdateTransactio }, }) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) .finally(() => { diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts index 3b5f3c6753..23febb7f63 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/wallets.ts @@ -19,13 +19,13 @@ import { WalletWizardPath } from 'components/WalletWizard' import i18n from 'utils/i18n' import { wallets as walletsCache, currentWallet as currentWalletCache } from 'utils/localCache' import { Routes } from 'utils/const' -import { addressesToBalance } from 'utils/formatters' +import { addressesToBalance, failureResToNotification } from 'utils/formatters' import { NeuronWalletActions } from '../reducer' import { addNotification, addPopup } from './app' export const updateCurrentWallet = () => (dispatch: StateDispatch, history: any) => { getCurrentWallet().then(res => { - if (res.status) { + if (res.status === 1) { const payload = res.result || initStates.wallet if (!payload || !payload.id) { history.push(`${Routes.WalletWizard}${WalletWizardPath.Welcome}`) @@ -36,7 +36,7 @@ export const updateCurrentWallet = () => (dispatch: StateDispatch, history: any) }) currentWalletCache.save(payload) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) } @@ -46,10 +46,15 @@ export const createWalletWithMnemonic = (params: Controller.ImportMnemonicParams history: any ) => { createWallet(params).then(res => { - if (res.status) { + if (res.status === 1) { history.push(Routes.Overview) - } else { - showErrorMessage(i18n.t('messages.error'), i18n.t(res.message.title)) + } else if (res.status > 0) { + showErrorMessage(i18n.t(`messages.error`), i18n.t(`messages.codes.${res.status}`)) + } else if (res.message) { + const msg = typeof res.message === 'string' ? res.message : res.message.content || '' + if (msg) { + showErrorMessage(i18n.t(`messages.error`), msg) + } } }) } @@ -59,10 +64,15 @@ export const importWalletWithMnemonic = (params: Controller.ImportMnemonicParams history: any ) => { importMnemonic(params).then(res => { - if (res.status) { + if (res.status === 1) { history.push(Routes.Overview) - } else { - showErrorMessage(i18n.t('messages.error'), i18n.t(res.message.title)) + } else if (res.status > 0) { + showErrorMessage(i18n.t(`messages.error`), i18n.t(`messages.codes.${res.status}`)) + } else if (res.message) { + const msg = typeof res.message === 'string' ? res.message : res.message.content || '' + if (msg) { + showErrorMessage(i18n.t(`messages.error`), msg) + } } }) } @@ -72,17 +82,22 @@ export const importWalletWithKeystore = (params: Controller.ImportKeystoreParams history: any ) => { importKeystore(params).then(res => { - if (res.status) { + if (res.status === 1) { history.push(Routes.Overview) - } else { - showErrorMessage(i18n.t('messages.error'), i18n.t(res.message.title)) + } else if (res.status > 0) { + showErrorMessage(i18n.t(`messages.error`), i18n.t(`messages.codes.${res.status}`)) + } else if (res.message) { + const msg = typeof res.message === 'string' ? res.message : res.message.content || '' + if (msg) { + showErrorMessage(i18n.t(`messages.error`), msg) + } } }) } export const updateWalletList = () => (dispatch: StateDispatch, history: any) => { getWalletList().then(res => { - if (res.status) { + if (res.status === 1) { const payload = res.result || [] if (!payload.length) { history.push(`${Routes.WalletWizard}${WalletWizardPath.Welcome}`) @@ -93,7 +108,7 @@ export const updateWalletList = () => (dispatch: StateDispatch, history: any) => }) walletsCache.save(payload) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) } @@ -109,7 +124,7 @@ export const updateWalletProperty = (params: Controller.UpdateWalletParams) => ( history.push(Routes.SettingsWallets) } } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) } @@ -121,7 +136,7 @@ export const setCurrentWallet = (id: string) => (dispatch: StateDispatch) => { payload: null, }) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) } @@ -135,17 +150,29 @@ export const sendTransaction = (params: Controller.SendTransaction) => (dispatch }) sendCapacity(params) .then(res => { - if (res.status) { + if (res.status === 1) { history.push(Routes.History) } else { // TODO: the pretreatment is unnecessary once the error code is implemented - addNotification({ type: 'alert', content: res.message.title.replace(/(\b"|"\b)/g, '') })(dispatch) + addNotification({ + type: 'alert', + timestamp: +new Date(), + code: res.status, + content: (typeof res.message === 'string' ? res.message : res.message.content || '').replace( + /(\b"|"\b)/g, + '' + ), + meta: typeof res.message === 'string' ? undefined : res.message.meta, + })(dispatch) } dispatch({ type: AppActions.DismissPasswordRequest, payload: null, }) }) + .catch(err => { + console.warn(err) + }) .finally(() => { dispatch({ type: AppActions.UpdateLoadings, @@ -168,7 +195,7 @@ export const updateAddressListAndBalance = (params: Controller.GetAddressesByWal payload: { addresses, balance }, }) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) } @@ -193,7 +220,7 @@ export const updateAddressDescription = (params: Controller.UpdateAddressDescrip }, }) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) .finally(() => { @@ -215,7 +242,7 @@ export const deleteWallet = (params: Controller.DeleteWalletParams) => (dispatch if (res.status) { addPopup('delete-wallet-successfully')(dispatch) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) } @@ -232,7 +259,7 @@ export const backupWallet = (params: Controller.BackupWalletParams) => (dispatch payload: null, }) } else { - addNotification({ type: 'alert', content: res.message.title })(dispatch) + addNotification(failureResToNotification(res))(dispatch) } }) } diff --git a/packages/neuron-ui/src/tests/formatters.test.ts b/packages/neuron-ui/src/tests/formatters/formatters.test.ts similarity index 100% rename from packages/neuron-ui/src/tests/formatters.test.ts rename to packages/neuron-ui/src/tests/formatters/formatters.test.ts diff --git a/packages/neuron-ui/src/tests/validators/verifyAmount/fixtures.ts b/packages/neuron-ui/src/tests/validators/verifyAmount/fixtures.ts new file mode 100644 index 0000000000..9dcd4ac08c --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyAmount/fixtures.ts @@ -0,0 +1,37 @@ +import { ErrorCode } from 'utils/const' + +const fixtures: { + [title: string]: { + amount: string + expected: + | boolean + | { + code: ErrorCode + } + } +} = { + 'Valid Amount of 100000000000000000000000000000000000000000000000.00000001': { + amount: '100000000000000000000000000000000000000000000000.00000001', + expected: true, + }, + 'Amount which is not a number should fail': { + amount: 'not a number', + expected: { + code: ErrorCode.FieldInvalid, + }, + }, + 'Negative amount should fail': { + amount: '-1', + expected: { + code: ErrorCode.NotNegative, + }, + }, + 'Amount has more than 8 decimal places should fail': { + amount: '0.000000001', + expected: { + code: ErrorCode.DecimalExceed, + }, + }, +} + +export default fixtures diff --git a/packages/neuron-ui/src/tests/validators/verifyAmount/index.test.ts b/packages/neuron-ui/src/tests/validators/verifyAmount/index.test.ts new file mode 100644 index 0000000000..9efb3336d9 --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyAmount/index.test.ts @@ -0,0 +1,11 @@ +import { verifyAmount } from 'utils/validators' +import { ErrorCode } from '../../../utils/const' +import fixtures from './fixtures' + +const fixtureTable = Object.entries(fixtures).map(([title, { amount, expected }]) => [title, amount, expected]) + +describe(`Verify amount`, () => { + test.each(fixtureTable)(`%s`, (_title: string, amount: string, expected: boolean | { code: ErrorCode }) => { + expect(verifyAmount(amount)).toEqual(expected) + }) +}) diff --git a/packages/neuron-ui/src/tests/validators/verifyAmountRange/fixtures.ts b/packages/neuron-ui/src/tests/validators/verifyAmountRange/fixtures.ts new file mode 100644 index 0000000000..b7d6801a72 --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyAmountRange/fixtures.ts @@ -0,0 +1,22 @@ +export default { + 'Amount of 0 should fail': { + amount: '0', + expected: false, + }, + 'Amount of 60.99999999 should fail': { + amount: '60.99999999', + expected: false, + }, + 'Amount equals to 61 should pass': { + amount: '61', + expected: true, + }, + 'Amount close to 61.00000001 should pass': { + amount: '61.00000001', + expected: true, + }, + 'Amount far away from 61 should pass': { + amount: '6100000001', + expected: true, + }, +} diff --git a/packages/neuron-ui/src/tests/validators/verifyAmountRange/index.test.ts b/packages/neuron-ui/src/tests/validators/verifyAmountRange/index.test.ts new file mode 100644 index 0000000000..1138435193 --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyAmountRange/index.test.ts @@ -0,0 +1,10 @@ +import { verifyAmountRange } from 'utils/validators' +import fixtures from './fixtures' + +const fixtureTable = Object.entries(fixtures).map(([title, { amount, expected }]) => [title, amount, expected]) + +describe('Verify amount range', () => { + test.each(fixtureTable)(`%s`, (_title: string, amount: string, expected: boolean) => { + expect(verifyAmountRange(amount)).toBe(expected) + }) +}) diff --git a/packages/neuron-ui/src/tests/validators/verifyNetworkName/fixtures.ts b/packages/neuron-ui/src/tests/validators/verifyNetworkName/fixtures.ts new file mode 100644 index 0000000000..ea59d674e6 --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyNetworkName/fixtures.ts @@ -0,0 +1,43 @@ +import { ErrorCode } from 'utils/const' + +const fixtures: { + [title: string]: { + name: string + usedNames: string[] + expected: boolean | { code: ErrorCode } + } +} = { + 'Valid name': { + name: 'Testnet', + usedNames: ['Local'], + expected: true, + }, + 'Empty name should fail': { + name: '', + usedNames: ['Local'], + expected: { + code: ErrorCode.FieldRequired, + }, + }, + 'Name consists of 28 charcters': { + name: '1234567890123456789012345678', + usedNames: ['Local'], + expected: true, + }, + 'Name consists of more than 28 characters should fail': { + name: '12345678901234567890123456789', + usedNames: ['Local'], + expected: { + code: ErrorCode.FieldTooLong, + }, + }, + 'Name which is used should fail': { + name: 'Testnet', + usedNames: ['Testnet', 'Local'], + expected: { + code: ErrorCode.FieldUsed, + }, + }, +} + +export default fixtures diff --git a/packages/neuron-ui/src/tests/validators/verifyNetworkName/index.test.ts b/packages/neuron-ui/src/tests/validators/verifyNetworkName/index.test.ts new file mode 100644 index 0000000000..43b350166a --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyNetworkName/index.test.ts @@ -0,0 +1,19 @@ +import { verifyNetworkName } from 'utils/validators' +import { ErrorCode } from 'utils/const' +import fixtures from './fixtures' + +const fixtureTable = Object.entries(fixtures).map(([title, { name, usedNames, expected }]) => [ + title, + name, + usedNames, + expected, +]) + +describe(`Verify network name`, () => { + test.each(fixtureTable)( + `%s`, + (_title: string, name: string, usedNames: string[], expected: boolean | { code: ErrorCode }) => { + expect(verifyNetworkName(name, usedNames)).toEqual(expected) + } + ) +}) diff --git a/packages/neuron-ui/src/tests/validators/verifyTotalAmount/fixtures.ts b/packages/neuron-ui/src/tests/validators/verifyTotalAmount/fixtures.ts new file mode 100644 index 0000000000..d44e0c1b75 --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyTotalAmount/fixtures.ts @@ -0,0 +1,35 @@ +const fixtures: { + [title: string]: { + totalAmount: string + fee: string + balance: string + expected: boolean + } +} = { + 'Valid total amount': { + totalAmount: '10000000000000000000000', + fee: '1', + balance: '10000000000000000000001', + expected: true, + }, + 'Too large total amount should fail': { + totalAmount: '10000000000000000000001', + fee: '0', + balance: '10000000000000000000000', + expected: false, + }, + 'Too large fee should fail': { + totalAmount: '10000000000000000000000', + fee: '1', + balance: '10000000000000000000000', + expected: false, + }, + 'Negative balance should fail': { + totalAmount: '10000000000000000000000', + fee: '10000000000', + balance: '-10000000000010000000000', + expected: false, + }, +} + +export default fixtures diff --git a/packages/neuron-ui/src/tests/validators/verifyTotalAmount/index.test.ts b/packages/neuron-ui/src/tests/validators/verifyTotalAmount/index.test.ts new file mode 100644 index 0000000000..3b18e5a712 --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyTotalAmount/index.test.ts @@ -0,0 +1,19 @@ +import { verifyTotalAmount } from 'utils/validators' +import fixtures from './fixtures' + +const fixtureTable = Object.entries(fixtures).map(([title, { totalAmount, fee, balance, expected }]) => [ + title, + totalAmount, + fee, + balance, + expected, +]) + +describe('Verify total amount', () => { + test.each(fixtureTable)( + `%s`, + (_title: string, totalAmount: string, fee: string, balance: string, expected: boolean) => { + expect(verifyTotalAmount(totalAmount, fee, balance)).toBe(expected) + } + ) +}) diff --git a/packages/neuron-ui/src/tests/validators/verifyTransactionOutputs/fixture.ts b/packages/neuron-ui/src/tests/validators/verifyTransactionOutputs/fixture.ts new file mode 100644 index 0000000000..91f12e4987 --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyTransactionOutputs/fixture.ts @@ -0,0 +1,63 @@ +const fixtures: { + [title: string]: { + outputs: { address: string; amount: string }[] + expected: boolean + } +} = { + 'Valid address and valid amount': { + outputs: [ + { + address: 'ckt1qyqg5w7emdntvnnk7utzqkz3kx276um0j4qs525t0y', + amount: '100', + }, + ], + expected: true, + }, + 'Empty address and valid amount should fail': { + outputs: [ + { + address: '', + amount: '100', + }, + ], + expected: false, + }, + 'Invalid address and valid amount should fail': { + outputs: [ + { + address: 'abcdefg', + amount: '100', + }, + ], + expected: false, + }, + 'Valid address and amount of invalid number should fail': { + outputs: [ + { + address: 'abcdefg', + amount: 'invalid number', + }, + ], + expected: false, + }, + 'Valid address and negative amount should fail': { + outputs: [ + { + address: 'abcdefg', + amount: '-1', + }, + ], + expected: false, + }, + 'Valid address and amount less than 61 should fail': { + outputs: [ + { + address: 'abcdefg', + amount: '60', + }, + ], + expected: false, + }, +} + +export default fixtures diff --git a/packages/neuron-ui/src/tests/validators/verifyTransactionOutputs/index.test.ts b/packages/neuron-ui/src/tests/validators/verifyTransactionOutputs/index.test.ts new file mode 100644 index 0000000000..b4f07c7b7f --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyTransactionOutputs/index.test.ts @@ -0,0 +1,10 @@ +import { verifyTransactionOutputs } from 'utils/validators' +import fixtures from './fixture' + +const fixtureTable = Object.entries(fixtures).map(([title, { outputs, expected }]) => [title, outputs, expected]) + +describe(`Verify transaction outputs`, () => { + test.each(fixtureTable)(`%s`, (_title: string, outputs: { address: string; amount: string }[], expected: boolean) => { + expect(verifyTransactionOutputs(outputs)).toBe(expected) + }) +}) diff --git a/packages/neuron-ui/src/tests/validators/verifyURL/fixtures.ts b/packages/neuron-ui/src/tests/validators/verifyURL/fixtures.ts new file mode 100644 index 0000000000..7a2b71fea9 --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyURL/fixtures.ts @@ -0,0 +1,49 @@ +import { ErrorCode } from 'utils/const' + +const fixtures: { + [title: string]: { + url: string + expected: boolean | { code: ErrorCode } + } +} = { + 'URL starts with http://': { + url: 'http://localhost', + expected: true, + }, + 'URL starts with https://': { + url: 'https://localhost', + expected: true, + }, + 'URL starts with http should fail': { + url: 'http hello', + expected: { + code: ErrorCode.ProtocolRequired, + }, + }, + 'URL starts with https should fail': { + url: 'https hello', + expected: { + code: ErrorCode.ProtocolRequired, + }, + }, + 'URL start with ws:// should fail': { + url: 'ws://localhost', + expected: { + code: ErrorCode.ProtocolRequired, + }, + }, + 'URL contains whitespaces should fail': { + url: 'http:// localhost', + expected: { + code: ErrorCode.NoWhiteSpaces, + }, + }, + 'Empty URL should fail': { + url: '', + expected: { + code: ErrorCode.FieldRequired, + }, + }, +} + +export default fixtures diff --git a/packages/neuron-ui/src/tests/validators/verifyURL/index.test.ts b/packages/neuron-ui/src/tests/validators/verifyURL/index.test.ts new file mode 100644 index 0000000000..04e325918c --- /dev/null +++ b/packages/neuron-ui/src/tests/validators/verifyURL/index.test.ts @@ -0,0 +1,11 @@ +import { verifyURL } from 'utils/validators' +import { ErrorCode } from 'utils/const' +import fixtures from './fixtures' + +const fixtureTable = Object.entries(fixtures).map(([title, { url, expected }]) => [title, url, expected]) + +describe('Verify URL', () => { + test.each(fixtureTable)(`%s`, (_title: string, url: string, expected: boolean | { code: ErrorCode }) => { + expect(verifyURL(url)).toEqual(expected) + }) +}) diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 2cfb486745..35964b6440 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -47,11 +47,13 @@ declare namespace State { amount: string unit: any } - interface Message { - type: 'success' | 'warning' | 'alert' + type MessageType = 'success' | 'warning' | 'alert' + interface Message { + type: MessageType timestamp: number - content: string - meta?: { [key: string]: string } + code?: Code + content?: string + meta?: Meta } interface Send { txID: string diff --git a/packages/neuron-ui/src/utils/const.ts b/packages/neuron-ui/src/utils/const.ts index 1dfa352b08..ad02db9781 100644 --- a/packages/neuron-ui/src/utils/const.ts +++ b/packages/neuron-ui/src/utils/const.ts @@ -1,4 +1,5 @@ export const MAX_NETWORK_NAME_LENGTH = 28 +export const MAX_WALLET_NAME_LENGTH = 20 export const ADDRESS_LENGTH = 46 export const MIN_PASSWORD_LENGTH = 8 export const MAX_PASSWORD_LENGTH = 50 @@ -49,20 +50,6 @@ export const PlaceHolders = { }, } -export enum Message { - NameRequired = 'messages.name-required', - URLRequired = 'messages.url-required', - LengthOfNameShouldBeLessThanOrEqualTo = 'messages.length-of-name-should-be-less-than-or-equal-to', - NetworkNameUsed = 'messages.network-name-used', - AtLeastOneAddressNeeded = 'messages.at-least-one-address-needed', - InvalidAddress = 'messages.invalid-address', - InvalidAmount = 'messages.invalid-amount', - DecimalExceed = 'messages.amount-decimal-exceed', - IsUnremovable = 'messages.is-unremovable', - ProtocolRequired = 'messages.protocol-required', - AmountTooSmall = 'messages.amount-too-small', -} - export enum MnemonicAction { Create = 'create', Verify = 'verify', @@ -77,3 +64,26 @@ export const FULL_SCREENS = [ `${Routes.WalletEditor}/`, `${Routes.NetworkEditor}/`, ] + +export enum ErrorCode { + // Errors from RPC + ErrorFromRPC = -3, + // Errors from neuron-wallet + AmountNotEnough = 100, + AmountTooSmall = 101, + // Parameter validation errors from neuron-ui + FieldRequired = 201, + FieldUsed = 202, + FieldTooLong = 203, + FieldTooShort = 204, + FieldInvalid = 205, + DecimalExceed = 206, + NotNegative = 207, + ProtocolRequired = 208, + NoWhiteSpaces = 209, + FieldIrremovable = 301, + FailToLaunch = 302, + FieldNotFound = 303, + CameraUnavailable = 304, + AddressIsEmpty = 305, +} diff --git a/packages/neuron-ui/src/utils/formatters.ts b/packages/neuron-ui/src/utils/formatters.ts index e178b94cd5..bc4dfaadcc 100644 --- a/packages/neuron-ui/src/utils/formatters.ts +++ b/packages/neuron-ui/src/utils/formatters.ts @@ -173,6 +173,16 @@ export const outputsToTotalCapacity = (outputs: { amount: string; unit: Capacity return totalCapacity.toString() } +export const failureResToNotification = (res: any): State.Message => { + return { + type: 'alert', + timestamp: +new Date(), + code: res.status, + content: typeof res.message !== 'string' ? res.message.content : res.message, + meta: typeof res.message !== 'string' ? res.message.meta : undefined, + } +} + export default { queryFormatter, currencyFormatter, @@ -183,4 +193,5 @@ export default { priceToFee, addressesToBalance, outputsToTotalCapacity, + failureResToNotification, } diff --git a/packages/neuron-ui/src/utils/validators.ts b/packages/neuron-ui/src/utils/validators.ts index 0f9667d913..cccd918f54 100644 --- a/packages/neuron-ui/src/utils/validators.ts +++ b/packages/neuron-ui/src/utils/validators.ts @@ -1,23 +1,44 @@ +import { MAX_NETWORK_NAME_LENGTH } from 'utils/const' +/* global BigInt */ import { ckbCore } from 'services/chain' -import { ADDRESS_LENGTH, MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, MIN_AMOUNT } from './const' +import { MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, MIN_AMOUNT, MAX_DECIMAL_DIGITS, ErrorCode } from './const' -export const verifyAddress = (address: string): boolean | string => { - // TODO: verify address, prd required +export const verifyAddress = (address: string): boolean => { try { - if (address.length !== ADDRESS_LENGTH) { - throw new Error('Address length is incorrect') - } ckbCore.utils.parseAddress(address) return true } catch (err) { - return err.message + return false } } -export const verifyAmountRange = (amount: string) => { +export const verifyAmountRange = (amount: string = '') => { return +amount >= MIN_AMOUNT } +export const verifyAmount = (amount: string = '0') => { + if (Number.isNaN(+amount)) { + return { code: ErrorCode.FieldInvalid } + } + if (+amount < 0) { + return { code: ErrorCode.NotNegative } + } + const [, decimal = ''] = amount.split('.') + if (decimal.length > MAX_DECIMAL_DIGITS) { + return { + code: ErrorCode.DecimalExceed, + } + } + return true +} + +export const verifyTotalAmount = (totalAmount: string, fee: string, balance: string) => { + if (+balance < 0) { + return false + } + return BigInt(totalAmount) + BigInt(fee) <= BigInt(balance) +} + export const verifyPasswordComplexity = (password: string) => { if (!password) { return 'password-is-empty' @@ -51,8 +72,61 @@ export const verifyPasswordComplexity = (password: string) => { return true } +export const verifyTransactionOutputs = (items: { address: string; amount: string }[] = []) => { + return !items.some(item => { + if (item.address === '' || verifyAddress(item.address) !== true) { + return true + } + if (verifyAmount(item.amount) !== true || verifyAmountRange(item.amount) !== true) { + return true + } + return false + }) +} + +export const verifyNetworkName = (name: string, usedNames: string[]) => { + if (!name) { + return { + code: ErrorCode.FieldRequired, + } + } + if (usedNames.includes(name)) { + return { + code: ErrorCode.FieldUsed, + } + } + if (name.length > MAX_NETWORK_NAME_LENGTH) { + return { + code: ErrorCode.FieldTooLong, + } + } + return true +} + +export const verifyURL = (url: string) => { + if (!url) { + return { + code: ErrorCode.FieldRequired, + } + } + if (!/^https?:\/\//.test(url)) { + return { + code: ErrorCode.ProtocolRequired, + } + } + if (/\s/.test(url)) { + return { + code: ErrorCode.NoWhiteSpaces, + } + } + return true +} + export default { verifyAddress, verifyAmountRange, + verifyTotalAmount, verifyPasswordComplexity, + verifyTransactionOutputs, + verifyNetworkName, } diff --git a/packages/neuron-ui/src/widgets/QRScanner/index.tsx b/packages/neuron-ui/src/widgets/QRScanner/index.tsx index e2b3fcc942..067963af32 100644 --- a/packages/neuron-ui/src/widgets/QRScanner/index.tsx +++ b/packages/neuron-ui/src/widgets/QRScanner/index.tsx @@ -14,6 +14,7 @@ import jsQR from 'jsqr' import { showErrorMessage } from 'services/remote' import { drawPolygon } from 'utils/canvasActions' import { verifyAddress } from 'utils/validators' +import { ErrorCode } from 'utils/const' interface QRScannerProps { title: string @@ -100,7 +101,7 @@ const QRScanner = ({ title, label, onConfirm, styles }: QRScannerProps) => { requestAnimationFrame(tick) }) .catch((err: Error) => { - showErrorMessage(t('messages.camera-not-available-or-disabled'), err.message) + showErrorMessage(t(`messages.codes.${ErrorCode.CameraUnavailable}`), err.message) setOpen(false) }) }, [video, t, onConfirm, onDismiss]) diff --git a/packages/neuron-wallet/src/controllers/wallets/index.ts b/packages/neuron-wallet/src/controllers/wallets/index.ts index 6fc3c95328..0f98884eb9 100644 --- a/packages/neuron-wallet/src/controllers/wallets/index.ts +++ b/packages/neuron-wallet/src/controllers/wallets/index.ts @@ -371,7 +371,7 @@ export default class WalletsController { } catch (err) { return { status: ResponseCode.Fail, - msg: `Error: "${err.message}"`, + message: `Error: "${err.message}"`, } } } @@ -391,7 +391,7 @@ export default class WalletsController { } catch (err) { return { status: ResponseCode.Fail, - msg: `Error: "${err.message}"`, + message: `Error: "${err.message}"`, } } } diff --git a/packages/neuron-wallet/src/decorators/errors.ts b/packages/neuron-wallet/src/decorators/errors.ts index 93df8d62b0..efaa1b3ffe 100644 --- a/packages/neuron-wallet/src/decorators/errors.ts +++ b/packages/neuron-wallet/src/decorators/errors.ts @@ -10,7 +10,7 @@ export const CatchControllerError = (_target: any, _name: string, descriptor: Pr } catch (err) { return { status: ResponseCode.Fail, - msg: err.message, + message: typeof err.message === 'string' ? { content: err.message } : err.message, } } }, diff --git a/packages/neuron-wallet/src/types/controller/index.d.ts b/packages/neuron-wallet/src/types/controller/index.d.ts index c6d318a9c0..43b39ce24a 100644 --- a/packages/neuron-wallet/src/types/controller/index.d.ts +++ b/packages/neuron-wallet/src/types/controller/index.d.ts @@ -1,7 +1,12 @@ declare module Controller { interface Response { status: number - msg?: string + message?: + | string + | { + content?: string + meta?: { [key: string]: string } + } result?: T }