Skip to content

Commit

Permalink
feat: Support lock window for Neuron (#3064)
Browse files Browse the repository at this point in the history
Co-authored-by: Chen Yu <keithwhisper@gmail.com>
  • Loading branch information
yanguoyu and Keith-CY authored Apr 29, 2024
1 parent 76c3871 commit 128e425
Show file tree
Hide file tree
Showing 38 changed files with 903 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { TFunction } from 'i18next'
import { useCallback, useState } from 'react'
import { verifyLockWindowPassword } from 'services/remote'
import { updateLockWindowInfo, useDispatch } from 'states'
import { isSuccessResponse } from 'utils'

export const passwordLength = 4

export const useOldPassword = ({ t }: { t: TFunction }) => {
const [oldPasswordErr, setOldPasswordErr] = useState('')
const [oldPassword, setOldPassword] = useState(new Array(passwordLength).fill(''))
const [verifySuccess, setVerifySuccess] = useState(false)
const onUpdateOldPassword = useCallback(
(v: string, idx: number) => {
const updatedOldPassword = oldPassword.toSpliced(idx, 1, v).join('')
if (updatedOldPassword.length === passwordLength) {
verifyLockWindowPassword(updatedOldPassword)
.then(res => {
if (isSuccessResponse(res)) {
// verify success
setVerifySuccess(true)
}
throw new Error('verify failed')
})
.catch(() => {
setOldPassword(new Array(passwordLength).fill(''))
setOldPasswordErr(t('settings.general.lock-window.password-error'))
})
} else {
setOldPassword(value => value.toSpliced(idx, 1, v))
}
},
[oldPassword]
)
const resetOldPassword = useCallback(() => {
setOldPasswordErr('')
setOldPassword(new Array(passwordLength).fill(''))
setVerifySuccess(false)
}, [])
return {
oldPasswordErr,
oldPassword,
onUpdateOldPassword,
verifySuccess,
setVerifySuccess,
resetOldPassword,
}
}

export const usePassword = () => {
const [password, setPassword] = useState(new Array(passwordLength).fill(''))
const onUpdatePassword = useCallback((v: string, idx: number) => {
setPassword(value => value.toSpliced(idx, 1, v))
}, [])
const resetPassword = useCallback(() => {
setPassword(new Array(passwordLength).fill(''))
}, [])
return {
password,
onUpdatePassword,
resetPassword,
}
}

export const useRepeatPassword = ({
password,
t,
encryptedPassword,
onCancel,
}: {
password: string
t: TFunction
encryptedPassword?: string
onCancel: () => void
}) => {
const dispatch = useDispatch()
const [errMsg, setErrMsg] = useState('')
const [repeatPassword, setRepeatPassword] = useState(new Array(passwordLength).fill(''))
const [isSuccess, setIsSuccess] = useState(false)
const onUpdateRepeatPassword = useCallback(
(v: string, idx: number) => {
const updatedRepeatPassword = repeatPassword.toSpliced(idx, 1, v).join('')
if (updatedRepeatPassword.length === passwordLength) {
if (updatedRepeatPassword !== password) {
setErrMsg(t('settings.general.lock-window.different-password'))
setRepeatPassword(new Array(passwordLength).fill(''))
} else {
setIsSuccess(true)
updateLockWindowInfo(
encryptedPassword ? { password: updatedRepeatPassword } : { password: updatedRepeatPassword, locked: true }
)(dispatch)
onCancel()
}
} else {
setErrMsg('')
setRepeatPassword(value => value.toSpliced(idx, 1, v))
}
},
[password, t, repeatPassword]
)
const resetRepeatPassword = useCallback(() => {
setErrMsg('')
setRepeatPassword(new Array(passwordLength).fill(''))
setIsSuccess(false)
}, [])
return {
errMsg,
repeatPassword,
onUpdateRepeatPassword,
resetRepeatPassword,
isSuccess,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, { useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import Dialog from 'widgets/Dialog'
import SplitPasswordInput from 'widgets/SplitPasswordInput'
import Alert from 'widgets/Alert'
import Button from 'widgets/Button'
import { passwordLength, useOldPassword, usePassword, useRepeatPassword } from './hooks'
import styles from './lockWindowDialog.module.scss'

const LockWindowDialog = ({
show,
onCancel,
encryptedPassword,
}: {
show: boolean
onCancel: () => void
encryptedPassword?: string
}) => {
const [t] = useTranslation()
const { oldPasswordErr, verifySuccess, oldPassword, onUpdateOldPassword, resetOldPassword } = useOldPassword({ t })
const { password, onUpdatePassword, resetPassword } = usePassword()
const joinedPassword = useMemo(() => password.join(''), [password])
const { errMsg, repeatPassword, onUpdateRepeatPassword, resetRepeatPassword, isSuccess } = useRepeatPassword({
t,
password: joinedPassword,
encryptedPassword,
onCancel,
})
useEffect(() => {
// when dialog open, reset all status
if (show) {
resetOldPassword()
resetPassword()
resetRepeatPassword()
}
}, [show, resetOldPassword, resetPassword, resetRepeatPassword])
const onReset = useCallback(() => {
resetPassword()
resetRepeatPassword()
}, [resetPassword, resetRepeatPassword])
const content = () => {
if (encryptedPassword && !verifySuccess) {
// is verify old password
return (
<>
<span className={styles.secTitle}>{t('settings.general.lock-window.enter-current-password')}</span>
<div className={styles.password}>
<SplitPasswordInput values={oldPassword} onChange={onUpdateOldPassword} />
<div className={styles.err}>{oldPasswordErr}</div>
</div>
</>
)
}
if (joinedPassword.length !== passwordLength) {
// enter password
return (
<>
<span className={styles.secTitle}>{t(`settings.general.lock-window.set-password`)}</span>
<div className={styles.password}>
<SplitPasswordInput values={password} onChange={onUpdatePassword} />
</div>
</>
)
}
// is verify repeat password
return (
<>
<span className={styles.secTitle}>{t(`settings.general.lock-window.confirm-password`)}</span>
<div className={styles.password}>
<SplitPasswordInput values={repeatPassword} onChange={onUpdateRepeatPassword} />
<div className={styles.err}>{errMsg}</div>
{errMsg ? (
<Button type="primary" onClick={onReset}>
{t('settings.general.lock-window.reset')}
</Button>
) : null}
</div>
</>
)
}

return (
<>
<Dialog
show={show}
title={t(`settings.general.${encryptedPassword ? 'change-lock-password' : 'set-lock-password'}`)}
onCancel={onCancel}
showFooter={false}
className={styles.dialog}
>
<div className={styles.content}>{content()}</div>
</Dialog>
{isSuccess ? (
<Alert status="success" className={styles.notice}>
{t(`settings.general.lock-window.${encryptedPassword ? 'change-password-success' : 'set-password-success'}`)}
</Alert>
) : null}
</>
)
}

LockWindowDialog.displayName = 'LockWindowDialog'
export default LockWindowDialog
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@import '../../../styles/mixin.scss';

.content {
text-align: center;

.secTitle {
color: var(--secondary-text-color);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}

.password {
margin-top: 24px;
}

.err {
color: var(--error-color);
margin: 16px 0 24px 0;
}
}

.dialog {
width: 680px;
}

.notice {
@include dialog-copy-animation;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ $action-button-width: 11.25rem;
margin-right: 4px;
}
}

&.lockWindow {
width: auto;
min-width: 176px;
}
}
.showVersion {
position: relative;
Expand Down
31 changes: 29 additions & 2 deletions packages/neuron-ui/src/components/GeneralSetting/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import { ReactComponent as ArrowNext } from 'widgets/Icons/ArrowNext.svg'
import { ReactComponent as Update } from 'widgets/Icons/Update.svg'
import { cancelCheckUpdates, downloadUpdate, installUpdate, getVersion } from 'services/remote'
import { uniformTimeFormatter, bytesFormatter, clsx, wakeScreen, releaseWakeLock } from 'utils'
import { LanguageSelect } from 'widgets/Icons/icon'
import Switch from 'widgets/Switch'
import { keepScreenAwake } from 'services/localCache'
import { LanguageSelect, UnLock } from 'widgets/Icons/icon'
import styles from './generalSetting.module.scss'
import { useCheckUpdate, useUpdateDownloadStatus } from './hooks'
import LockWindowDialog from './LockWindowDialog'

interface UpdateDownloadStatusProps {
show: boolean
Expand Down Expand Up @@ -136,11 +137,13 @@ const UpdateDownloadStatus = ({

interface GeneralSettingProps {
updater: State.AppUpdater
app: State.App
}

const GeneralSetting = ({ updater }: GeneralSettingProps) => {
const GeneralSetting = ({ updater, app }: GeneralSettingProps) => {
const [t, i18n] = useTranslation()
const [showLangDialog, setShowLangDialog] = useState(false)
const [isLockDialogShow, setIsLockDialogShow] = useState(false)
const [searchParams] = useSearchParams()
const [errorMsg, setErrorMsg] = useState('')
const { showCheckDialog, setShowCheckDialog, onCancelCheckUpdates } = useCheckUpdate()
Expand Down Expand Up @@ -221,6 +224,22 @@ const GeneralSetting = ({ updater }: GeneralSettingProps) => {
<Switch checked={isScreenKeepAwake} onChange={onChangeScreenKeepAwake} />
</div>

<div className={clsx(styles.content, styles.lockWindow)}>
<p>{t('settings.general.lock-password')}</p>
<button
type="button"
data-button-type="text"
onClick={() => {
setIsLockDialogShow(true)
}}
>
<UnLock />
{t(
`settings.general.${app.lockWindowInfo?.encryptedPassword ? 'change-lock-password' : 'set-lock-password'}`
)}
</button>
</div>

<AlertDialog
show={!!errorMsg}
title={t(`updates.check-updates`)}
Expand Down Expand Up @@ -260,6 +279,14 @@ const GeneralSetting = ({ updater }: GeneralSettingProps) => {
setShowLangDialog(false)
}}
/>

<LockWindowDialog
show={isLockDialogShow}
encryptedPassword={app.lockWindowInfo?.encryptedPassword}
onCancel={() => {
setIsLockDialogShow(false)
}}
/>
</div>
)
}
Expand Down
Loading

2 comments on commit 128e425

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Packaging for test is done in 8874779385

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Packaging for test is done in 8874779638

Please sign in to comment.