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
2 changes: 1 addition & 1 deletion packages/neuron-ui/src/components/CellManagement/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export const usePassword = () => {
}

export const useHardWallet = ({ wallet, t }: { wallet: State.WalletIdentity; t: TFunction }) => {
const isWin32 = useMemo(() => {
const isWin32 = useMemo<boolean>(() => {
return getPlatform() === 'win32'
}, [])
const [error, setError] = useState<ErrorCode | string | undefined>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const useRepeatPassword = ({
password: string
t: TFunction
encryptedPassword?: string
onCancel: () => void
onCancel: (success: boolean) => void
}) => {
const dispatch = useDispatch()
const [errMsg, setErrMsg] = useState('')
Expand All @@ -89,7 +89,7 @@ export const useRepeatPassword = ({
updateLockWindowInfo(
encryptedPassword ? { password: updatedRepeatPassword } : { password: updatedRepeatPassword, locked: true }
)(dispatch)
onCancel()
onCancel(true)
}
} else {
setErrMsg('')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const LockWindowDialog = ({
encryptedPassword,
}: {
show: boolean
onCancel: () => void
onCancel: (success?: boolean) => void
encryptedPassword?: string
}) => {
const [t] = useTranslation()
Expand Down
70 changes: 61 additions & 9 deletions packages/neuron-ui/src/containers/LockWindow/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable jsx-a11y/media-has-caption */
import React, { useCallback, useEffect, useState } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { AppActions, getLockWindowInfo, useDispatch, useState as useGlobalState } from 'states'
import Spinner from 'widgets/Spinner'
import Locked from 'widgets/Icons/Locked.png'
Expand All @@ -8,10 +8,13 @@ import UnLockMp4 from 'widgets/Icons/unlock.mp4'
import SplitPasswordInput from 'widgets/SplitPasswordInput'
import { useTranslation } from 'react-i18next'
import { clsx, isSuccessResponse } from 'utils'
import { isDark, unlockWindow } from 'services/remote'
import { isDark, signMessage, unlockWindow } from 'services/remote'
import { retryUnlockWindow } from 'services/localCache'
import { MILLISECS_PER_HOUR, MILLISECS_PER_MIN, MILLISECS_PER_SEC } from 'utils/getSyncLeftTime'
import { ControllerResponse } from 'services/remote/remoteApiWrapper'
import LockWindowDialog from 'components/GeneralSetting/LockWindowDialog'
import styles from './lockWindow.module.scss'
import VerifyWallet from './verifyWallet'

const passwordLen = 4
const wrongEnterTimes = 3
Expand All @@ -24,12 +27,6 @@ const formatterLockMillisecs = (lockMillisecs: number) => {

const getWaitMillisecs = (retryTimes: number) => {
if (retryTimes % wrongEnterTimes === 0) {
if (retryTimes >= 3 * wrongEnterTimes) {
return 24 * MILLISECS_PER_HOUR
}
if (retryTimes > wrongEnterTimes) {
return 30 * MILLISECS_PER_MIN
}
return 5 * MILLISECS_PER_MIN
}
return undefined
Expand All @@ -41,7 +38,7 @@ const LockWindow = ({ children }: { children: React.ReactNode }) => {
useEffect(() => {
getLockWindowInfo(dispatch)
}, [])
const { app } = useGlobalState()
const { app, wallet } = useGlobalState()
const [password, setPassword] = useState<string[]>(new Array(passwordLen).fill(''))
const [errMsg, setErrMsg] = useState('')
const [retryUnlockInfo, setRetryUnlockInfo] = useState(retryUnlockWindow.get())
Expand Down Expand Up @@ -130,6 +127,25 @@ const LockWindow = ({ children }: { children: React.ReactNode }) => {
}
return () => clearInterval(interval)
}, [retryUnlockInfo])
const splitPasswordInputRef = useRef<{ focus: () => void } | null>(null)
const [isVerifyWalletDialogShow, setIsVerifyWalletDialogShow] = useState(false)
const [isResetPasswordDialogShow, setIsResetPasswordDialogShow] = useState(false)
const onVerifyWallet = useCallback(
async (walletPassword?: string) => {
const res: ControllerResponse = await signMessage({
walletID: wallet?.id ?? '',
message: 'verify wallet for reset lock window password',
password: walletPassword ?? '',
})
if (isSuccessResponse(res)) {
setIsVerifyWalletDialogShow(false)
setIsResetPasswordDialogShow(true)
} else {
throw new Error(typeof res.message === 'string' ? res.message : res.message.content)
}
},
[setIsResetPasswordDialogShow, setIsVerifyWalletDialogShow, wallet]
)
if (!app.lockWindowInfo) {
return (
<div className={styles.loading}>
Expand All @@ -156,11 +172,47 @@ const LockWindow = ({ children }: { children: React.ReactNode }) => {
disabled={retryUnlockInfo.retryTimes % wrongEnterTimes === 0 && !!retryUnlockInfo.lastRetryTime}
values={password}
onChange={onUpdatePassword}
ref={splitPasswordInputRef}
/>
</div>
<div className={styles.notice} data-has-err={!!errMsg}>
{errMsg || t('lock-window.enter-lock-password')}
{wallet.isWatchOnly ? null : (
<button
type="button"
onClick={() => {
setIsVerifyWalletDialogShow(true)
}}
>
{t('lock-window.forget-password')}
</button>
)}
</div>
<VerifyWallet
show={isVerifyWalletDialogShow}
wallet={wallet}
onCancel={() => {
setIsVerifyWalletDialogShow(false)
setTimeout(() => {
// wait for dialog close
splitPasswordInputRef.current?.focus()
}, 10)
}}
onConfirm={onVerifyWallet}
/>
<LockWindowDialog
show={isResetPasswordDialogShow}
onCancel={success => {
setIsResetPasswordDialogShow(false)
if (success) {
setPassword(new Array(passwordLen).fill(''))
}
setTimeout(() => {
// wait for dialog close
splitPasswordInputRef.current?.focus()
}, 10)
}}
/>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
&[data-has-err='true'] {
color: var(--error-color);
}

& > button {
border: none;
color: var(--primary-color);
background-color: transparent;
cursor: pointer;
}
}

.passwordContainer {
Expand Down Expand Up @@ -53,3 +60,12 @@
height: 88px;
}
}

.verifyWallet {
min-width: 600px;

.hardwalletErr {
justify-content: center;
margin-top: 12px;
}
}
121 changes: 121 additions & 0 deletions packages/neuron-ui/src/containers/LockWindow/verifyWallet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useCallback, useEffect, useState } from 'react'
import Dialog from 'widgets/Dialog'
import Hardware from 'widgets/Icons/Hardware.png'
import Button from 'widgets/Button'
import { useHardWallet, usePassword } from 'components/CellManagement/hooks'
import Alert from 'widgets/Alert'
import TextField from 'widgets/TextField'
import { useTranslation } from 'react-i18next'
import styles from './lockWindow.module.scss'

const VerifyWallet = ({
wallet,
show,
onCancel,
onConfirm,
}: {
wallet: State.Wallet
show: boolean
onCancel: () => void
onConfirm: (password?: string) => Promise<void>
}) => {
const [t] = useTranslation()
const [loading, setLoading] = useState(false)
const {
isReconnecting,
isNotAvailable,
reconnect,
verifyDeviceStatus,
errorMessage: hardwalletError,
setError: setHardwalletError,
} = useHardWallet({
wallet,
t,
})
const { password, error, onPasswordChange, setError, resetPassword } = usePassword()
useEffect(() => {
if (show) {
resetPassword()
}
}, [show, resetPassword])
useEffect(() => {
if (show && wallet.device) {
verifyDeviceStatus()
}
}, [show, wallet.device, verifyDeviceStatus])
const onConfirmWrapper = useCallback(() => {
setLoading(true)
onConfirm(wallet.device ? undefined : password)
.catch(err => {
if (wallet.device) {
setHardwalletError(err.message)
} else {
setError(err.message)
}
})
.finally(() => {
setLoading(false)
})
}, [wallet.device, onConfirm, setLoading, setHardwalletError, setError, password])
if (wallet.device) {
return (
<Dialog
show={show}
title={`${t(`lock-window.verify-wallet`)}-${wallet.name}`}
onCancel={onCancel}
onConfirm={onConfirmWrapper}
showFooter={false}
className={styles.verifyWallet}
>
<div>
<img src={Hardware} alt="hard-wallet" className={styles.hardWalletImg} />
</div>
<div className={styles.lockActions}>
<Button
onClick={isNotAvailable ? reconnect : onConfirmWrapper}
loading={loading || isReconnecting}
type="primary"
>
{isNotAvailable || isReconnecting
? t('hardware-verify-address.actions.reconnect')
: t('cell-manage.verify')}
</Button>
</div>
{hardwalletError ? (
<Alert status="error" className={styles.hardwalletErr}>
{hardwalletError}
</Alert>
) : null}
</Dialog>
)
}
return (
<Dialog
show={show}
title={`${t(`lock-window.verify-wallet`)}-${wallet.name}`}
onCancel={onCancel}
onConfirm={onConfirmWrapper}
showCancel={false}
className={styles.verifyWallet}
isLoading={loading}
>
<TextField
className={styles.passwordInput}
placeholder={t('cell-manage.password-placeholder')}
width="100%"
label={t('cell-manage.enter-password')}
value={password}
field="password"
type="password"
title={t('cell-manage.enter-password')}
onChange={onPasswordChange}
autoFocus
error={error}
/>
</Dialog>
)
}

VerifyWallet.displayName = 'VerifyWallet'

export default VerifyWallet
4 changes: 3 additions & 1 deletion packages/neuron-ui/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1300,8 +1300,10 @@
"lock-window": {
"neuron-is-locked": "Neuron's window has been locked",
"enter-lock-password": "Enter lock password",
"forget-password": "Forget password?",
"lock-password-error": "Lock windows password error",
"failed-times": "Failed more than {{frequency}} times, please retry after {{time}}"
"failed-times": "Failed more than {{frequency}} times, please retry after {{time}}",
"verify-wallet": "Verify"
}
}
}
4 changes: 3 additions & 1 deletion packages/neuron-ui/src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1280,8 +1280,10 @@
"lock-window": {
"neuron-is-locked": "La ventana de Neuron está bloqueada",
"enter-lock-password": "Ingresar contraseña de bloqueo de pantalla",
"forget-password": "Olvidé mi contraseña?",
"lock-password-error": "Contraseña de bloqueo de pantalla incorrecta",
"failed-times": "Fallo más de {{frequency}} veces, por favor inténtalo de nuevo después de {{time}}"
"failed-times": "Fallo más de {{frequency}} veces, por favor inténtalo de nuevo después de {{time}}",
"verify-wallet": "Verificar"
}
}
}
4 changes: 3 additions & 1 deletion packages/neuron-ui/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1290,8 +1290,10 @@
"lock-window": {
"neuron-is-locked": "a fenêtre de Neuron est verrouillée",
"enter-lock-password": "Entrer le mot de passe de verrouillage d'écran",
"forget-password": "Mot de passe oublié?",
"lock-password-error": "Mot de passe de verrouillage d'écran incorrect",
"failed-times": "Échec plus de {{frequency}} fois, veuillez réessayer après {{time}}"
"failed-times": "Échec plus de {{frequency}} fois, veuillez réessayer après {{time}}",
"verify-wallet": "Vérifier"
}
}
}
4 changes: 3 additions & 1 deletion packages/neuron-ui/src/locales/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -1289,8 +1289,10 @@
"lock-window": {
"neuron-is-locked": "Neuron 窗口已鎖定",
"enter-lock-password": "輸入鎖屏密碼",
"forget-password": "忘記密碼?",
"lock-password-error": "鎖屏密碼錯誤",
"failed-times": "失敗超過 {{frequency}} 次, 請在 {{time}} 後重試"
"failed-times": "失敗超過 {{frequency}} 次, 請在 {{time}} 後重試",
"verify-wallet": "驗證"
}
}
}
4 changes: 3 additions & 1 deletion packages/neuron-ui/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1292,8 +1292,10 @@
"lock-window": {
"neuron-is-locked": "Neuron 窗口已锁定",
"enter-lock-password": "输入锁屏密码",
"forget-password": "忘记密码?",
"lock-password-error": "锁屏密码错误",
"failed-times": "失败超过 {{frequency}} 次, 请在 {{time}} 后重试"
"failed-times": "失败超过 {{frequency}} 次, 请在 {{time}} 后重试",
"verify-wallet": "验证"
}
}
}
2 changes: 1 addition & 1 deletion packages/neuron-ui/src/types/Controller/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ declare namespace Controller {

interface SignMessageParams {
walletID: string
address: string
address?: string
password: string
message: string
}
Expand Down
8 changes: 7 additions & 1 deletion packages/neuron-ui/src/widgets/Dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,13 @@ const Dialog = ({
<dialog
ref={dialogRef}
className={`${styles.dialogWrap} ${className}`}
onKeyDown={e => (e.key === 'Escape' && enableCloseWithEsc ? onCancel : undefined)}
onKeyDown={e => {
if (e.key === 'Escape' && enableCloseWithEsc) {
onCancel?.()
} else if (e.key === 'Enter' && showFooter && showConfirm) {
handleConfirm(e)
}
}}
role="none"
>
{showHeader ? (
Expand Down
Loading