diff --git a/packages/keyring/src/Keyring.ts b/packages/keyring/src/Keyring.ts index c348f16e..6ded1713 100644 --- a/packages/keyring/src/Keyring.ts +++ b/packages/keyring/src/Keyring.ts @@ -89,8 +89,11 @@ export default class Keyring { } } + async getRawMnemonic(password: string): Promise { + return await this.#decryptMnemonic(password); + } + async verifyPassword(password: string) { - await this.ensureWalletInitialized(); await this.#decryptMnemonic(password); } diff --git a/packages/ui/public/locales/en/translation.json b/packages/ui/public/locales/en/translation.json index c823eec3..03232e56 100644 --- a/packages/ui/public/locales/en/translation.json +++ b/packages/ui/public/locales/en/translation.json @@ -1,4 +1,7 @@ { + "15 minutes": "", + "30 minutes": "", + "5 minutes": "", "A multichain crypto wallet for Polkadot & Kusama ecosystem": "A multichain crypto wallet
for Polkadot & Kusama ecosystem", "AccountNameRequired": "Account name is required", "AccountNameUsed": "Account name is already picked", @@ -8,7 +11,9 @@ "Address format": "", "An application, self-identifying as request app name is requesting access your wallet from origin.": "An application, self-identifying as {{appName}} is requesting access your wallet from {{origin}}.", "Approve Transaction": "", + "Auto-lock wallet after": "", "Back": "", + "Backup secret recovery phrase": "", "Cancel": "", "Choose a name for your new account": "", "Click to copy address": "", @@ -16,12 +21,15 @@ "Coming soon": "", "Confirm wallet password": "", "Connect": "", + "Copied!": "", + "Copy to clipboard": "", "Create": "", "Create New Wallet": "", "Create new account": "", "Create your first account now!": "", "Dark": "", "Deselect all": "", + "Enter your wallet password to continue": "", "Finally, back up your secret recovery phrase": "", "Finish": "", "First, choose your wallet password": "", @@ -37,6 +45,7 @@ "Language": "", "Light": "", "Lock the wallet": "", + "Make sure you are in a safe place.": "", "My first account": "", "New Account": "", "New account name": "", @@ -74,6 +83,7 @@ "UnknownRequestOrigin": "Unknown request origin", "Unlock": "", "Unlock your wallet": "", + "View Secret Recovery Phrase": "", "Wallet Access Request": "", "Wallet password": "", "WalletLocked": "The wallet is locked, please unlock it first", @@ -81,9 +91,11 @@ "Welcome to Coong": "", "Welcome to Coong Wallet!": "", "Write down the below 12 words and keep it in a safe place.": "", + "You are about to reveal the secret recovery phrase which give access to your accounts and funds.": "", "You are approving a transaction with account": "", "You are signing a message with account": "", "Your password will be used to encrypt accounts as well as unlock the wallet, make sure to pick a strong & easy-to-remember password": "Your password will be used to encrypt accounts as well as unlock the wallet, make sure to pick a strong & easy-to-remember password.", + "Your wallet password": "", "account(s) selected": "", "bytes": "", "from": "", diff --git a/packages/ui/src/components/pages/__tests__/MainScreen.spec.tsx b/packages/ui/src/components/pages/__tests__/MainScreen.spec.tsx index 9b7fc996..6b9d7555 100644 --- a/packages/ui/src/components/pages/__tests__/MainScreen.spec.tsx +++ b/packages/ui/src/components/pages/__tests__/MainScreen.spec.tsx @@ -1,6 +1,6 @@ import { defaultNetwork } from '@coong/base'; import { initializeKeyring, newUser, PASSWORD, render, screen } from '__tests__/testUtils'; -import { AutoLockTimerOptions } from 'components/shared/settings/AutoLockSelection'; +import { AutoLockTimerOptions } from 'components/shared/settings/SettingsWalletDialog/AutoLockSelection'; import MainScreen from '../MainScreen'; vi.mock('react-router-dom', async () => { diff --git a/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/ShowingSecretPhrase.tsx b/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/ShowingSecretPhrase.tsx new file mode 100644 index 00000000..db2d1019 --- /dev/null +++ b/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/ShowingSecretPhrase.tsx @@ -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 = () => { + 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 ( + <> + {secretPhrase} +
+ + +
+ + ); +}; + +export default ShowingSecretPhrase; diff --git a/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/VerifyingPassword.tsx b/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/VerifyingPassword.tsx new file mode 100644 index 00000000..6d2d1520 --- /dev/null +++ b/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/VerifyingPassword.tsx @@ -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 = () => { + const { keyring } = useWalletState(); + const [password, setPassword] = useState(''); + const [validation, setValidation] = useState(''); + const dispatch = useDispatch(); + const { t } = useTranslation(); + + const handleChange = (event: ChangeEvent) => { + 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(e.message)); + } + }; + + const doBack = () => { + dispatch(settingsDialogActions.resetState()); + }; + + return ( + <> + {t('Enter your wallet password to continue')} +
+ ('Your wallet password')} + fullWidth + autoFocus + error={!!validation} + helperText={validation || } + onChange={handleChange} + /> +
+ + +
+ + + ); +}; + +export default VerifyingPassword; diff --git a/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/__test__/BackupSecretPhraseDialog.spec.tsx b/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/__test__/BackupSecretPhraseDialog.spec.tsx new file mode 100644 index 00000000..5e4e8115 --- /dev/null +++ b/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/__test__/BackupSecretPhraseDialog.spec.tsx @@ -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(, { + 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(); + }); +}); diff --git a/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/index.tsx b/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/index.tsx new file mode 100644 index 00000000..aeb1a989 --- /dev/null +++ b/packages/ui/src/components/shared/settings/BackupSecretPhraseDialog/index.tsx @@ -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 = ({ onClose }) => { + const { verifiedPassword } = useSelector((state: RootState) => state.settingsDialog); + const { t } = useTranslation(); + const dispatch = useDispatch(); + + return ( + <> + + + dispatch(settingsDialogActions.resetState())}> + {t('Settings')} + + + {t('Backup secret recovery phrase')} + + + + + + {t( + 'You are about to reveal the secret recovery phrase which give access to your accounts and funds.', + )}{' '} + {t('Make sure you are in a safe place.')} + + {verifiedPassword ? : } + + + ); +}; + +export default BackupSecretPhraseDialog; diff --git a/packages/ui/src/components/shared/settings/SettingsWalletButton.tsx b/packages/ui/src/components/shared/settings/SettingsWalletButton.tsx index 1378b9cf..813310a8 100644 --- a/packages/ui/src/components/shared/settings/SettingsWalletButton.tsx +++ b/packages/ui/src/components/shared/settings/SettingsWalletButton.tsx @@ -1,25 +1,46 @@ import React, { FC, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import SettingsIcon from '@mui/icons-material/Settings'; -import { Dialog, DialogContent, DialogContentText, IconButton } from '@mui/material'; -import DialogTitle from 'components/shared/DialogTitle'; -import AutoLockSelection from 'components/shared/settings/AutoLockSelection'; -import LanguageSelection from 'components/shared/settings/LanguageSelection'; -import ThemeModeButton from 'components/shared/settings/ThemeModeButton'; +import { Dialog, IconButton } from '@mui/material'; +import BackupSecretPhraseDialog from 'components/shared/settings/BackupSecretPhraseDialog'; +import SettingsWalletDialog from 'components/shared/settings/SettingsWalletDialog'; +import { settingsDialogActions } from 'redux/slices/settings-dialog'; import { RootState } from 'redux/store'; import { Props } from 'types'; +import { SettingsDialogScreen } from 'types'; + +interface SettingsDialogContent extends Props { + onClose: () => void; +} +const SettingsDialogContent: FC = ({ onClose }) => { + const { settingsDialogScreen } = useSelector((state: RootState) => state.settingsDialog); + + switch (settingsDialogScreen) { + case SettingsDialogScreen.BackupSecretPhrase: + return ; + default: + return ; + } +}; const SettingsWalletButton: FC = () => { const [open, setOpen] = useState(false); const { seedReady, locked } = useSelector((state: RootState) => state.app); const { t } = useTranslation(); + const dispatch = useDispatch(); if (!seedReady || locked) { return null; } - const handleClose = () => setOpen(false); + const handleClose = () => { + setOpen(false); + + // Make sure the dialog disappears before resetting the state + // to prevent the dialog content from changing in the transition + setTimeout(() => dispatch(settingsDialogActions.resetState()), 50); + }; return ( <> @@ -27,15 +48,7 @@ const SettingsWalletButton: FC = () => { - {t('Settings')} - - {t('Theme Mode')} - - {t('Language')} - - {t('Auto-lock wallet after')} - - + ); diff --git a/packages/ui/src/components/shared/settings/AutoLockSelection.tsx b/packages/ui/src/components/shared/settings/SettingsWalletDialog/AutoLockSelection.tsx similarity index 100% rename from packages/ui/src/components/shared/settings/AutoLockSelection.tsx rename to packages/ui/src/components/shared/settings/SettingsWalletDialog/AutoLockSelection.tsx diff --git a/packages/ui/src/components/shared/settings/SettingsWalletDialog/BackupSecretPhraseButton.tsx b/packages/ui/src/components/shared/settings/SettingsWalletDialog/BackupSecretPhraseButton.tsx new file mode 100644 index 00000000..4f3ff0c6 --- /dev/null +++ b/packages/ui/src/components/shared/settings/SettingsWalletDialog/BackupSecretPhraseButton.tsx @@ -0,0 +1,29 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; +import KeyIcon from '@mui/icons-material/Key'; +import { Button } from '@mui/material'; +import useThemeMode from 'hooks/useThemeMode'; +import { settingsDialogActions } from 'redux/slices/settings-dialog'; +import { Props, SettingsDialogScreen } from 'types'; + +const BackupSecretPhraseButton: FC = () => { + const { t } = useTranslation(); + const { dark } = useThemeMode(); + const dispatch = useDispatch(); + + return ( + + ); +}; + +export default BackupSecretPhraseButton; diff --git a/packages/ui/src/components/shared/settings/LanguageSelection.tsx b/packages/ui/src/components/shared/settings/SettingsWalletDialog/LanguageSelection.tsx similarity index 100% rename from packages/ui/src/components/shared/settings/LanguageSelection.tsx rename to packages/ui/src/components/shared/settings/SettingsWalletDialog/LanguageSelection.tsx diff --git a/packages/ui/src/components/shared/settings/ThemeModeButton.tsx b/packages/ui/src/components/shared/settings/SettingsWalletDialog/ThemeModeButton.tsx similarity index 100% rename from packages/ui/src/components/shared/settings/ThemeModeButton.tsx rename to packages/ui/src/components/shared/settings/SettingsWalletDialog/ThemeModeButton.tsx diff --git a/packages/ui/src/components/shared/settings/SettingsWalletDialog/index.tsx b/packages/ui/src/components/shared/settings/SettingsWalletDialog/index.tsx new file mode 100644 index 00000000..1f7814e6 --- /dev/null +++ b/packages/ui/src/components/shared/settings/SettingsWalletDialog/index.tsx @@ -0,0 +1,35 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { DialogContent, DialogContentText, Divider } from '@mui/material'; +import DialogTitle from 'components/shared/DialogTitle'; +import AutoLockSelection from 'components/shared/settings/SettingsWalletDialog/AutoLockSelection'; +import BackupSecretPhraseButton from 'components/shared/settings/SettingsWalletDialog/BackupSecretPhraseButton'; +import LanguageSelection from 'components/shared/settings/SettingsWalletDialog/LanguageSelection'; +import ThemeModeButton from 'components/shared/settings/SettingsWalletDialog/ThemeModeButton'; +import { Props } from 'types'; + +interface SettingsWalletDialogProps extends Props { + onClose: () => void; +} + +const SettingsWalletDialog: FC = ({ onClose }) => { + const { t } = useTranslation(); + + return ( + <> + {t('Settings')} + + {t('Theme Mode')} + + {t('Language')} + + {t('Auto-lock wallet after')} + + + + + + ); +}; + +export default SettingsWalletDialog; diff --git a/packages/ui/src/components/shared/settings/__test__/SettingsWalletButton.spec.tsx b/packages/ui/src/components/shared/settings/__test__/SettingsWalletButton.spec.tsx index d0a20315..8ee6e6e7 100644 --- a/packages/ui/src/components/shared/settings/__test__/SettingsWalletButton.spec.tsx +++ b/packages/ui/src/components/shared/settings/__test__/SettingsWalletButton.spec.tsx @@ -1,4 +1,4 @@ -import { initializeKeyring, newUser, render, screen, UserEvent, waitFor } from '__tests__/testUtils'; +import { newUser, render, screen, UserEvent, waitFor } from '__tests__/testUtils'; import SettingsWalletButton from 'components/shared/settings/SettingsWalletButton'; describe('SettingsWalletButton', () => { @@ -9,7 +9,7 @@ describe('SettingsWalletButton', () => { describe('when the dialog is open', () => { let user: UserEvent; - beforeEach(() => { + beforeEach(async () => { user = newUser(); render(, { @@ -17,12 +17,12 @@ describe('SettingsWalletButton', () => { }); const settingsButton = screen.getByTitle('Open settings'); - user.click(settingsButton); + await user.click(settingsButton); }); it('should active dark button and switch to dark theme when clicking on the dark mode button', async () => { const darkModeButton = await screen.findByRole('button', { name: /Dark/ }); - user.click(darkModeButton); + await user.click(darkModeButton); await waitFor(() => { expect(darkModeButton).toHaveClass('MuiButton-outlinedPrimary'); @@ -32,31 +32,50 @@ describe('SettingsWalletButton', () => { it("should the AutoLockSelection switch to '15 minutes' when choose 15 minutes", async () => { const autoLockSelectionButton = await screen.findByRole('button', { name: /5 minutes/ }); - user.click(autoLockSelectionButton); + await user.click(autoLockSelectionButton); const fifteenMinutesSelection = await screen.findByText(/15 minutes/); - user.click(fifteenMinutesSelection); + await user.click(fifteenMinutesSelection); expect(await screen.findByRole('button', { name: /15 minutes/ })).toBeInTheDocument(); }); it('should close the dialog when clicking the close button', async () => { const closeButton = await screen.findByTitle('Close settings'); - user.click(closeButton); + await user.click(closeButton); await waitFor(() => { expect(screen.queryByText('Settings')).not.toBeInTheDocument(); }); }); - it('should show ThemeModeButton, LanguageSelection, AutoLockSelection', async () => { + it('should switch to BackupSecretPhraseDialog content when clicking on `Backup secret recovery phrase` button', async () => { + const BackupSecretRecoveryPhraseButton = await screen.findByRole('button', { + name: /Backup secret recovery phrase/, + }); + + user.click(BackupSecretRecoveryPhraseButton); + + 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 show ThemeModeButton, LanguageSelection, AutoLockSelection, `Backup secret recovery phrase` button, `Change wallet password` button', async () => { expect(await screen.findByRole('dialog')).toBeInTheDocument(); - expect(await screen.findByRole('button', { name: /Dark/ })).toBeInTheDocument(); - expect(await screen.findByRole('button', { name: /Light/ })).toBeInTheDocument(); - expect(await screen.findByRole('button', { name: /System/ })).toBeInTheDocument(); - expect(await screen.findByRole('button', { name: /Close settings/ })).toBeInTheDocument(); - expect(await screen.findByRole('button', { name: /English/ })).toBeInTheDocument(); - expect(await screen.findByRole('button', { name: /5 minutes/ })).toBeInTheDocument(); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /Dark/ })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Light/ })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /System/ })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Close settings/ })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /English/ })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /5 minutes/ })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Backup secret recovery phrase/ })).toBeInTheDocument(); + }); }); }); @@ -68,9 +87,6 @@ describe('SettingsWalletButton', () => { }); describe('keyring initialized', () => { - beforeEach(async () => { - await initializeKeyring(); - }); it('should be hidden if the wallet is locked', () => { render(, { preloadedState: { app: { seedReady: true, ready: true, locked: true } } }); expect(screen.queryByTitle('Open settings')).not.toBeInTheDocument(); diff --git a/packages/ui/src/redux/slices/settings-dialog.ts b/packages/ui/src/redux/slices/settings-dialog.ts new file mode 100644 index 00000000..decc448a --- /dev/null +++ b/packages/ui/src/redux/slices/settings-dialog.ts @@ -0,0 +1,32 @@ +import { createSlice, Draft, PayloadAction } from '@reduxjs/toolkit'; +import { SettingsDialogScreen } from 'types'; + +export interface SettingsDialog { + settingsDialogScreen: SettingsDialogScreen; + verifiedPassword?: string; +} + +const initialState: SettingsDialog = { + settingsDialogScreen: SettingsDialogScreen.SettingsWallet, +}; + +const settingsDialogSlice = createSlice({ + name: 'settingsDialog', + initialState, + reducers: { + switchSettingsDialogScreen: (state: Draft, action: PayloadAction) => { + state.settingsDialogScreen = action.payload; + }, + setVerifiedPassword: (state: Draft, action: PayloadAction) => { + state.verifiedPassword = action.payload; + }, + resetState: (state: Draft) => { + state.settingsDialogScreen = SettingsDialogScreen.SettingsWallet; + state.verifiedPassword = ''; + }, + }, +}); + +export const settingsDialogActions = settingsDialogSlice.actions; + +export default settingsDialogSlice; diff --git a/packages/ui/src/redux/store.ts b/packages/ui/src/redux/store.ts index 1bb35254..af8816a2 100644 --- a/packages/ui/src/redux/store.ts +++ b/packages/ui/src/redux/store.ts @@ -4,6 +4,7 @@ import storage from 'redux-persist/lib/storage'; import accountsSlice from 'redux/slices/accounts'; import appSlice from 'redux/slices/app'; import settingsSlice from 'redux/slices/settings'; +import settingsDialogSlice from 'redux/slices/settings-dialog'; import setupWalletSlice from 'redux/slices/setup-wallet'; const appPersistConfig = { @@ -22,6 +23,7 @@ const rootReducer = combineReducers({ [setupWalletSlice.name]: setupWalletSlice.reducer, [accountsSlice.name]: accountsSlice.reducer, [settingsSlice.name]: persistReducer(settingsPersistConfig, settingsSlice.reducer), + [settingsDialogSlice.name]: settingsDialogSlice.reducer, }); export const newStore = (preloadedState?: PreloadedState) => { diff --git a/packages/ui/src/styles/theme.ts b/packages/ui/src/styles/theme.ts index 2c79593d..ae6e9dae 100644 --- a/packages/ui/src/styles/theme.ts +++ b/packages/ui/src/styles/theme.ts @@ -49,6 +49,13 @@ export const newTheme = (mode: PaletteMode) => }, }, }, + MuiDialogContentText: { + styleOverrides: { + root: { + color: 'inherit', + }, + }, + }, MuiTextField: { defaultProps: { variant: 'outlined', diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index f927afa5..66c68979 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -26,3 +26,8 @@ export enum AutoLockInterval { FifteenMinutes = 15 * 60e3, ThirtyMinutes = 30 * 60e3, } + +export enum SettingsDialogScreen { + SettingsWallet, + BackupSecretPhrase, +}