-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into coong/feature/sign-raw-message
# Conflicts: # packages/ui/public/locales/en/translation.json
- Loading branch information
Showing
18 changed files
with
439 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/ShowingSecretPhrase.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { FC, useState } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { useAsync, useCopyToClipboard } from 'react-use'; | ||
import ContentCopyIcon from '@mui/icons-material/ContentCopy'; | ||
import { Button, DialogContentText } from '@mui/material'; | ||
import { useWalletState } from 'providers/WalletStateProvider'; | ||
import { settingsDialogActions } from 'redux/slices/settings-dialog'; | ||
import { RootState } from 'redux/store'; | ||
import { Props } from 'types'; | ||
|
||
const ShowingSecretPhrase: FC<Props> = () => { | ||
const { keyring } = useWalletState(); | ||
const { verifiedPassword } = useSelector((state: RootState) => state.settingsDialog); | ||
const [secretPhrase, setSecretPhrase] = useState(''); | ||
const [_, copyToClipboard] = useCopyToClipboard(); | ||
const { t } = useTranslation(); | ||
const [copyButtonLabel, setCopyButtonLabel] = useState('Copy to clipboard'); | ||
const dispatch = useDispatch(); | ||
|
||
const doBack = () => { | ||
dispatch(settingsDialogActions.resetState()); | ||
}; | ||
|
||
const doCopy = () => { | ||
copyToClipboard(secretPhrase); | ||
setCopyButtonLabel('Copied!'); | ||
setTimeout(() => setCopyButtonLabel('Copy to clipboard'), 5e3); | ||
}; | ||
|
||
useAsync(async () => { | ||
try { | ||
setSecretPhrase(await keyring.getRawMnemonic(verifiedPassword!)); | ||
} catch (e: any) { | ||
console.error(e.message); | ||
} | ||
}); | ||
|
||
return ( | ||
<> | ||
<DialogContentText className='my-8 p-4 bg-black/10 dark:bg-white/15'>{secretPhrase}</DialogContentText> | ||
<div className='mt-4 flex gap-4'> | ||
<Button variant='text' onClick={doBack}> | ||
{t<string>('Back')} | ||
</Button> | ||
<Button variant='contained' onClick={doCopy} startIcon={<ContentCopyIcon />} fullWidth> | ||
{t<string>(copyButtonLabel)} | ||
</Button> | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default ShowingSecretPhrase; |
67 changes: 67 additions & 0 deletions
67
packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/VerifyingPassword.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { ChangeEvent, FC, FormEvent, useState } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { useDispatch } from 'react-redux'; | ||
import { Button, DialogContentText, TextField } from '@mui/material'; | ||
import EmptySpace from 'components/shared/misc/EmptySpace'; | ||
import { useWalletState } from 'providers/WalletStateProvider'; | ||
import { settingsDialogActions } from 'redux/slices/settings-dialog'; | ||
import { Props } from 'types'; | ||
|
||
const VerifyingPassword: FC<Props> = () => { | ||
const { keyring } = useWalletState(); | ||
const [password, setPassword] = useState(''); | ||
const [validation, setValidation] = useState(''); | ||
const dispatch = useDispatch(); | ||
const { t } = useTranslation(); | ||
|
||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => { | ||
setPassword(event.currentTarget.value); | ||
setValidation(''); | ||
}; | ||
|
||
const doVerify = async (event: FormEvent) => { | ||
event.preventDefault(); | ||
if (!password) { | ||
return; | ||
} | ||
|
||
try { | ||
await keyring.verifyPassword(password); | ||
dispatch(settingsDialogActions.setVerifiedPassword(password)); | ||
} catch (e: any) { | ||
setValidation(t<string>(e.message)); | ||
} | ||
}; | ||
|
||
const doBack = () => { | ||
dispatch(settingsDialogActions.resetState()); | ||
}; | ||
|
||
return ( | ||
<> | ||
<DialogContentText className='mt-4 mb-4'>{t<string>('Enter your wallet password to continue')}</DialogContentText> | ||
<form onSubmit={doVerify} noValidate> | ||
<TextField | ||
type='password' | ||
value={password} | ||
label={t<string>('Your wallet password')} | ||
fullWidth | ||
autoFocus | ||
error={!!validation} | ||
helperText={validation || <EmptySpace />} | ||
onChange={handleChange} | ||
/> | ||
<div className='mt-2.5 flex gap-4'> | ||
<Button variant='text' onClick={doBack}> | ||
{t<string>('Back')} | ||
</Button> | ||
<Button type='submit' disabled={!password} fullWidth variant='contained'> | ||
{t<string>('View Secret Recovery Phrase')} | ||
</Button> | ||
</div> | ||
</form> | ||
</> | ||
); | ||
}; | ||
|
||
export default VerifyingPassword; |
78 changes: 78 additions & 0 deletions
78
...nents/shared/settings/BackupSecretPhraseDialog/__test__/BackupSecretPhraseDialog.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import Keyring from '@coong/keyring'; | ||
import { initializeKeyring, newUser, render, screen, UserEvent, waitFor } from '__tests__/testUtils'; | ||
import SettingsWalletButton from 'components/shared/settings/SettingsWalletButton'; | ||
import { SettingsDialogScreen } from 'types'; | ||
|
||
let user: UserEvent; | ||
vi.spyOn(window, 'prompt').mockImplementation(() => ''); | ||
beforeEach(async () => { | ||
user = newUser(); | ||
|
||
render(<SettingsWalletButton />, { | ||
preloadedState: { | ||
app: { seedReady: true, ready: true, locked: false }, | ||
settingsDialog: { settingsDialogScreen: SettingsDialogScreen.BackupSecretPhrase }, | ||
}, | ||
}); | ||
|
||
const settingsButton = screen.getByTitle('Open settings'); | ||
await user.click(settingsButton); | ||
}); | ||
|
||
describe('when verifying password', () => { | ||
it('should show the content of BackupSecretPhraseDialog', async () => { | ||
await waitFor(() => { | ||
expect(screen.getByText(/Backup secret recovery phrase/)).toBeInTheDocument(); | ||
expect(screen.getByText(/reveal the secret recovery phrase/)).toBeInTheDocument(); | ||
expect(screen.getByRole('button', { name: /View Secret Recovery Phrase/ })).toBeInTheDocument(); | ||
expect(screen.getByRole('button', { name: /Back/ })).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('should enable `View Secret Recovery Phrase` button when typing password', async () => { | ||
const passwordField = await screen.findByLabelText(/Your wallet password/); | ||
await user.type(passwordField, 'password'); | ||
|
||
expect(await screen.findByRole('button', { name: /View Secret Recovery Phrase/ })).toBeEnabled(); | ||
}); | ||
|
||
it('should show error message on submitting incorrect password', async () => { | ||
await initializeKeyring(); | ||
|
||
const passwordField = await screen.findByLabelText(/Your wallet password/); | ||
await user.type(passwordField, 'incorrect-password'); | ||
|
||
const viewSecretPhraseButton = await screen.findByRole('button', { name: /View Secret Recovery Phrase/ }); | ||
await user.click(viewSecretPhraseButton); | ||
|
||
expect(await screen.findByText(/Password incorrect/)).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
describe('when showing secret phrase', () => { | ||
let keyring: Keyring; | ||
beforeEach(async () => { | ||
keyring = await initializeKeyring(); | ||
|
||
const passwordField = await screen.findByLabelText(/Your wallet password/); | ||
await user.type(passwordField, 'supersecretpassword'); | ||
|
||
const viewSecretPhraseButton = await screen.findByRole('button', { name: /View Secret Recovery Phrase/ }); | ||
await user.click(viewSecretPhraseButton); | ||
}); | ||
|
||
it('should show `ShowingSecretPhrase` when submitting correct password', async () => { | ||
const rawMnemonic = await keyring.getRawMnemonic('supersecretpassword'); | ||
|
||
expect(await screen.findByText(rawMnemonic)).toBeInTheDocument(); | ||
expect(await screen.findByRole('button', { name: /Copy to clipboard/ })).toBeInTheDocument(); | ||
expect(await screen.findByRole('button', { name: /Back/ })).toBeInTheDocument(); | ||
}); | ||
|
||
it('should show change button label to `Copied!` when clicking on `Copy to clipboard` button', async () => { | ||
const copyToClipboardButton = await screen.findByRole('button', { name: /Copy to clipboard/ }); | ||
await user.click(copyToClipboardButton); | ||
|
||
expect(await screen.findByRole('button', { name: /Copied!/ })).toBeInTheDocument(); | ||
}); | ||
}); |
51 changes: 51 additions & 0 deletions
51
packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { FC } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { Breadcrumbs, DialogContent, DialogContentText, Link, Typography } from '@mui/material'; | ||
import DialogTitle from 'components/shared/DialogTitle'; | ||
import ShowingSecretPhrase from 'components/shared/settings/BackupSecretPhraseDialog/ShowingSecretPhrase'; | ||
import VerifyingPassword from 'components/shared/settings/BackupSecretPhraseDialog/VerifyingPassword'; | ||
import { settingsDialogActions } from 'redux/slices/settings-dialog'; | ||
import { RootState } from 'redux/store'; | ||
import { Props } from 'types'; | ||
|
||
interface BackupSecretPhraseDialogProps extends Props { | ||
onClose: () => void; | ||
} | ||
|
||
const BackupSecretPhraseDialog: FC<BackupSecretPhraseDialogProps> = ({ onClose }) => { | ||
const { verifiedPassword } = useSelector((state: RootState) => state.settingsDialog); | ||
const { t } = useTranslation(); | ||
const dispatch = useDispatch(); | ||
|
||
return ( | ||
<> | ||
<DialogTitle onClose={onClose}> | ||
<Breadcrumbs> | ||
<Link | ||
className='cursor-pointer' | ||
underline='hover' | ||
color='inherit' | ||
variant='h6' | ||
onClick={() => dispatch(settingsDialogActions.resetState())}> | ||
{t<string>('Settings')} | ||
</Link> | ||
<Typography color='text.primary' variant='h6'> | ||
{t<string>('Backup secret recovery phrase')} | ||
</Typography> | ||
</Breadcrumbs> | ||
</DialogTitle> | ||
<DialogContent className='pb-8'> | ||
<DialogContentText> | ||
{t<string>( | ||
'You are about to reveal the secret recovery phrase which give access to your accounts and funds.', | ||
)}{' '} | ||
<strong>{t<string>('Make sure you are in a safe place.')}</strong> | ||
</DialogContentText> | ||
{verifiedPassword ? <ShowingSecretPhrase /> : <VerifyingPassword />} | ||
</DialogContent> | ||
</> | ||
); | ||
}; | ||
|
||
export default BackupSecretPhraseDialog; |
Oops, something went wrong.