minor refactors
This commit is contained in:
@@ -12,10 +12,10 @@ import {
|
||||
import { Edit } from '@mui/icons-material';
|
||||
import { AccountsContext } from 'src/context';
|
||||
import { AccountAvatar } from './AccountAvatar';
|
||||
import { ShowMnemonic } from './ShowMnemonic';
|
||||
|
||||
export const AccountItem = ({ name, address }: { name: string; address: string }) => {
|
||||
const { selectedAccount, handleSelectAccount, handleAccountToEdit } = useContext(AccountsContext);
|
||||
const { selectedAccount, handleSelectAccount, handleAccountToEdit, setDialogToDisplay, setAccountMnemonic } =
|
||||
useContext(AccountsContext);
|
||||
return (
|
||||
<ListItem
|
||||
disablePadding
|
||||
@@ -32,7 +32,18 @@ export const AccountItem = ({ name, address }: { name: string; address: string }
|
||||
<Box>
|
||||
<Typography variant="body2">{address}</Typography>
|
||||
<Box sx={{ mt: 0.5 }}>
|
||||
<ShowMnemonic accountName={name} />
|
||||
<Typography
|
||||
variant="body2"
|
||||
component="span"
|
||||
sx={{ textDecoration: 'underline', mb: 0.5, '&:hover': { color: 'primary.main' } }}
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
setDialogToDisplay('Mnemonic');
|
||||
setAccountMnemonic((accountMnemonic) => ({ ...accountMnemonic, accountName: name }));
|
||||
}}
|
||||
>
|
||||
Show mnemonic
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { EditAccountModal } from './EditAccountModal';
|
||||
import { AddAccountModal } from './AddAccountModal';
|
||||
import { AccountsModal } from './AccountsModal';
|
||||
import { AccountAvatar } from './AccountAvatar';
|
||||
import { MnemonicModal } from './MnemonicModal';
|
||||
|
||||
export const Accounts = () => {
|
||||
const { accounts, selectedAccount, setDialogToDisplay } = useContext(AccountsContext);
|
||||
@@ -22,6 +23,7 @@ export const Accounts = () => {
|
||||
<AccountsModal />
|
||||
<AddAccountModal />
|
||||
<EditAccountModal />
|
||||
<MnemonicModal />
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Check, Close, ContentCopySharp } from '@mui/icons-material';
|
||||
import { useClipboard } from 'use-clipboard-copy';
|
||||
import { createMnemonic } from 'src/requests';
|
||||
import { AccountsContext } from 'src/context';
|
||||
import { Mnemonic } from '../Mnemonic';
|
||||
|
||||
const createAccountSteps = [
|
||||
'Copy and save mnemonic for your new account',
|
||||
@@ -29,67 +30,12 @@ const importAccountSteps = [
|
||||
'Confirm the password used to login to your wallet',
|
||||
];
|
||||
|
||||
const passwordCreationSteps = [
|
||||
'Log out',
|
||||
'During sign in screen click “Sign in with mnemonic” button',
|
||||
'On next screen click “Create a password for your account”',
|
||||
'Sign in to wallet with your new password',
|
||||
'Now you can create multiple accounts',
|
||||
];
|
||||
|
||||
const NoPassword = ({ onClose }: { onClose: () => void }) => (
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<DialogContent>
|
||||
<Stack spacing={2}>
|
||||
<Alert severity="warning" icon={false} sx={{ display: 'block' }}>
|
||||
<Typography sx={{ textAlign: 'center' }}>
|
||||
You can’t add new accounts if your wallet doesn’t have a password.
|
||||
</Typography>
|
||||
<Typography sx={{ textAlign: 'center' }}>Follow steps below to create password.</Typography>
|
||||
</Alert>
|
||||
<Typography variant="h6">How to create password to your account</Typography>
|
||||
{passwordCreationSteps.map((step, i) => (
|
||||
<Typography>{`${i + 1}. ${step}`}</Typography>
|
||||
))}
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3 }}>
|
||||
<Button fullWidth disableElevation variant="contained" size="large" onClick={onClose}>
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const MnemonicStep = ({ mnemonic, onNext }: { mnemonic: string; onNext: () => void }) => {
|
||||
const { copy, copied } = useClipboard({ copiedTimeout: 5000 });
|
||||
return (
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} alignItems="center">
|
||||
<Alert severity="warning" icon={false} sx={{ display: 'block' }}>
|
||||
<Typography sx={{ textAlign: 'center' }}>
|
||||
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the
|
||||
future
|
||||
</Typography>
|
||||
</Alert>
|
||||
<TextField multiline rows={3} value={mnemonic} fullWidth />
|
||||
|
||||
<Button
|
||||
color="inherit"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={() => {
|
||||
copy(mnemonic);
|
||||
}}
|
||||
sx={{
|
||||
width: 250,
|
||||
}}
|
||||
endIcon={!copied ? <ContentCopySharp /> : <Check color="success" />}
|
||||
>
|
||||
Copy mnemonic
|
||||
</Button>
|
||||
</Stack>
|
||||
<Mnemonic mnemonic={mnemonic} handleCopy={copy} copied={copied} />
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3, pt: 0 }}>
|
||||
<Button disabled={!copied} fullWidth disableElevation variant="contained" size="large" onClick={onNext}>
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Close } from '@mui/icons-material';
|
||||
import { AccountsContext } from 'src/context';
|
||||
import { useClipboard } from 'use-clipboard-copy';
|
||||
import { Mnemonic } from '../Mnemonic';
|
||||
|
||||
export const MnemonicModal = () => {
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const { copy, copied } = useClipboard({ copiedTimeout: 5000 });
|
||||
|
||||
const {
|
||||
dialogToDisplay,
|
||||
setDialogToDisplay,
|
||||
accountMnemonic,
|
||||
setAccountMnemonic,
|
||||
handleGetAcccountMnemonic,
|
||||
error,
|
||||
setError,
|
||||
isLoading,
|
||||
} = useContext(AccountsContext);
|
||||
|
||||
const handleClose = () => {
|
||||
setAccountMnemonic({ value: undefined, accountName: undefined });
|
||||
setError(undefined);
|
||||
setDialogToDisplay('Accounts');
|
||||
setPassword('');
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={dialogToDisplay === 'Mnemonic'} onClose={handleClose} fullWidth hideBackdrop>
|
||||
<DialogTitle>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">Display mnemonic</Typography>
|
||||
<IconButton onClick={handleClose}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ color: 'grey.600' }}>
|
||||
{`Display mnemonic for: ${accountMnemonic?.accountName}`}
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ p: 0 }}>
|
||||
<Box sx={{ px: 3, mt: 1 }}>
|
||||
{error && (
|
||||
<Typography variant="body1" sx={{ color: 'error.main', mb: 2 }}>
|
||||
{error}
|
||||
</Typography>
|
||||
)}
|
||||
{!accountMnemonic.value ? (
|
||||
<>
|
||||
<Typography sx={{ mb: 2 }}>Enter the password used to login to your wallet</Typography>
|
||||
<TextField
|
||||
label="Password"
|
||||
type="password"
|
||||
fullWidth
|
||||
value={password}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPassword(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Mnemonic mnemonic={accountMnemonic.value} handleCopy={copy} copied={copied} />
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3 }}>
|
||||
{!accountMnemonic.value && (
|
||||
<Button
|
||||
disableRipple
|
||||
disabled={!password.length || isLoading}
|
||||
fullWidth
|
||||
disableElevation
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={async () => {
|
||||
if (accountMnemonic?.accountName) {
|
||||
setError(undefined);
|
||||
await handleGetAcccountMnemonic({ password, accountName: accountMnemonic?.accountName });
|
||||
}
|
||||
}}
|
||||
endIcon={isLoading && <CircularProgress size={20} />}
|
||||
>
|
||||
Display mnemonic
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { CopyToClipboard } from '@nymproject/react';
|
||||
|
||||
export const ShowMnemonic = ({ accountName }: { accountName: string }) => {
|
||||
const [showMnemonic, setShowMnemonic] = useState<string>();
|
||||
const [mnemonic, setMnemonic] = useState<string>();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ textDecoration: 'underline', mb: 0.5 }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowMnemonic((show) => (!show ? accountName : undefined));
|
||||
}}
|
||||
>
|
||||
{`${showMnemonic ? 'Hide' : 'Show'} mnemonic`}
|
||||
</Typography>
|
||||
{mnemonic && (
|
||||
<Box display="flex" alignItems="end">
|
||||
<Typography variant="caption">{mnemonic}</Typography>
|
||||
<CopyToClipboard sx={{ width: 18 }} value={mnemonic} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Alert,
|
||||
AlertTitle,
|
||||
Box,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
Stack,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Close } from '@mui/icons-material';
|
||||
import { CopyToClipboard } from '@nymproject/react';
|
||||
|
||||
export const ShowMnemonicModal = ({
|
||||
mnemonic,
|
||||
show,
|
||||
onClose,
|
||||
}: {
|
||||
mnemonic: string;
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
}) => (
|
||||
<Dialog open={show} onClose={onClose} fullWidth hideBackdrop>
|
||||
<DialogTitle>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">Show mnemonic</Typography>
|
||||
<IconButton onClick={onClose}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2}>
|
||||
<Alert severity="warning" icon={false} sx={{ display: 'block' }}>
|
||||
<Typography sx={{ textAlign: 'center' }}>DO NOT share this phrase with anyone!</Typography>
|
||||
<Typography sx={{ textAlign: 'center' }}>These words can be used to steal all your accounts.</Typography>
|
||||
</Alert>
|
||||
<Alert color="info" action={<CopyToClipboard value={mnemonic} />} icon={false}>
|
||||
<AlertTitle sx={{ fontWeight: 700 }}>Mnemonic</AlertTitle>
|
||||
<Typography variant="body1">{mnemonic}</Typography>
|
||||
</Alert>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { ShowMnemonicModal } from 'src/components/Accounts/ShowMnemonicModal';
|
||||
|
||||
export default {
|
||||
title: 'Wallet / Multi Account / Show Mnemonic',
|
||||
component: ShowMnemonicModal,
|
||||
} as ComponentMeta<typeof ShowMnemonicModal>;
|
||||
|
||||
const Template: ComponentStory<typeof ShowMnemonicModal> = (args) => (
|
||||
<Box display="flex" alignContent="center">
|
||||
<ShowMnemonicModal {...args} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
mnemonic:
|
||||
'lonely employ curtain skull gas swim pizza injury tail birth inmate apart giraffe behave caution hammer echo action best symptom skull toast beyond casino',
|
||||
show: true,
|
||||
onClose: () => {},
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { Alert, Button, Stack, TextField, Typography } from '@mui/material';
|
||||
import { Check, ContentCopySharp } from '@mui/icons-material';
|
||||
|
||||
export const Mnemonic = ({
|
||||
mnemonic,
|
||||
copied,
|
||||
handleCopy,
|
||||
}: {
|
||||
mnemonic: string;
|
||||
copied: boolean;
|
||||
handleCopy: (text?: string) => void;
|
||||
}) => (
|
||||
<Stack spacing={2} alignItems="center">
|
||||
<Alert severity="warning" icon={false} sx={{ display: 'block' }}>
|
||||
<Typography sx={{ textAlign: 'center' }}>
|
||||
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future
|
||||
</Typography>
|
||||
</Alert>
|
||||
<TextField multiline rows={3} value={mnemonic} fullWidth />
|
||||
|
||||
<Button
|
||||
color="inherit"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={() => {
|
||||
handleCopy(mnemonic);
|
||||
}}
|
||||
sx={{
|
||||
width: 250,
|
||||
}}
|
||||
endIcon={!copied ? <ContentCopySharp /> : <Check color="success" />}
|
||||
>
|
||||
Copy mnemonic
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { createContext, Dispatch, SetStateAction, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { AccountEntry } from 'src/types';
|
||||
import { addAccount as addAccountRequest } from 'src/requests';
|
||||
import { addAccount as addAccountRequest, showMnemonicForAccount } from 'src/requests';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { ClientContext } from './main';
|
||||
|
||||
@@ -11,16 +11,20 @@ type TAccounts = {
|
||||
dialogToDisplay?: TAccountsDialog;
|
||||
isLoading: boolean;
|
||||
error?: string;
|
||||
accountMnemonic: TAccountMnemonic;
|
||||
setError: Dispatch<SetStateAction<string | undefined>>;
|
||||
setAccountMnemonic: Dispatch<SetStateAction<TAccountMnemonic>>;
|
||||
handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void;
|
||||
setDialogToDisplay: (dialog?: TAccountsDialog) => void;
|
||||
handleSelectAccount: (accountId: string) => void;
|
||||
handleAccountToEdit: (accountId: string) => void;
|
||||
handleEditAccount: (account: AccountEntry) => void;
|
||||
handleImportAccount: (account: AccountEntry) => void;
|
||||
handleGetAcccountMnemonic: (data: { password: string; accountName: string }) => void;
|
||||
};
|
||||
|
||||
export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import';
|
||||
export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import' | 'Mnemonic';
|
||||
export type TAccountMnemonic = { value?: string; accountName?: string };
|
||||
|
||||
export const AccountsContext = createContext({} as TAccounts);
|
||||
|
||||
@@ -29,6 +33,10 @@ export const AccountsProvider: React.FC = ({ children }) => {
|
||||
const [selectedAccount, setSelectedAccount] = useState<AccountEntry>();
|
||||
const [accountToEdit, setAccountToEdit] = useState<AccountEntry>();
|
||||
const [dialogToDisplay, setDialogToDisplay] = useState<TAccountsDialog>();
|
||||
const [accountMnemonic, setAccountMnemonic] = useState<TAccountMnemonic>({
|
||||
value: undefined,
|
||||
accountName: undefined,
|
||||
});
|
||||
const [error, setError] = useState<string>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { onAccountChange, storedAccounts } = useContext(ClientContext);
|
||||
@@ -73,6 +81,18 @@ export const AccountsProvider: React.FC = ({ children }) => {
|
||||
setSelectedAccount(match);
|
||||
};
|
||||
|
||||
const handleGetAcccountMnemonic = async ({ password, accountName }: { password: string; accountName: string }) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const mnemonic = await showMnemonicForAccount({ password, accountName });
|
||||
setAccountMnemonic({ value: mnemonic, accountName });
|
||||
} catch (e) {
|
||||
setError(e as string);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (storedAccounts) {
|
||||
setAccounts(storedAccounts);
|
||||
@@ -93,15 +113,18 @@ export const AccountsProvider: React.FC = ({ children }) => {
|
||||
selectedAccount,
|
||||
accountToEdit,
|
||||
dialogToDisplay,
|
||||
accountMnemonic,
|
||||
setDialogToDisplay,
|
||||
setAccountMnemonic,
|
||||
isLoading,
|
||||
handleAddAccount,
|
||||
handleEditAccount,
|
||||
handleAccountToEdit,
|
||||
handleSelectAccount,
|
||||
handleImportAccount,
|
||||
handleGetAcccountMnemonic,
|
||||
}),
|
||||
[accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error],
|
||||
[accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error, accountMnemonic],
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -58,7 +58,7 @@ export const listAccounts = async () => {
|
||||
};
|
||||
|
||||
export const showMnemonicForAccount = async ({ password, accountName }: { password: string; accountName: string }) => {
|
||||
const res: string = await invoke('show_mnemonic_for_account_in_password', { password, innerId: accountName });
|
||||
const res: string = await invoke('show_mnemonic_for_account_in_password', { password, accountId: accountName });
|
||||
return res;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user