Merge pull request #1227 from nymtech/feature/multi-accounts-integrate-rust
Multiple accounts
This commit is contained in:
@@ -44,6 +44,7 @@ fn main() {
|
||||
mixnet::account::get_validator_api_urls,
|
||||
mixnet::account::get_validator_nymd_urls,
|
||||
mixnet::account::list_accounts,
|
||||
mixnet::account::sign_in_decrypted_account,
|
||||
mixnet::account::logout,
|
||||
mixnet::account::remove_account_for_password,
|
||||
mixnet::account::remove_password,
|
||||
|
||||
@@ -3,6 +3,6 @@ import { Avatar } from '@mui/material';
|
||||
import stc from 'string-to-color';
|
||||
import { TAccount } from 'src/types';
|
||||
|
||||
export const AccountAvatar = ({ name, address }: TAccount) => (
|
||||
<Avatar sx={{ bgcolor: stc(address), width: 35, height: 35, fontSize: 20 }}>{name.split('')[0]}</Avatar>
|
||||
export const AccountAvatar = ({ name }: Pick<TAccount, 'name'>) => (
|
||||
<Avatar sx={{ bgcolor: stc(name), width: 35, height: 35 }}>{name?.split('')[0]}</Avatar>
|
||||
);
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { TAccount } from 'src/types';
|
||||
import { Accounts } from './Accounts';
|
||||
import { TDialog } from './types';
|
||||
|
||||
export const AccountsContainer = ({ storedAccounts }: { storedAccounts: TAccount[] }) => {
|
||||
const [accounts, setAccounts] = useState(storedAccounts);
|
||||
const [selectedAccount, setSelectedAccount] = useState(accounts[0]);
|
||||
const [accountToEdit, setAccountToEdit] = useState<TAccount>();
|
||||
const [dialogToDisplay, setDialogToDisplay] = useState<TDialog>();
|
||||
|
||||
useEffect(() => {
|
||||
const selected = accounts.find((acc) => acc.address === selectedAccount.address);
|
||||
if (selected) setSelectedAccount(selected);
|
||||
}, [accounts]);
|
||||
|
||||
const addAccount = (account: TAccount) => setAccounts((accs) => [...accs, account]);
|
||||
const editAccount = (account: TAccount) =>
|
||||
setAccounts((accs) => accs.map((acc) => (acc.address === account.address ? account : acc)));
|
||||
const importAccount = (account: TAccount) => setAccounts((accs) => [...accs, account]);
|
||||
|
||||
return (
|
||||
<Accounts
|
||||
accounts={accounts}
|
||||
selectedAccount={selectedAccount}
|
||||
accountToEdit={accountToEdit}
|
||||
dialogToDisplay={dialogToDisplay}
|
||||
addAccount={addAccount}
|
||||
editAccount={editAccount}
|
||||
importAccount={importAccount}
|
||||
setAccountToEdit={setAccountToEdit}
|
||||
setSelectedAccount={setSelectedAccount}
|
||||
setDialogToDisplay={setDialogToDisplay}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,37 +1,68 @@
|
||||
import React from 'react';
|
||||
import { IconButton, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
||||
import { Edit } from '@mui/icons-material';
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, ListItem, ListItemAvatar, ListItemButton, ListItemText, Tooltip, Typography } from '@mui/material';
|
||||
import { useClipboard } from 'use-clipboard-copy';
|
||||
import { AccountsContext } from 'src/context';
|
||||
import { AccountAvatar } from './AccountAvatar';
|
||||
|
||||
export const AccountItem = ({
|
||||
name,
|
||||
address,
|
||||
selected,
|
||||
onSelect,
|
||||
onEdit,
|
||||
}: {
|
||||
name: string;
|
||||
address: string;
|
||||
selected: boolean;
|
||||
onSelect: () => void;
|
||||
onEdit: () => void;
|
||||
}) => (
|
||||
<ListItem disablePadding disableGutters sx={selected ? { bgcolor: 'rgba(33, 208, 115, 0.1)' } : {}}>
|
||||
<ListItemButton disableRipple onClick={onSelect}>
|
||||
<ListItemAvatar sx={{ minWidth: 0, mr: 2 }}>
|
||||
<AccountAvatar name={name} address={address} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={name} secondary={address} />
|
||||
<ListItemIcon>
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit();
|
||||
}}
|
||||
>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
</ListItemIcon>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
export const AccountItem = ({ name, address }: { name: string; address: string }) => {
|
||||
const { selectedAccount, handleSelectAccount, setDialogToDisplay, setAccountMnemonic } = useContext(AccountsContext);
|
||||
const { copy, copied } = useClipboard({ copiedTimeout: 1000 });
|
||||
return (
|
||||
<ListItem
|
||||
disablePadding
|
||||
disableGutters
|
||||
sx={selectedAccount?.id === name ? { bgcolor: 'rgba(33, 208, 115, 0.1)' } : {}}
|
||||
>
|
||||
<ListItemButton disableRipple onClick={() => handleSelectAccount(name)}>
|
||||
<ListItemAvatar sx={{ minWidth: 0, mr: 2 }}>
|
||||
<AccountAvatar name={name} />
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={name}
|
||||
secondary={
|
||||
<Box>
|
||||
<Tooltip title={copied ? 'Copied!' : `Click to copy address ${address}`}>
|
||||
<Typography
|
||||
component="span"
|
||||
variant="body2"
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
copy(address);
|
||||
}}
|
||||
sx={{ '&:hover': { color: 'grey.900' } }}
|
||||
>
|
||||
{address}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Box sx={{ mt: 0.5 }}>
|
||||
<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>
|
||||
}
|
||||
/>
|
||||
{/* edit and remove accounts todo */}
|
||||
{/* <ListItemIcon>
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleAccountToEdit(name);
|
||||
}}
|
||||
>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
</ListItemIcon> */}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@mui/material';
|
||||
import { AccountEntry } from 'src/types';
|
||||
import { AccountAvatar } from './AccountAvatar';
|
||||
|
||||
export const AccountOverview = ({ account, onClick }: { account: AccountEntry; onClick: () => void }) => (
|
||||
<Button
|
||||
startIcon={<AccountAvatar name={account.id} />}
|
||||
sx={{ color: 'nym.text.dark' }}
|
||||
onClick={onClick}
|
||||
disableRipple
|
||||
>
|
||||
{account.id}
|
||||
</Button>
|
||||
);
|
||||
@@ -1,89 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@mui/material';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { TAccount } from 'src/types';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { AccountsContext, AppContext } from 'src/context';
|
||||
import { EditAccountModal } from './EditAccountModal';
|
||||
import { AddAccountModal } from './AddAccountModal';
|
||||
import { AccountsModal } from './AccountsModal';
|
||||
import { ImportAccountModal } from './ImportAccountModal';
|
||||
import { AccountAvatar } from './AccountAvatar';
|
||||
import { TDialog } from './types';
|
||||
import { MnemonicModal } from './MnemonicModal';
|
||||
import { AccountOverview } from './AccountOverview';
|
||||
import { MultiAccountHowTo } from './MultiAccountHowTo';
|
||||
|
||||
export const Accounts = ({
|
||||
accounts,
|
||||
selectedAccount,
|
||||
accountToEdit,
|
||||
dialogToDisplay,
|
||||
addAccount,
|
||||
editAccount,
|
||||
setAccountToEdit,
|
||||
importAccount,
|
||||
setSelectedAccount,
|
||||
setDialogToDisplay,
|
||||
}: {
|
||||
accounts: TAccount[];
|
||||
selectedAccount: TAccount;
|
||||
accountToEdit?: TAccount;
|
||||
dialogToDisplay?: TDialog;
|
||||
addAccount: (acc: TAccount) => void;
|
||||
editAccount: (acc: TAccount) => void;
|
||||
importAccount: (acc: TAccount) => void;
|
||||
setAccountToEdit: (acc: TAccount) => void;
|
||||
setSelectedAccount: (accs: TAccount) => void;
|
||||
setDialogToDisplay: (dialog: TDialog | undefined) => void;
|
||||
}) => (
|
||||
<>
|
||||
<Button
|
||||
startIcon={<AccountAvatar address={selectedAccount.address} name={selectedAccount.name} />}
|
||||
color="inherit"
|
||||
onClick={() => setDialogToDisplay('Accounts')}
|
||||
disableRipple
|
||||
>
|
||||
{selectedAccount.name}
|
||||
</Button>
|
||||
<AccountsModal
|
||||
show={dialogToDisplay === 'Accounts'}
|
||||
onClose={() => setDialogToDisplay(undefined)}
|
||||
accounts={accounts}
|
||||
onAccountSelect={(acc) => setSelectedAccount(acc)}
|
||||
selectedAccount={selectedAccount.address}
|
||||
onAdd={() => {
|
||||
setDialogToDisplay('Add');
|
||||
}}
|
||||
onEdit={(acc) => {
|
||||
setAccountToEdit(acc);
|
||||
setDialogToDisplay('Edit');
|
||||
}}
|
||||
onImport={() => setDialogToDisplay('Import')}
|
||||
/>
|
||||
<AddAccountModal
|
||||
show={dialogToDisplay === 'Add'}
|
||||
onClose={() => {
|
||||
setDialogToDisplay('Accounts');
|
||||
}}
|
||||
onAdd={(name) => {
|
||||
addAccount({ name, address: uuidv4() });
|
||||
setDialogToDisplay('Accounts');
|
||||
}}
|
||||
/>
|
||||
<EditAccountModal
|
||||
show={dialogToDisplay === 'Edit'}
|
||||
account={accountToEdit}
|
||||
onClose={() => {
|
||||
setDialogToDisplay('Accounts');
|
||||
}}
|
||||
onEdit={(account) => {
|
||||
editAccount(account);
|
||||
setDialogToDisplay('Accounts');
|
||||
}}
|
||||
/>
|
||||
<ImportAccountModal
|
||||
show={dialogToDisplay === 'Import'}
|
||||
onClose={() => setDialogToDisplay('Accounts')}
|
||||
onImport={() => {
|
||||
importAccount({ name: 'New Account', address: uuidv4() });
|
||||
setDialogToDisplay('Accounts');
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
export const Accounts = () => {
|
||||
const { accounts, selectedAccount, setDialogToDisplay } = useContext(AccountsContext);
|
||||
|
||||
return accounts && selectedAccount ? (
|
||||
<>
|
||||
<AccountOverview account={selectedAccount} onClick={() => setDialogToDisplay('Accounts')} />
|
||||
<AccountsModal />
|
||||
<AddAccountModal />
|
||||
<EditAccountModal />
|
||||
<MnemonicModal />
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const SingleAccount = () => {
|
||||
const [showHowToDialog, setShowHowToDialog] = useState(false);
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
return (
|
||||
<>
|
||||
<AccountOverview
|
||||
account={{ id: 'Account 1', address: clientDetails?.client_address || '' }}
|
||||
onClick={() => setShowHowToDialog(true)}
|
||||
/>
|
||||
<MultiAccountHowTo show={showHowToDialog} handleClose={() => setShowHowToDialog(false)} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,62 +1,48 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Typography } from '@mui/material';
|
||||
import { Add, ArrowDownwardSharp, Close } from '@mui/icons-material';
|
||||
import { TAccount } from 'src/types';
|
||||
import { AccountsContext } from 'src/context';
|
||||
import { AccountItem } from './AccountItem';
|
||||
|
||||
export const AccountsModal = ({
|
||||
show,
|
||||
accounts,
|
||||
selectedAccount,
|
||||
onClose,
|
||||
onAccountSelect,
|
||||
onAdd,
|
||||
onEdit,
|
||||
onImport,
|
||||
}: {
|
||||
show: boolean;
|
||||
accounts: TAccount[];
|
||||
selectedAccount: TAccount['address'];
|
||||
onClose: () => void;
|
||||
onAccountSelect: (account: TAccount) => void;
|
||||
onAdd: () => void;
|
||||
onEdit: (acc: TAccount) => void;
|
||||
onImport: () => void;
|
||||
}) => (
|
||||
<Dialog open={show} onClose={onClose} fullWidth hideBackdrop>
|
||||
<DialogTitle>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">Accounts</Typography>
|
||||
<IconButton onClick={onClose}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ color: 'grey.600' }}>
|
||||
Switch between accounts
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ padding: 0 }}>
|
||||
{accounts.map(({ name, address }) => (
|
||||
<AccountItem
|
||||
name={name}
|
||||
address={address}
|
||||
onSelect={() => {
|
||||
onAccountSelect({ name, address });
|
||||
onClose();
|
||||
}}
|
||||
onEdit={() => onEdit({ name, address })}
|
||||
selected={selectedAccount === address}
|
||||
key={address}
|
||||
/>
|
||||
))}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3 }}>
|
||||
<Button startIcon={<ArrowDownwardSharp />} onClick={onImport}>
|
||||
Import account
|
||||
</Button>
|
||||
<Button disableElevation variant="contained" startIcon={<Add fontSize="small" />} onClick={onAdd}>
|
||||
Add new account
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
export const AccountsModal = ({ onClose }: { onClose?: () => void }) => {
|
||||
const { accounts, dialogToDisplay, setDialogToDisplay } = useContext(AccountsContext);
|
||||
|
||||
const handleClose = () => {
|
||||
setDialogToDisplay(undefined);
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={dialogToDisplay === 'Accounts'} onClose={handleClose} fullWidth hideBackdrop>
|
||||
<DialogTitle>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">Accounts</Typography>
|
||||
<IconButton onClick={handleClose}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ color: 'grey.600' }}>
|
||||
Switch between accounts
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ padding: 0 }}>
|
||||
{accounts?.map(({ id, address }) => (
|
||||
<AccountItem name={id} address={address} key={address} />
|
||||
))}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3 }}>
|
||||
<Button startIcon={<ArrowDownwardSharp />} onClick={() => setDialogToDisplay('Import')}>
|
||||
Import account
|
||||
</Button>
|
||||
<Button
|
||||
disableElevation
|
||||
variant="contained"
|
||||
startIcon={<Add fontSize="small" />}
|
||||
onClick={() => setDialogToDisplay('Add')}
|
||||
>
|
||||
Add new account
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
@@ -12,75 +12,32 @@ import {
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Check, Close, ContentCopySharp } from '@mui/icons-material';
|
||||
import { ArrowBackSharp } 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 = ['Save and copy mnemonic for your new account', 'Name your new account'];
|
||||
|
||||
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 createAccountSteps = [
|
||||
'Copy and save mnemonic for your new account',
|
||||
'Name your new account',
|
||||
'Confirm the password used to login to your wallet',
|
||||
];
|
||||
const importAccountSteps = [
|
||||
'Provide mnemonic of account you want to import',
|
||||
'Name your new account',
|
||||
'Confirm the password used to login to your wallet',
|
||||
];
|
||||
|
||||
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, onSave }: { mnemonic: string; onSave: () => void }) => {
|
||||
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={onSave}>
|
||||
<Button disabled={!copied} fullWidth disableElevation variant="contained" size="large" onClick={onNext}>
|
||||
I saved my mnemonic
|
||||
</Button>
|
||||
</DialogActions>
|
||||
@@ -88,7 +45,44 @@ const MnemonicStep = ({ mnemonic, onSave }: { mnemonic: string; onSave: () => vo
|
||||
);
|
||||
};
|
||||
|
||||
const NameAccount = ({ onAdd }: { onAdd: (value: string) => void }) => {
|
||||
const ImportMnemonic = ({
|
||||
value,
|
||||
onChange,
|
||||
onNext,
|
||||
}: {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onNext: () => void;
|
||||
}) => (
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} alignItems="center">
|
||||
<TextField
|
||||
multiline
|
||||
rows={3}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
fullWidth
|
||||
type="password"
|
||||
/>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3, pt: 0 }}>
|
||||
<Button
|
||||
disabled={value.length === 0}
|
||||
fullWidth
|
||||
disableElevation
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={onNext}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => {
|
||||
const [value, setValue] = useState('');
|
||||
return (
|
||||
<Box sx={{ mt: 1 }}>
|
||||
@@ -102,61 +96,128 @@ const NameAccount = ({ onAdd }: { onAdd: (value: string) => void }) => {
|
||||
disableElevation
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={() => onAdd(value)}
|
||||
onClick={() => onNext(value)}
|
||||
>
|
||||
Add
|
||||
Next
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const AddAccountModal = ({
|
||||
show,
|
||||
withoutPassword,
|
||||
onClose,
|
||||
onAdd,
|
||||
}: {
|
||||
show: boolean;
|
||||
withoutPassword?: boolean;
|
||||
onClose: () => void;
|
||||
onAdd: (accountName: string) => void;
|
||||
}) => {
|
||||
const [step, setStep] = useState(0);
|
||||
const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void }) => {
|
||||
const [value, setValue] = useState('');
|
||||
const { isLoading, error } = useContext(AccountsContext);
|
||||
|
||||
return (
|
||||
<Dialog open={show} onClose={onClose} fullWidth hideBackdrop>
|
||||
<DialogTitle>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">Add new account</Typography>
|
||||
<IconButton onClick={onClose}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Box>
|
||||
{!withoutPassword && (
|
||||
<Typography variant="body1" sx={{ color: 'grey.600' }}>
|
||||
{`Step ${step + 1}/${createAccountSteps.length}`}
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<DialogContent>
|
||||
{error && (
|
||||
<Typography variant="body1" sx={{ color: 'error.main', mb: 2 }}>
|
||||
{error}
|
||||
</Typography>
|
||||
)}
|
||||
<Typography sx={{ mt: 2 }}>{createAccountSteps[step]}</Typography>
|
||||
<TextField value={value} onChange={(e) => setValue(e.target.value)} fullWidth type="password" />
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3, pt: 0 }}>
|
||||
<Button
|
||||
disabled={!value.length || isLoading}
|
||||
fullWidth
|
||||
disableElevation
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={() => onConfirm(value)}
|
||||
endIcon={isLoading && <CircularProgress size={20} />}
|
||||
>
|
||||
Add account
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const AddAccountModal = () => {
|
||||
const [step, setStep] = useState(0);
|
||||
const [data, setData] = useState({
|
||||
mnemonic: '',
|
||||
accountName: '',
|
||||
});
|
||||
|
||||
const { dialogToDisplay, setDialogToDisplay, handleAddAccount, setError } = useContext(AccountsContext);
|
||||
|
||||
const generateMnemonic = async () => {
|
||||
const mnemon = await createMnemonic();
|
||||
setData((d) => ({ ...d, mnemonic: mnemon }));
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setDialogToDisplay('Accounts');
|
||||
setData({ mnemonic: '', accountName: '' });
|
||||
setStep(0);
|
||||
setError(undefined);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogToDisplay === 'Add') generateMnemonic();
|
||||
}, [dialogToDisplay]);
|
||||
|
||||
useEffect(() => {
|
||||
setError(undefined);
|
||||
}, [step]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={dialogToDisplay === 'Add' || dialogToDisplay === 'Import'}
|
||||
onClose={handleClose}
|
||||
fullWidth
|
||||
hideBackdrop
|
||||
>
|
||||
<DialogTitle sx={{ pb: 0 }}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">{`${dialogToDisplay} new account`}</Typography>
|
||||
<IconButton onClick={() => (step === 0 ? handleClose() : setStep((s) => s - 1))}>
|
||||
<ArrowBackSharp />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Typography sx={{ mt: 2 }}>
|
||||
{dialogToDisplay === 'Add' ? createAccountSteps[step] : importAccountSteps[step]}
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
{withoutPassword && <NoPassword onClose={onClose} />}
|
||||
{!withoutPassword &&
|
||||
(() => {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return (
|
||||
<MnemonicStep
|
||||
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"
|
||||
onSave={() => setStep((s) => s + 1)}
|
||||
/>
|
||||
);
|
||||
case 1:
|
||||
return <NameAccount onAdd={onAdd} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})()}
|
||||
{(() => {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return dialogToDisplay === 'Add' ? (
|
||||
<MnemonicStep mnemonic={data.mnemonic} onNext={() => setStep((s) => s + 1)} />
|
||||
) : (
|
||||
<ImportMnemonic
|
||||
value={data.mnemonic}
|
||||
onChange={(value) => setData((d) => ({ ...d, mnemonic: value }))}
|
||||
onNext={() => setStep((s) => s + 1)}
|
||||
/>
|
||||
);
|
||||
case 1:
|
||||
return (
|
||||
<NameAccount
|
||||
onNext={(accountName) => {
|
||||
setData((d) => ({ ...d, accountName }));
|
||||
setStep((s) => s + 1);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<ConfirmPassword
|
||||
onConfirm={(password) => {
|
||||
if (data.accountName && data.mnemonic) {
|
||||
handleAddAccount({ accountName: data.accountName, mnemonic: data.mnemonic, password });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})()}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -11,31 +11,23 @@ import {
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Close } from '@mui/icons-material';
|
||||
import { TAccount } from 'src/types';
|
||||
import { AccountsContext } from 'src/context';
|
||||
|
||||
export const EditAccountModal = ({
|
||||
account,
|
||||
show,
|
||||
onClose,
|
||||
onEdit,
|
||||
}: {
|
||||
account?: TAccount;
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
onEdit: (account: TAccount) => void;
|
||||
}) => {
|
||||
export const EditAccountModal = () => {
|
||||
const [accountName, setAccountName] = useState('');
|
||||
|
||||
const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount } = useContext(AccountsContext);
|
||||
|
||||
useEffect(() => {
|
||||
setAccountName(account ? account?.name : '');
|
||||
}, [account]);
|
||||
setAccountName(accountToEdit ? accountToEdit?.id : '');
|
||||
}, [accountToEdit]);
|
||||
|
||||
return (
|
||||
<Dialog open={show} onClose={onClose} fullWidth hideBackdrop>
|
||||
<Dialog open={dialogToDisplay === 'Edit'} onClose={() => setDialogToDisplay('Accounts')} fullWidth hideBackdrop>
|
||||
<DialogTitle>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">Edit account name</Typography>
|
||||
<IconButton onClick={onClose}>
|
||||
<IconButton onClick={() => setDialogToDisplay('Accounts')}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Box>
|
||||
@@ -60,7 +52,12 @@ export const EditAccountModal = ({
|
||||
disableElevation
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={() => account && onEdit({ ...account, name: accountName })}
|
||||
onClick={() => {
|
||||
if (accountToEdit) {
|
||||
handleEditAccount({ ...accountToEdit, id: accountName });
|
||||
setDialogToDisplay('Accounts');
|
||||
}
|
||||
}}
|
||||
disabled={!accountName?.length}
|
||||
>
|
||||
Edit
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -11,28 +11,24 @@ import {
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Close } from '@mui/icons-material';
|
||||
import { AccountsContext } from 'src/context';
|
||||
|
||||
export const ImportAccountModal = ({
|
||||
show,
|
||||
onClose,
|
||||
onImport,
|
||||
}: {
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
onImport: (mnemonic: string) => void;
|
||||
}) => {
|
||||
export const ImportAccountModal = () => {
|
||||
const [mnemonic, setMnemonic] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!show) setMnemonic('');
|
||||
}, [show]);
|
||||
const { dialogToDisplay, setDialogToDisplay, handleImportAccount } = useContext(AccountsContext);
|
||||
|
||||
const handleClose = () => {
|
||||
setMnemonic('');
|
||||
setDialogToDisplay('Accounts');
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={show} onClose={onClose} fullWidth hideBackdrop>
|
||||
<Dialog open={dialogToDisplay === 'Import'} onClose={handleClose} fullWidth hideBackdrop>
|
||||
<DialogTitle>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">Import account</Typography>
|
||||
<IconButton onClick={onClose}>
|
||||
<IconButton onClick={handleClose}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Box>
|
||||
@@ -59,7 +55,7 @@ export const ImportAccountModal = ({
|
||||
disableElevation
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={() => onImport(mnemonic)}
|
||||
onClick={() => handleImportAccount({ id: '', address: '' })}
|
||||
disabled={!mnemonic.length}
|
||||
>
|
||||
Import account
|
||||
|
||||
@@ -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 { ArrowBackSharp } 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}>
|
||||
<ArrowBackSharp />
|
||||
</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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Alert, Box, Dialog, DialogContent, DialogTitle, IconButton, Stack, Typography } from '@mui/material';
|
||||
import { Close } from '@mui/icons-material';
|
||||
|
||||
const passwordCreationSteps = [
|
||||
'Log out',
|
||||
'When signing in, select “Sign in with mnemonic”',
|
||||
'On the next screen click “Create a password for your account”',
|
||||
'Sign in to wallet with your new password',
|
||||
'Now you can create multiple accounts',
|
||||
];
|
||||
|
||||
export const MultiAccountHowTo = ({ show, handleClose }: { show: boolean; handleClose: () => void }) => (
|
||||
<Dialog open={show} onClose={handleClose} fullWidth hideBackdrop>
|
||||
<DialogTitle>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">Multi accounts</Typography>
|
||||
<IconButton onClick={handleClose}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ color: 'grey.600' }}>
|
||||
How to set up multiple accounts
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2}>
|
||||
<Alert severity="warning" icon={false}>
|
||||
<Typography>In order to create multiple accounts your wallet need password.</Typography>
|
||||
<Typography>Follow steps below to create password.</Typography>
|
||||
</Alert>
|
||||
<Typography>How to create a password for your account</Typography>
|
||||
{passwordCreationSteps.map((step, index) => (
|
||||
<Typography key={step}>{`${index + 1}. ${step}`}</Typography>
|
||||
))}
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { AccountsProvider, AppContext } from 'src/context';
|
||||
import { Accounts, SingleAccount } from './Accounts';
|
||||
|
||||
export const MultiAccounts = () => {
|
||||
const { loginType } = useContext(AppContext);
|
||||
|
||||
if (loginType === 'password') {
|
||||
return (
|
||||
<AccountsProvider>
|
||||
<Accounts />
|
||||
</AccountsProvider>
|
||||
);
|
||||
}
|
||||
return <SingleAccount />;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
export const accounts = [
|
||||
{
|
||||
id: 'Account 1',
|
||||
address: 'n107wsxkj08hycflnkp5ayfg6rt3pt0psm7w2t9r',
|
||||
},
|
||||
{
|
||||
id: 'Account 2',
|
||||
address: 'n1dgp04lqaasnzaww66zwdp6u24smqe7ltuny8vk',
|
||||
},
|
||||
];
|
||||
@@ -1,25 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
|
||||
import { v4 as uuid4 } from 'uuid';
|
||||
import { AccountsContainer } from '../AccountContainer';
|
||||
import { MockAccountsProvider } from 'src/context/mocks/accounts';
|
||||
import { Accounts } from '../Accounts';
|
||||
|
||||
export default {
|
||||
title: 'Wallet / Multi Account',
|
||||
component: AccountsContainer,
|
||||
} as ComponentMeta<typeof AccountsContainer>;
|
||||
component: Accounts,
|
||||
} as ComponentMeta<typeof Accounts>;
|
||||
|
||||
const Template: ComponentStory<typeof AccountsContainer> = (args) => (
|
||||
export const Default: ComponentStory<typeof Accounts> = () => (
|
||||
<Box display="flex" alignContent="center">
|
||||
<AccountsContainer {...args} />
|
||||
<MockAccountsProvider>
|
||||
<Accounts />
|
||||
</MockAccountsProvider>
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
storedAccounts: [
|
||||
{ name: 'Account 1', address: uuid4() },
|
||||
{ name: 'Account 2', address: uuid4() },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { AddAccountModal } from 'src/components/Accounts/AddAccountModal';
|
||||
|
||||
export default {
|
||||
title: 'Wallet / Multi Account / Add Account',
|
||||
component: AddAccountModal,
|
||||
} as ComponentMeta<typeof AddAccountModal>;
|
||||
|
||||
const Template: ComponentStory<typeof AddAccountModal> = (args) => (
|
||||
<Box display="flex" alignContent="center">
|
||||
<AddAccountModal {...args} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
show: true,
|
||||
onClose: () => {},
|
||||
onAdd: () => {},
|
||||
};
|
||||
|
||||
export const WithoutPassword = Template.bind({});
|
||||
WithoutPassword.args = {
|
||||
show: true,
|
||||
withoutPassword: true,
|
||||
onClose: () => {},
|
||||
onAdd: () => {},
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { EditAccountModal } from 'src/components/Accounts/EditAccountModal';
|
||||
|
||||
export default {
|
||||
title: 'Wallet / Multi Account / Edit Account',
|
||||
component: EditAccountModal,
|
||||
} as ComponentMeta<typeof EditAccountModal>;
|
||||
|
||||
const Template: ComponentStory<typeof EditAccountModal> = (args) => (
|
||||
<Box display="flex" alignContent="center">
|
||||
<EditAccountModal {...args} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
account: { name: 'Account 1', address: uuid() },
|
||||
show: true,
|
||||
onClose: () => {},
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { ImportAccountModal } from 'src/components/Accounts/ImportAccountModal';
|
||||
|
||||
export default {
|
||||
title: 'Wallet / Multi Account / Import Account',
|
||||
component: ImportAccountModal,
|
||||
} as ComponentMeta<typeof ImportAccountModal>;
|
||||
|
||||
const Template: ComponentStory<typeof ImportAccountModal> = (args) => (
|
||||
<Box display="flex" alignContent="center">
|
||||
<ImportAccountModal {...args} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
show: true,
|
||||
onClose: () => {},
|
||||
onImport: () => {},
|
||||
};
|
||||
@@ -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: () => {},
|
||||
};
|
||||
@@ -1,19 +1,26 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Logout } from '@mui/icons-material';
|
||||
import { ClientContext } from '../context/main';
|
||||
import { AppContext } from '../context/main';
|
||||
import { NetworkSelector } from './NetworkSelector';
|
||||
import { Node as NodeIcon } from '../svg-icons/node';
|
||||
import { MultiAccounts } from './Accounts';
|
||||
|
||||
export const AppBar = () => {
|
||||
const { showSettings, logOut, handleShowSettings } = useContext(ClientContext);
|
||||
|
||||
const { showSettings, logOut, handleShowSettings } = useContext(AppContext);
|
||||
const history = useHistory();
|
||||
return (
|
||||
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent' }}>
|
||||
<Toolbar disableGutters>
|
||||
<Grid container justifyContent="space-between" alignItems="center" flexWrap="nowrap">
|
||||
<Grid item>
|
||||
<NetworkSelector />
|
||||
<Grid item container alignItems="center" spacing={1}>
|
||||
<Grid item>
|
||||
<MultiAccounts />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<NetworkSelector />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item container justifyContent="flex-end" md={12} lg={5} spacing={2}>
|
||||
<Grid item>
|
||||
@@ -26,7 +33,14 @@ export const AppBar = () => {
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<IconButton size="small" onClick={logOut} sx={{ color: 'nym.background.dark' }}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={async () => {
|
||||
await logOut();
|
||||
history.push('/');
|
||||
}}
|
||||
sx={{ color: 'nym.background.dark' }}
|
||||
>
|
||||
<Logout fontSize="small" />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useContext } from 'react';
|
||||
import { Box, Typography, Tooltip } from '@mui/material';
|
||||
import { ClientContext } from '../context/main';
|
||||
import { AppContext } from '../context/main';
|
||||
import { CopyToClipboard } from './CopyToClipboard';
|
||||
import { splice } from '../utils';
|
||||
|
||||
@@ -53,6 +53,6 @@ export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string }>
|
||||
);
|
||||
|
||||
export const ClientAddress: FC<ClientAddressProps> = ({ ...props }) => {
|
||||
const { clientDetails } = useContext(ClientContext);
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
return <ClientAddressDisplay {...props} address={clientDetails?.client_address} />;
|
||||
};
|
||||
|
||||
@@ -2,11 +2,11 @@ import React, { useState, useEffect, useContext } from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { Operation } from '../types';
|
||||
import { getGasFee } from '../requests';
|
||||
import { ClientContext } from '../context/main';
|
||||
import { AppContext } from '../context/main';
|
||||
|
||||
export const Fee = ({ feeType }: { feeType: Operation }) => {
|
||||
const [fee, setFee] = useState<string>();
|
||||
const { currency } = useContext(ClientContext);
|
||||
const { currency } = useContext(AppContext);
|
||||
|
||||
const getFee = async () => {
|
||||
const res = await getGasFee(feeType);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Box, LinearProgress, Stack } from '@mui/material';
|
||||
import { NymWordmark } from '@nymproject/react';
|
||||
import { AuthTheme } from 'src/theme';
|
||||
|
||||
export const LoadingPage = () => (
|
||||
<AuthTheme>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
overflow: 'auto',
|
||||
bgcolor: 'nym.background.dark',
|
||||
zIndex: 2000,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: 'auto',
|
||||
}}
|
||||
>
|
||||
<Stack spacing={3} alignItems="center" sx={{ width: 1080 }}>
|
||||
<NymWordmark width={75} fill="white" />
|
||||
<Box width="25%">
|
||||
<LinearProgress variant="indeterminate" color="primary" />
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</AuthTheme>
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
@@ -2,7 +2,7 @@ import React, { useContext, useEffect } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material';
|
||||
import { AccountBalanceWalletOutlined, ArrowBack, ArrowForward, Description, Settings } from '@mui/icons-material';
|
||||
import { ADMIN_ADDRESS, ClientContext } from '../context/main';
|
||||
import { ADMIN_ADDRESS, AppContext } from '../context/main';
|
||||
import { Bond, Delegate, Unbond, Undelegate } from '../svg-icons';
|
||||
|
||||
const routesSchema = [
|
||||
@@ -44,7 +44,7 @@ const routesSchema = [
|
||||
];
|
||||
|
||||
export const Nav = () => {
|
||||
const { clientDetails, handleShowAdmin } = useContext(ClientContext);
|
||||
const { clientDetails, handleShowAdmin } = useContext(AppContext);
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { Button, List, ListItem, ListItemIcon, ListItemText, ListSubheader, Popover } from '@mui/material';
|
||||
import { ArrowDropDown, CheckSharp } from '@mui/icons-material';
|
||||
import { ClientContext, IS_DEV_MODE } from '../context/main';
|
||||
import { AppContext, IS_DEV_MODE } from '../context/main';
|
||||
import { Network } from '../types';
|
||||
|
||||
const networks: { networkName: Network; name: string }[] = [
|
||||
@@ -22,7 +22,7 @@ const NetworkItem: React.FC<{ title: string; isSelected: boolean; onSelect: () =
|
||||
);
|
||||
|
||||
export const NetworkSelector = () => {
|
||||
const { network, switchNetwork } = useContext(ClientContext);
|
||||
const { network, switchNetwork } = useContext(AppContext);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { FormControl, InputLabel, ListItemText, MenuItem, Select, SelectChangeEvent, Typography } from '@mui/material';
|
||||
import { ClientContext } from '../context/main';
|
||||
import { AppContext } from '../context/main';
|
||||
|
||||
type TPoolOption = 'balance' | 'locked';
|
||||
|
||||
@@ -12,7 +12,7 @@ export const TokenPoolSelector: React.FC<{ disabled: boolean; onSelect: (pool: T
|
||||
const {
|
||||
userBalance: { tokenAllocation, balance, fetchBalance, fetchTokenAllocation },
|
||||
currency,
|
||||
} = useContext(ClientContext);
|
||||
} = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
|
||||
@@ -15,3 +15,4 @@ export * from './ClientAddress';
|
||||
export * from './InfoToolTip';
|
||||
export * from './Title';
|
||||
export * from './TokenPoolSelector';
|
||||
export * from './LoadingPage';
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import React, { createContext, Dispatch, SetStateAction, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { AccountEntry } from 'src/types';
|
||||
import { addAccount as addAccountRequest, showMnemonicForAccount } from 'src/requests';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { AppContext } from './main';
|
||||
|
||||
type TAccounts = {
|
||||
accounts?: AccountEntry[];
|
||||
selectedAccount?: AccountEntry;
|
||||
accountToEdit?: AccountEntry;
|
||||
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' | 'Mnemonic';
|
||||
export type TAccountMnemonic = { value?: string; accountName?: string };
|
||||
|
||||
export const AccountsContext = createContext({} as TAccounts);
|
||||
|
||||
export const AccountsProvider: React.FC = ({ children }) => {
|
||||
const [accounts, setAccounts] = useState<AccountEntry[]>([]);
|
||||
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(AppContext);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const handleAddAccount = async ({
|
||||
accountName,
|
||||
mnemonic,
|
||||
password,
|
||||
}: {
|
||||
accountName: string;
|
||||
mnemonic: string;
|
||||
password: string;
|
||||
}) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const newAccount = await addAccountRequest({
|
||||
accountName,
|
||||
mnemonic,
|
||||
password,
|
||||
});
|
||||
setAccounts((accs) => [...accs, newAccount]);
|
||||
enqueueSnackbar('New account created', { variant: 'success' });
|
||||
setDialogToDisplay('Accounts');
|
||||
} catch (e) {
|
||||
setError(`Error adding account: ${e}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
const handleEditAccount = (account: AccountEntry) =>
|
||||
setAccounts((accs) => accs?.map((acc) => (acc.address === account.address ? account : acc)));
|
||||
|
||||
const handleImportAccount = (account: AccountEntry) => setAccounts((accs) => [...(accs ? [...accs] : []), account]);
|
||||
|
||||
const handleAccountToEdit = (accountName: string) =>
|
||||
setAccountToEdit(accounts?.find((acc) => acc.id === accountName));
|
||||
|
||||
const handleSelectAccount = async (accountName: string) => {
|
||||
if (accountName !== selectedAccount?.id) {
|
||||
await onAccountChange(accountName);
|
||||
const match = accounts?.find((acc) => acc.id === accountName);
|
||||
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);
|
||||
}
|
||||
|
||||
if (storedAccounts && !selectedAccount) {
|
||||
setSelectedAccount(storedAccounts[0]);
|
||||
}
|
||||
}, [storedAccounts]);
|
||||
|
||||
return (
|
||||
<AccountsContext.Provider
|
||||
value={useMemo(
|
||||
() => ({
|
||||
error,
|
||||
setError,
|
||||
accounts,
|
||||
selectedAccount,
|
||||
accountToEdit,
|
||||
dialogToDisplay,
|
||||
accountMnemonic,
|
||||
setDialogToDisplay,
|
||||
setAccountMnemonic,
|
||||
isLoading,
|
||||
handleAddAccount,
|
||||
handleEditAccount,
|
||||
handleAccountToEdit,
|
||||
handleSelectAccount,
|
||||
handleImportAccount,
|
||||
handleGetAcccountMnemonic,
|
||||
}),
|
||||
[accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error, accountMnemonic],
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</AccountsContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,10 @@
|
||||
import React, { createContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { createMnemonic } from 'src/requests';
|
||||
import { TMnemonicWords } from 'src/pages/sign-in/types';
|
||||
import { TMnemonicWords } from 'src/pages/auth/types';
|
||||
|
||||
export const SignInContext = createContext({} as TSignInContent);
|
||||
export const AuthContext = createContext({} as TAuthContext);
|
||||
|
||||
export type TSignInContent = {
|
||||
export type TAuthContext = {
|
||||
error?: string;
|
||||
password: string;
|
||||
mnemonic: string;
|
||||
@@ -22,23 +21,17 @@ const mnemonicToArray = (mnemonic: string): TMnemonicWords =>
|
||||
.split(' ')
|
||||
.reduce((a, c: string, index) => [...a, { name: c, index: index + 1, disabled: false }], [] as TMnemonicWords);
|
||||
|
||||
export const SignInProvider: React.FC = ({ children }) => {
|
||||
export const AuthProvider: React.FC = ({ children }) => {
|
||||
const [password, setPassword] = useState('');
|
||||
const [mnemonic, setMnemonic] = useState('');
|
||||
const [mnemonicWords, setMnemonicWords] = useState<TMnemonicWords>([]);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const generateMnemonic = async () => {
|
||||
const mnemonicPhrase = await createMnemonic();
|
||||
setMnemonic(mnemonicPhrase);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
history.push('/welcome');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (mnemonic.length > 0) {
|
||||
const mnemonicArray = mnemonicToArray(mnemonic);
|
||||
@@ -54,7 +47,7 @@ export const SignInProvider: React.FC = ({ children }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SignInContext.Provider
|
||||
<AuthContext.Provider
|
||||
value={useMemo(
|
||||
() => ({
|
||||
error,
|
||||
@@ -71,6 +64,6 @@ export const SignInProvider: React.FC = ({ children }) => {
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SignInContext.Provider>
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './main';
|
||||
export * from './sign-in';
|
||||
export * from './auth';
|
||||
export * from './accounts';
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import React, { useMemo, createContext, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { TLoginType } from 'src/pages/sign-in/types';
|
||||
import { Account, Network, TCurrency, TMixnodeBondDetails } from '../types';
|
||||
import { Account, Network, TCurrency, TMixnodeBondDetails, AccountEntry } from '../types';
|
||||
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
|
||||
import { config } from '../../config';
|
||||
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signInWithPassword, signOut } from '../requests';
|
||||
import {
|
||||
getMixnodeBondDetails,
|
||||
listAccounts,
|
||||
selectNetwork,
|
||||
signInWithMnemonic,
|
||||
signInWithPassword,
|
||||
signOut,
|
||||
switchAccount,
|
||||
} from '../requests';
|
||||
import { currencyMap } from '../utils';
|
||||
import { Console } from '../utils/console';
|
||||
|
||||
@@ -22,9 +29,12 @@ export const urls = (networkName?: Network) =>
|
||||
networkExplorer: `https://${networkName}-explorer.nymtech.net`,
|
||||
};
|
||||
|
||||
type TClientContext = {
|
||||
type TLoginType = 'mnemonic' | 'password';
|
||||
|
||||
type TAppContext = {
|
||||
mode: 'light' | 'dark';
|
||||
clientDetails?: Account;
|
||||
storedAccounts?: AccountEntry[];
|
||||
mixnodeDetails?: TMixnodeBondDetails | null;
|
||||
userBalance: TUseuserBalance;
|
||||
showAdmin: boolean;
|
||||
@@ -33,27 +43,31 @@ type TClientContext = {
|
||||
currency?: TCurrency;
|
||||
isLoading: boolean;
|
||||
error?: string;
|
||||
loginType?: TLoginType;
|
||||
setIsLoading: (isLoading: boolean) => void;
|
||||
setError: (value?: string) => void;
|
||||
switchNetwork: (network: Network) => void;
|
||||
getBondDetails: () => Promise<void>;
|
||||
handleShowSettings: () => void;
|
||||
handleShowAdmin: () => void;
|
||||
logIn: (opts: { type: 'mnemonic' | 'password'; value: string }) => void;
|
||||
logIn: (opts: { type: TLoginType; value: string }) => void;
|
||||
signInWithPassword: (password: string) => void;
|
||||
logOut: () => void;
|
||||
onAccountChange: (accountId: string) => void;
|
||||
};
|
||||
|
||||
export const ClientContext = createContext({} as TClientContext);
|
||||
export const AppContext = createContext({} as TAppContext);
|
||||
|
||||
export const ClientContextProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [clientDetails, setClientDetails] = useState<Account>();
|
||||
const [storedAccounts, setStoredAccounts] = useState<AccountEntry[]>();
|
||||
const [mixnodeDetails, setMixnodeDetails] = useState<TMixnodeBondDetails | null>();
|
||||
const [network, setNetwork] = useState<Network | undefined>();
|
||||
const [currency, setCurrency] = useState<TCurrency>();
|
||||
const [showAdmin, setShowAdmin] = useState(false);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [mode] = useState<'light' | 'dark'>('light');
|
||||
const [loginType, setLoginType] = useState<'mnemonic' | 'password'>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
@@ -61,6 +75,15 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
const history = useHistory();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const clearState = () => {
|
||||
userBalance.clearAll();
|
||||
setStoredAccounts(undefined);
|
||||
setNetwork(undefined);
|
||||
setError(undefined);
|
||||
setIsLoading(false);
|
||||
setMixnodeDetails(undefined);
|
||||
};
|
||||
|
||||
const loadAccount = async (n: Network) => {
|
||||
try {
|
||||
const client = await selectNetwork(n);
|
||||
@@ -73,6 +96,11 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
}
|
||||
};
|
||||
|
||||
const loadStoredAccounts = async () => {
|
||||
const accounts = await listAccounts();
|
||||
setStoredAccounts(accounts);
|
||||
};
|
||||
|
||||
const getBondDetails = async () => {
|
||||
setMixnodeDetails(undefined);
|
||||
try {
|
||||
@@ -83,15 +111,22 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
}
|
||||
};
|
||||
|
||||
const refreshAccount = async (_network: Network) => {
|
||||
await loadAccount(_network);
|
||||
if (loginType === 'password') {
|
||||
await loadStoredAccounts();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const refreshAccount = async () => {
|
||||
if (network) {
|
||||
await loadAccount(network);
|
||||
await getBondDetails();
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
};
|
||||
refreshAccount();
|
||||
if (!clientDetails) {
|
||||
clearState();
|
||||
history.push('/');
|
||||
}
|
||||
}, [clientDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
if (network) refreshAccount(network);
|
||||
}, [network]);
|
||||
|
||||
const logIn = async ({ type, value }: { type: TLoginType; value: string }) => {
|
||||
@@ -103,8 +138,10 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
setIsLoading(true);
|
||||
if (type === 'mnemonic') {
|
||||
await signInWithMnemonic(value);
|
||||
setLoginType('mnemonic');
|
||||
} else {
|
||||
await signInWithPassword(value);
|
||||
setLoginType('password');
|
||||
}
|
||||
setNetwork('MAINNET');
|
||||
history.push('/balance');
|
||||
@@ -116,16 +153,26 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
};
|
||||
|
||||
const logOut = async () => {
|
||||
userBalance.clearAll();
|
||||
setClientDetails(undefined);
|
||||
setNetwork(undefined);
|
||||
setError(undefined);
|
||||
setIsLoading(false);
|
||||
setMixnodeDetails(undefined);
|
||||
await signOut();
|
||||
setClientDetails(undefined);
|
||||
enqueueSnackbar('Successfully logged out', { variant: 'success' });
|
||||
};
|
||||
|
||||
const onAccountChange = async (accountId: string) => {
|
||||
if (network) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await switchAccount(accountId);
|
||||
await loadAccount(network);
|
||||
enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true });
|
||||
} catch (e) {
|
||||
enqueueSnackbar(`Error swtiching account: ${e}`, { variant: 'error' });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleShowAdmin = () => setShowAdmin((show) => !show);
|
||||
const handleShowSettings = () => setShowSettings((show) => !show);
|
||||
const switchNetwork = (_network: Network) => setNetwork(_network);
|
||||
@@ -136,12 +183,14 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
isLoading,
|
||||
error,
|
||||
clientDetails,
|
||||
storedAccounts,
|
||||
mixnodeDetails,
|
||||
userBalance,
|
||||
showAdmin,
|
||||
showSettings,
|
||||
network,
|
||||
currency,
|
||||
loginType,
|
||||
setIsLoading,
|
||||
setError,
|
||||
signInWithPassword,
|
||||
@@ -151,9 +200,23 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
handleShowAdmin,
|
||||
logIn,
|
||||
logOut,
|
||||
onAccountChange,
|
||||
}),
|
||||
[mode, isLoading, error, clientDetails, mixnodeDetails, userBalance, showAdmin, showSettings, network, currency],
|
||||
[
|
||||
loginType,
|
||||
mode,
|
||||
isLoading,
|
||||
error,
|
||||
clientDetails,
|
||||
mixnodeDetails,
|
||||
userBalance,
|
||||
showAdmin,
|
||||
showSettings,
|
||||
network,
|
||||
currency,
|
||||
storedAccounts,
|
||||
],
|
||||
);
|
||||
|
||||
return <ClientContext.Provider value={memoizedValue}>{children}</ClientContext.Provider>;
|
||||
return <AppContext.Provider value={memoizedValue}>{children}</AppContext.Provider>;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { AccountEntry } from 'src/types';
|
||||
import { AccountsContext, TAccountMnemonic, TAccountsDialog } from '../accounts';
|
||||
|
||||
export const MockAccountsProvider: React.FC = ({ children }) => {
|
||||
const [accounts, setAccounts] = useState<AccountEntry[]>([{ id: 'Account_1', address: 'abc123' }]);
|
||||
const [selectedAccount, setSelectedAccount] = useState<AccountEntry | undefined>({
|
||||
id: 'Account_1',
|
||||
address: 'abc123',
|
||||
});
|
||||
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 handleAddAccount = async ({ accountName }: { accountName: string; mnemonic: string; password: string }) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
setAccounts((accs) => [...accs, { address: 'abc123', id: accountName }]);
|
||||
setDialogToDisplay('Accounts');
|
||||
} catch (e) {
|
||||
setError(`Error adding account: ${e}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
const handleEditAccount = (account: AccountEntry) =>
|
||||
setAccounts((accs) => accs?.map((acc) => (acc.address === account.address ? account : acc)));
|
||||
|
||||
const handleImportAccount = (account: AccountEntry) => setAccounts((accs) => [...(accs ? [...accs] : []), account]);
|
||||
|
||||
const handleAccountToEdit = (accountName: string) =>
|
||||
setAccountToEdit(accounts?.find((acc) => acc.id === accountName));
|
||||
|
||||
const handleSelectAccount = async (accountName: string) => {
|
||||
if (accountName !== selectedAccount?.id) {
|
||||
const match = accounts?.find((acc) => acc.id === accountName);
|
||||
setSelectedAccount(match);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGetAcccountMnemonic = async ({ accountName }: { password: string; accountName: string }) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const mnemonic = 'test mnemonic';
|
||||
setAccountMnemonic({ value: mnemonic, accountName });
|
||||
} catch (e) {
|
||||
setError(e as string);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<AccountsContext.Provider
|
||||
value={useMemo(
|
||||
() => ({
|
||||
error,
|
||||
setError,
|
||||
accounts,
|
||||
selectedAccount,
|
||||
accountToEdit,
|
||||
dialogToDisplay,
|
||||
accountMnemonic,
|
||||
setDialogToDisplay,
|
||||
setAccountMnemonic,
|
||||
isLoading,
|
||||
handleAddAccount,
|
||||
handleEditAccount,
|
||||
handleAccountToEdit,
|
||||
handleSelectAccount,
|
||||
handleImportAccount,
|
||||
handleGetAcccountMnemonic,
|
||||
}),
|
||||
[accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error, accountMnemonic],
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</AccountsContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { Console } from '../utils/console';
|
||||
import { ClientContext } from '../context/main';
|
||||
import { AppContext } from '../context/main';
|
||||
import { checkGatewayOwnership, checkMixnodeOwnership, getVestingPledgeInfo } from '../requests';
|
||||
import { EnumNodeType, TNodeOwnership } from '../types';
|
||||
|
||||
@@ -11,7 +11,7 @@ const initial = {
|
||||
};
|
||||
|
||||
export const useCheckOwnership = () => {
|
||||
const { clientDetails } = useContext(ClientContext);
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
|
||||
const [ownership, setOwnership] = useState<TNodeOwnership>(initial);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
@@ -92,9 +92,7 @@ export const useGetBalance = (address?: string): TUseuserBalance => {
|
||||
} catch (err) {
|
||||
setError(err as string);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 1000);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
+22
-41
@@ -1,58 +1,39 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { SnackbarProvider } from 'notistack';
|
||||
import { AppRoutes, SignInRoutes } from './routes';
|
||||
import { ClientContext, ClientContextProvider } from './context/main';
|
||||
import { ApplicationLayout } from './layouts';
|
||||
import { Admin, Settings } from './pages';
|
||||
import { Routes } from './routes';
|
||||
import { AppProvider } from './context/main';
|
||||
import { ErrorFallback } from './components';
|
||||
import { NymWalletTheme, WelcomeTheme } from './theme';
|
||||
import { NymWalletTheme } from './theme';
|
||||
import { maximizeWindow } from './utils';
|
||||
import { SignInProvider } from './context';
|
||||
|
||||
const App = () => {
|
||||
const { clientDetails } = useContext(ClientContext);
|
||||
|
||||
useEffect(() => {
|
||||
maximizeWindow();
|
||||
}, []);
|
||||
|
||||
return !clientDetails ? (
|
||||
<WelcomeTheme>
|
||||
<SignInProvider>
|
||||
<SignInRoutes />
|
||||
</SignInProvider>
|
||||
</WelcomeTheme>
|
||||
) : (
|
||||
<NymWalletTheme>
|
||||
<ApplicationLayout>
|
||||
<Settings />
|
||||
<Admin />
|
||||
<AppRoutes />
|
||||
</ApplicationLayout>
|
||||
</NymWalletTheme>
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<Router>
|
||||
<SnackbarProvider
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
<AppProvider>
|
||||
<NymWalletTheme>
|
||||
<Routes />
|
||||
</NymWalletTheme>
|
||||
</AppProvider>
|
||||
</SnackbarProvider>
|
||||
</Router>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const AppWrapper = () => (
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<Router>
|
||||
<SnackbarProvider
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
<ClientContextProvider>
|
||||
<App />
|
||||
</ClientContextProvider>
|
||||
</SnackbarProvider>
|
||||
</Router>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
const root = document.getElementById('root');
|
||||
|
||||
ReactDOM.render(<AppWrapper />, root);
|
||||
ReactDOM.render(<App />, root);
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { NymWordmark } from '@nymproject/react';
|
||||
import { Box, Container } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { AppBar, Nav } from '../components';
|
||||
import { AppContext } from 'src/context';
|
||||
import { Settings } from 'src/pages';
|
||||
import { AppBar, LoadingPage, Nav } from '../components';
|
||||
|
||||
export const ApplicationLayout: React.FC = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
const { isLoading, showSettings } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '240px auto',
|
||||
gridTemplateRows: '100%',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{isLoading && <LoadingPage />}
|
||||
{showSettings && <Settings />}
|
||||
<Box
|
||||
sx={{
|
||||
background: '#121726',
|
||||
overflow: 'auto',
|
||||
py: 3,
|
||||
px: 5,
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '240px auto',
|
||||
gridTemplateRows: '100%',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Box>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<NymWordmark height={14} fill={theme.palette.background.paper} />
|
||||
<Box
|
||||
sx={{
|
||||
background: '#121726',
|
||||
overflow: 'auto',
|
||||
py: 3,
|
||||
px: 5,
|
||||
}}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Box>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<NymWordmark height={14} fill={theme.palette.background.paper} />
|
||||
</Box>
|
||||
<Nav />
|
||||
</Box>
|
||||
<Nav />
|
||||
</Box>
|
||||
<Container>
|
||||
<AppBar />
|
||||
{children}
|
||||
</Container>
|
||||
</Box>
|
||||
<Container>
|
||||
<AppBar />
|
||||
{children}
|
||||
</Container>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { NymWordmark } from '@nymproject/react';
|
||||
import { Stack, Box } from '@mui/material';
|
||||
import { AppContext } from 'src/context';
|
||||
import { LoadingPage } from 'src/components';
|
||||
import { Step } from '../pages/auth/components/step';
|
||||
|
||||
export const AuthLayout: React.FC = ({ children }) => {
|
||||
const { isLoading } = useContext(AppContext);
|
||||
|
||||
return isLoading ? (
|
||||
<LoadingPage />
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
overflow: 'auto',
|
||||
bgcolor: 'nym.background.dark',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: 'auto',
|
||||
}}
|
||||
>
|
||||
<Stack spacing={3} alignItems="center" sx={{ width: 1080 }}>
|
||||
<NymWordmark width={75} />
|
||||
<Step />
|
||||
{children}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Backdrop, Box, Button, CircularProgress, FormControl, Grid, Paper, Slide, TextField } from '@mui/material';
|
||||
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { NymCard } from '../../components';
|
||||
import { getContractParams, setContractParams } from '../../requests';
|
||||
import { TauriContractStateParams } from '../../types';
|
||||
@@ -99,7 +99,7 @@ const AdminForm: React.FC<{
|
||||
};
|
||||
|
||||
export const Admin: React.FC = () => {
|
||||
const { showAdmin, handleShowAdmin } = useContext(ClientContext);
|
||||
const { showAdmin, handleShowAdmin } = useContext(AppContext);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [params, setParams] = useState<TauriContractStateParams>();
|
||||
|
||||
|
||||
-2
@@ -1,8 +1,6 @@
|
||||
export * from './heading';
|
||||
export * from './word-tiles';
|
||||
export * from './render-page';
|
||||
export * from './password-strength';
|
||||
export * from './error';
|
||||
export * from './textfields';
|
||||
export * from './step';
|
||||
export * from './page-layout';
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import { AuthProvider } from 'src/context';
|
||||
import { AuthRoutes } from 'src/routes/auth';
|
||||
|
||||
export const Auth = () => (
|
||||
<AuthProvider>
|
||||
<AuthRoutes />
|
||||
</AuthProvider>
|
||||
);
|
||||
+2
-2
@@ -2,11 +2,11 @@ import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Button, Stack } from '@mui/material';
|
||||
import { validateMnemonic } from 'src/requests';
|
||||
import { SignInContext } from 'src/context';
|
||||
import { AuthContext } from 'src/context/auth';
|
||||
import { MnemonicInput, Subtitle } from '../components';
|
||||
|
||||
export const ConfirmMnemonic = () => {
|
||||
const { error, setError, setMnemonic, mnemonic } = useContext(SignInContext);
|
||||
const { error, setError, setMnemonic, mnemonic } = useContext(AuthContext);
|
||||
const [localMnemonic, setLocalMnemonic] = useState(mnemonic);
|
||||
const history = useHistory();
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import React, { useContext, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Button, CircularProgress, FormControl, Stack } from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { SignInContext } from 'src/context';
|
||||
import { AuthContext } from 'src/context/auth';
|
||||
import { createPassword } from 'src/requests';
|
||||
import { Subtitle, Title, PasswordStrength } from '../components';
|
||||
import { PasswordInput } from '../components/textfields';
|
||||
@@ -12,7 +12,7 @@ export const ConnectPassword = () => {
|
||||
const [isStrongPassword, setIsStrongPassword] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { mnemonic, password, setPassword, resetState } = useContext(SignInContext);
|
||||
const { mnemonic, password, setPassword, resetState } = useContext(AuthContext);
|
||||
const history = useHistory();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
+2
-2
@@ -1,13 +1,13 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Alert, Button, Stack, Typography } from '@mui/material';
|
||||
import { SignInContext } from 'src/context';
|
||||
import { AuthContext } from 'src/context/auth';
|
||||
import { Check, ContentCopySharp } from '@mui/icons-material';
|
||||
import { useClipboard } from 'use-clipboard-copy';
|
||||
import { WordTiles } from '../components';
|
||||
|
||||
export const CreateMnemonic = () => {
|
||||
const { mnemonic, mnemonicWords, generateMnemonic, resetState } = useContext(SignInContext);
|
||||
const { mnemonic, mnemonicWords, generateMnemonic, resetState } = useContext(AuthContext);
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
+2
-3
@@ -2,17 +2,16 @@ import React, { useContext, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Button, FormControl, Stack } from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { SignInContext } from 'src/context/sign-in';
|
||||
import { AuthContext } from 'src/context/auth';
|
||||
import { createPassword } from 'src/requests';
|
||||
import { PasswordInput, Subtitle, Title, PasswordStrength } from '../components';
|
||||
|
||||
export const CreatePassword = () => {
|
||||
const { password, setPassword, resetState } = useContext(SignInContext);
|
||||
const { password, setPassword, resetState, mnemonic } = useContext(AuthContext);
|
||||
const [confirmedPassword, setConfirmedPassword] = useState<string>('');
|
||||
const [isStrongPassword, setIsStrongPassword] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { mnemonic } = useContext(SignInContext);
|
||||
const history = useHistory();
|
||||
|
||||
const handleSkip = () => {
|
||||
+1
-1
@@ -19,7 +19,7 @@ export const ExistingAccount = () => {
|
||||
Sign in with password
|
||||
</Button>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Button color="inherit" onClick={() => history.push('/welcome')}>
|
||||
<Button color="inherit" onClick={() => history.push('/')}>
|
||||
Back
|
||||
</Button>
|
||||
<Button color="info" onClick={() => history.push('/sign-in-mnemonic')}>
|
||||
+4
-10
@@ -1,14 +1,15 @@
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
import { Box, Button, FormControl, LinearProgress, Stack } from '@mui/material';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Box, Button, FormControl, Stack } from '@mui/material';
|
||||
import { AppContext } from 'src/context';
|
||||
import { isPasswordCreated } from 'src/requests';
|
||||
import { MnemonicInput, Subtitle } from '../components';
|
||||
import { ClientContext } from '../../../context/main';
|
||||
|
||||
export const SignInMnemonic = () => {
|
||||
const [mnemonic, setMnemonic] = useState('');
|
||||
const { setError, logIn, error, isLoading } = useContext(ClientContext);
|
||||
const [passwordExists, setPasswordExists] = useState(true);
|
||||
|
||||
const { setError, logIn, error } = useContext(AppContext);
|
||||
const history = useHistory();
|
||||
|
||||
const checkForPassword = async () => {
|
||||
@@ -25,13 +26,6 @@ export const SignInMnemonic = () => {
|
||||
checkForPassword();
|
||||
}, []);
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<Box width="25%">
|
||||
<LinearProgress variant="indeterminate" />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack spacing={2} alignItems="center" minWidth="50%">
|
||||
<Subtitle subtitle="Enter a mnemonic to sign in" />
|
||||
+3
-10
@@ -1,21 +1,14 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Box, Button, LinearProgress, FormControl, Stack } from '@mui/material';
|
||||
import { Box, Button, FormControl, Stack } from '@mui/material';
|
||||
import { PasswordInput, Subtitle } from '../components';
|
||||
import { ClientContext } from '../../../context/main';
|
||||
import { AppContext } from '../../../context/main';
|
||||
|
||||
export const SignInPassword = () => {
|
||||
const [password, setPassword] = useState('');
|
||||
const { setError, logIn, error, isLoading } = useContext(ClientContext);
|
||||
const { setError, logIn, error } = useContext(AppContext);
|
||||
const history = useHistory();
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<Box width="25%">
|
||||
<LinearProgress variant="indeterminate" />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack spacing={2} alignItems="center" minWidth="50%">
|
||||
<Subtitle subtitle="Enter a password to sign in" />
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Button, Stack } from '@mui/material';
|
||||
import { SignInContext } from 'src/context/sign-in';
|
||||
import { AuthContext } from 'src/context/auth';
|
||||
import { randomNumberBetween } from 'src/utils';
|
||||
import { HiddenWords, Subtitle, Title, WordTiles } from '../components';
|
||||
import { THiddenMnemonicWord, THiddenMnemonicWords, TMnemonicWord, TMnemonicWords } from '../types';
|
||||
@@ -13,7 +13,7 @@ export const VerifyMnemonic = () => {
|
||||
const [hiddenRandomWords, setHiddenRandomWords] = useState<THiddenMnemonicWords>();
|
||||
const [currentSelection, setCurrentSelection] = useState(0);
|
||||
|
||||
const { mnemonicWords } = useContext(SignInContext);
|
||||
const { mnemonicWords } = useContext(AuthContext);
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -19,5 +19,3 @@ export type TMnemonicWords = TMnemonicWord[];
|
||||
export type THiddenMnemonicWord = { hidden: boolean } & TMnemonicWord;
|
||||
|
||||
export type THiddenMnemonicWords = THiddenMnemonicWord[];
|
||||
|
||||
export type TLoginType = 'mnemonic' | 'password';
|
||||
@@ -2,10 +2,10 @@ import React, { useContext, useEffect } from 'react';
|
||||
import { Alert, Button, Grid, Link, Typography } from '@mui/material';
|
||||
import { OpenInNew } from '@mui/icons-material';
|
||||
import { NymCard, ClientAddress } from '../../components';
|
||||
import { ClientContext, urls } from '../../context/main';
|
||||
import { AppContext, urls } from '../../context/main';
|
||||
|
||||
export const BalanceCard = () => {
|
||||
const { userBalance, clientDetails, network } = useContext(ClientContext);
|
||||
const { userBalance, clientDetails, network } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
userBalance.fetchBalance();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, Tooltip, Typography } from '@mui/material';
|
||||
import { format } from 'date-fns';
|
||||
import { ClientContext } from '../../../context/main';
|
||||
import { AppContext } from '../../../context/main';
|
||||
|
||||
const calculateMarkerPosition = (arrLength: number, index: number) => (1 / arrLength) * 100 * index;
|
||||
|
||||
@@ -19,7 +19,7 @@ const Marker: React.FC<{ tooltipText: string; color: string; position: string }>
|
||||
export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ percentageComplete }) => {
|
||||
const {
|
||||
userBalance: { currentVestingPeriod, vestingAccountInfo },
|
||||
} = useContext(ClientContext);
|
||||
} = useContext(AppContext);
|
||||
|
||||
const nextPeriod =
|
||||
typeof currentVestingPeriod === 'object' && !!vestingAccountInfo?.periods
|
||||
|
||||
@@ -2,11 +2,11 @@ import React, { useContext, useEffect } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { BalanceCard } from './balance';
|
||||
import { VestingCard } from './vesting';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { PageLayout } from '../../layouts';
|
||||
|
||||
export const Balance = () => {
|
||||
const { userBalance } = useContext(ClientContext);
|
||||
const { userBalance } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
userBalance.fetchBalance();
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { InfoOutlined, Refresh } from '@mui/icons-material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { Fee, InfoTooltip, NymCard, Title } from '../../components';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { withdrawVestedCoins } from '../../requests';
|
||||
import { Period } from '../../types';
|
||||
import { VestingTimeline } from './components/vesting-timeline';
|
||||
@@ -38,7 +38,7 @@ const vestingPeriod = (current?: Period, original?: number) => {
|
||||
};
|
||||
|
||||
const VestingSchedule = () => {
|
||||
const { userBalance, currency } = useContext(ClientContext);
|
||||
const { userBalance, currency } = useContext(AppContext);
|
||||
const [vestedPercentage, setVestedPercentage] = useState(0);
|
||||
|
||||
const calculatePercentage = () => {
|
||||
@@ -93,7 +93,7 @@ const VestingSchedule = () => {
|
||||
};
|
||||
|
||||
const TokenTransfer = () => {
|
||||
const { userBalance, currency } = useContext(ClientContext);
|
||||
const { userBalance, currency } = useContext(AppContext);
|
||||
const icon = useCallback(
|
||||
() => (
|
||||
<Box sx={{ display: 'flex', mr: 1 }}>
|
||||
@@ -123,7 +123,7 @@ const TokenTransfer = () => {
|
||||
export const VestingCard = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { userBalance } = useContext(ClientContext);
|
||||
const { userBalance } = useContext(AppContext);
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
||||
|
||||
const refreshBalances = async () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { NodeTypeSelector } from '../../components/NodeTypeSelector';
|
||||
import { bond, vestingBond, majorToMinor } from '../../requests';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { Gateway, MixNode, TBondArgs, EnumNodeType } from '../../types';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { Fee, TokenPoolSelector } from '../../components';
|
||||
|
||||
type TBondFormFields = {
|
||||
@@ -96,7 +96,7 @@ export const BondForm = ({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const { userBalance, currency, clientDetails } = useContext(ClientContext);
|
||||
const { userBalance, currency, clientDetails } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
reset();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { SuccessReponse, TransactionDetails } from '../../components';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
|
||||
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
|
||||
const { userBalance, currency } = useContext(ClientContext);
|
||||
const { userBalance, currency } = useContext(AppContext);
|
||||
const { ownership } = useCheckOwnership();
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { DelegationResult, EnumNodeType, TDelegateArgs } from '../../types';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { delegate, majorToMinor, vestingDelegateToMixnode } from '../../requests';
|
||||
import { Fee, TokenPoolSelector } from '../../components';
|
||||
import { Console } from '../../utils/console';
|
||||
@@ -41,7 +41,7 @@ export const DelegateForm = ({
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const { userBalance, currency, clientDetails } = useContext(ClientContext);
|
||||
const { userBalance, currency, clientDetails } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
reset();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, Stack, Typography } from '@mui/material';
|
||||
import { SuccessReponse, TransactionDetails } from '../../components';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
|
||||
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
|
||||
const { userBalance, currency } = useContext(ClientContext);
|
||||
const { userBalance, currency } = useContext(AppContext);
|
||||
return (
|
||||
<>
|
||||
<SuccessReponse
|
||||
|
||||
@@ -4,7 +4,7 @@ import { DelegateForm } from './DelegateForm';
|
||||
import { NymCard } from '../../components';
|
||||
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus';
|
||||
import { SuccessView } from './SuccessView';
|
||||
import { urls, ClientContext } from '../../context/main';
|
||||
import { urls, AppContext } from '../../context/main';
|
||||
import { PageLayout } from '../../layouts';
|
||||
|
||||
export const Delegate = () => {
|
||||
@@ -12,7 +12,7 @@ export const Delegate = () => {
|
||||
const [error, setError] = useState<string>();
|
||||
const [successDetails, setSuccessDetails] = useState<{ amount: string; address: string }>();
|
||||
|
||||
const { network } = useContext(ClientContext);
|
||||
const { network } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
|
||||
@@ -5,7 +5,7 @@ export * from './delegate';
|
||||
export * from './internal-docs';
|
||||
export * from './receive';
|
||||
export * from './send';
|
||||
export * from './sign-in';
|
||||
export * from './auth';
|
||||
export * from './settings';
|
||||
export * from './unbond';
|
||||
export * from './undelegate';
|
||||
|
||||
@@ -2,10 +2,10 @@ import React, { useContext } from 'react';
|
||||
import { NymCard } from '../../components';
|
||||
import { ApiList } from './ApiList';
|
||||
|
||||
import { ADMIN_ADDRESS, ClientContext } from '../../context/main';
|
||||
import { ADMIN_ADDRESS, AppContext } from '../../context/main';
|
||||
|
||||
export const InternalDocs = () => {
|
||||
const { clientDetails } = useContext(ClientContext);
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
if (clientDetails?.client_address === ADMIN_ADDRESS) {
|
||||
return (
|
||||
<NymCard title="Docs" subheader="Internal API docs">
|
||||
|
||||
@@ -2,11 +2,11 @@ import React, { useContext } from 'react';
|
||||
import QRCode from 'qrcode.react';
|
||||
import { Alert, Box, Stack } from '@mui/material';
|
||||
import { ClientAddress, NymCard } from '../../components';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { PageLayout } from '../../layouts';
|
||||
|
||||
export const Receive = () => {
|
||||
const { clientDetails, currency } = useContext(ClientContext);
|
||||
const { clientDetails, currency } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, CircularProgress, Link, Typography } from '@mui/material';
|
||||
import { SendError } from './SendError';
|
||||
import { ClientContext, urls } from '../../context/main';
|
||||
import { AppContext, urls } from '../../context/main';
|
||||
import { SuccessReponse } from '../../components';
|
||||
import { TransactionDetails } from '../../components/TransactionDetails';
|
||||
import { TransactionDetails as TTransactionDetails } from '../../types';
|
||||
@@ -15,7 +15,7 @@ export const SendConfirmation = ({
|
||||
error?: string;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const { userBalance, currency, network } = useContext(ClientContext);
|
||||
const { userBalance, currency, network } = useContext(AppContext);
|
||||
|
||||
if (!data && !error && !isLoading) return null;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Grid, InputAdornment, TextField } from '@mui/material';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { Fee } from '../../components';
|
||||
|
||||
export const SendForm = () => {
|
||||
@@ -9,7 +9,7 @@ export const SendForm = () => {
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
const { currency } = useContext(ClientContext);
|
||||
const { currency } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Card, Divider, Grid, Typography } from '@mui/material';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
|
||||
const SendReviewField = ({ title, subtitle, info }: { title: string; subtitle?: string; info?: boolean }) => (
|
||||
<>
|
||||
@@ -14,7 +14,7 @@ const SendReviewField = ({ title, subtitle, info }: { title: string; subtitle?:
|
||||
|
||||
export const SendReview = ({ transferFee }: { transferFee?: string }) => {
|
||||
const { getValues } = useFormContext();
|
||||
const { clientDetails, currency } = useContext(ClientContext);
|
||||
const { clientDetails, currency } = useContext(AppContext);
|
||||
|
||||
const values = getValues();
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Box, Button, Step, StepLabel, Stepper } from '@mui/material';
|
||||
import { SendForm } from './SendForm';
|
||||
import { SendReview } from './SendReview';
|
||||
import { SendConfirmation } from './SendConfirmation';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { TauriTxResult, TransactionDetails } from '../../types';
|
||||
import { getGasFee, majorToMinor, send } from '../../requests';
|
||||
@@ -32,7 +32,7 @@ export const SendWizard = () => {
|
||||
const [transferFee, setTransferFee] = useState<string>();
|
||||
const [confirmedData, setConfirmedData] = useState<TransactionDetails & { tx_hash: string }>();
|
||||
|
||||
const { userBalance } = useContext(ClientContext);
|
||||
const { userBalance } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
const getFee = async () => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { NymCard } from '../../components';
|
||||
import { SendWizard } from './SendWizard';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { PageLayout } from '../../layouts';
|
||||
|
||||
export const Send = () => {
|
||||
const { currency } = useContext(ClientContext);
|
||||
const { currency } = useContext(AppContext);
|
||||
return (
|
||||
<PageLayout>
|
||||
<NymCard title={`Send ${currency?.major}`} noPadding>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Alert, Box, Dialog } from '@mui/material';
|
||||
import { NymCard } from '../../components';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { Tabs } from './tabs';
|
||||
import { Profile } from './profile';
|
||||
import { SystemVariables } from './system-variables';
|
||||
@@ -15,7 +15,7 @@ const tabs = ['Profile', 'System variables', 'Node stats'];
|
||||
export const Settings = () => {
|
||||
const [selectedTab, setSelectedTab] = useState(0);
|
||||
|
||||
const { mixnodeDetails, showSettings, getBondDetails, handleShowSettings } = useContext(ClientContext);
|
||||
const { mixnodeDetails, showSettings, getBondDetails, handleShowSettings } = useContext(AppContext);
|
||||
const { status, saturation, rewardEstimation, inclusionProbability } = useSettingsState(showSettings);
|
||||
|
||||
const handleTabChange = (_: React.SyntheticEvent, newTab: number) => setSelectedTab(newTab);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { OpenInNew } from '@mui/icons-material';
|
||||
import { Button, Link, Stack, Typography } from '@mui/material';
|
||||
import { urls, ClientContext } from '../../context/main';
|
||||
import { urls, AppContext } from '../../context/main';
|
||||
|
||||
export const NodeStats = ({ mixnodeId }: { mixnodeId?: string }) => {
|
||||
const { network } = useContext(ClientContext);
|
||||
const { network } = useContext(AppContext);
|
||||
return (
|
||||
<Stack spacing={2} sx={{ p: 4 }}>
|
||||
<Typography>All your node stats are available on the link below</Typography>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, Button, Divider, Stack, TextField, Typography } from '@mui/material';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
|
||||
export const Profile = () => {
|
||||
const { mixnodeDetails } = useContext(ClientContext);
|
||||
const { mixnodeDetails } = useContext(AppContext);
|
||||
|
||||
if (!mixnodeDetails) return null;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Fee, InfoTooltip } from '../../components';
|
||||
import { InclusionProbabilityResponse } from '../../types';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
import { updateMixnode, vestingUpdateMixnode } from '../../requests';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { Console } from '../../utils/console';
|
||||
|
||||
const DataField = ({ title, info, Indicator }: { title: string; info: string; Indicator: React.ReactElement }) => (
|
||||
@@ -57,7 +57,7 @@ export const SystemVariables = ({
|
||||
inclusionProbability: InclusionProbabilityResponse;
|
||||
}) => {
|
||||
const [nodeUpdateResponse, setNodeUpdateResponse] = useState<'success' | 'failed'>();
|
||||
const { currency, mixnodeDetails } = useContext(ClientContext);
|
||||
const { currency, mixnodeDetails } = useContext(AppContext);
|
||||
const { ownership } = useCheckOwnership();
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import {
|
||||
getMixnodeRewardEstimation,
|
||||
getMixnodeStakeSaturation,
|
||||
@@ -18,7 +18,7 @@ export const useSettingsState = (shouldUpdate: boolean) => {
|
||||
in_reserve: 0,
|
||||
});
|
||||
|
||||
const { mixnodeDetails } = useContext(ClientContext);
|
||||
const { mixnodeDetails } = useContext(AppContext);
|
||||
|
||||
const getStatus = async (mixnodeKey: string) => {
|
||||
const newStatus = await getMixnodeStatus(mixnodeKey);
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Stack, Box } from '@mui/material';
|
||||
import { NymWordmark } from '@nymproject/react';
|
||||
import { Step } from './step';
|
||||
|
||||
export const PageLayout: React.FC = ({ children }) => (
|
||||
<Box
|
||||
sx={{
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
overflow: 'auto',
|
||||
bgcolor: 'nym.background.dark',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: 'auto',
|
||||
}}
|
||||
>
|
||||
<Stack spacing={3} alignItems="center" sx={{ width: 1080 }}>
|
||||
<NymWordmark width={75} />
|
||||
<Step />
|
||||
{children}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
import { TPages } from '../types';
|
||||
|
||||
export const RenderPage = ({ children, page }: { children: React.ReactElement[]; page: TPages }) => (
|
||||
<>
|
||||
{React.Children.map(children, (Child) => {
|
||||
if (page === Child?.props.page) return Child;
|
||||
return null;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
@@ -1 +0,0 @@
|
||||
export * from './pages';
|
||||
@@ -3,14 +3,14 @@ import { Alert, Box, Button, CircularProgress } from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { Fee, NymCard } from '../../components';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { unbond, vestingUnbond } from '../../requests';
|
||||
import { PageLayout } from '../../layouts';
|
||||
|
||||
export const Unbond = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { checkOwnership, ownership } = useCheckOwnership();
|
||||
const { userBalance, getBondDetails } = useContext(ClientContext);
|
||||
const { userBalance, getBondDetails } = useContext(AppContext);
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { EnumRequestStatus, NymCard, RequestStatus } from '../../components';
|
||||
import { UndelegateForm } from './UndelegateForm';
|
||||
import { getCurrentEpoch, getPendingDelegations, getReverseMixDelegations } from '../../requests';
|
||||
import { Epoch, PendingUndelegate, TPagedDelegations } from '../../types';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { PageLayout } from '../../layouts';
|
||||
import { removeObjectDuplicates } from '../../utils';
|
||||
|
||||
@@ -16,7 +16,7 @@ export const Undelegate = () => {
|
||||
const [pendingUndelegations, setPendingUndelegations] = useState<PendingUndelegate[]>();
|
||||
const [currentEndEpoch, setCurrentEndEpoch] = useState<Epoch['end']>();
|
||||
|
||||
const { clientDetails } = useContext(ClientContext);
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
|
||||
const initialize = async () => {
|
||||
setStatus(EnumRequestStatus.initial);
|
||||
|
||||
@@ -1,42 +1,67 @@
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { AccountEntry } from 'src/types/rust/accountentry';
|
||||
import { Account } from '../types';
|
||||
|
||||
export const createMnemonic = async (): Promise<string> => invoke('create_new_mnemonic');
|
||||
export const createMnemonic = async () => {
|
||||
const res: string = await invoke('create_new_mnemonic');
|
||||
return res;
|
||||
};
|
||||
|
||||
export const createPassword = async ({ mnemonic, password }: { mnemonic: string; password: string }): Promise<void> => {
|
||||
export const createPassword = async ({ mnemonic, password }: { mnemonic: string; password: string }) => {
|
||||
await invoke('create_password', { mnemonic, password });
|
||||
};
|
||||
|
||||
export const signInWithMnemonic = async (mnemonic: string): Promise<Account> => {
|
||||
export const signInWithMnemonic = async (mnemonic: string) => {
|
||||
const res: Account = await invoke('connect_with_mnemonic', { mnemonic });
|
||||
return res;
|
||||
};
|
||||
|
||||
export const validateMnemonic = async (mnemonic: string): Promise<boolean> => {
|
||||
const res: boolean = await invoke('validate_mnemonic', { mnemonic });
|
||||
return res;
|
||||
};
|
||||
|
||||
export const signInWithPassword = async (password: string): Promise<Account> => {
|
||||
export const signInWithPassword = async (password: string) => {
|
||||
const res: Account = await invoke('sign_in_with_password', { password });
|
||||
return res;
|
||||
};
|
||||
|
||||
export const signOut = async (): Promise<void> => {
|
||||
export const validateMnemonic = async (mnemonic: string) => {
|
||||
const res: boolean = await invoke('validate_mnemonic', { mnemonic });
|
||||
return res;
|
||||
};
|
||||
|
||||
export const signOut = async () => {
|
||||
await invoke('logout');
|
||||
};
|
||||
|
||||
export const isPasswordCreated = async (): Promise<boolean> => {
|
||||
export const isPasswordCreated = async () => {
|
||||
const res: boolean = await invoke('does_password_file_exist');
|
||||
return res;
|
||||
};
|
||||
|
||||
export const createNewAccount = async (mnemonic: string): Promise<Account> => {
|
||||
const res: Account = await invoke('create_new_account', { mnemonic });
|
||||
export const addAccount = async ({
|
||||
mnemonic,
|
||||
password,
|
||||
accountName,
|
||||
}: {
|
||||
mnemonic: string;
|
||||
password: string;
|
||||
accountName: string;
|
||||
}) => {
|
||||
const res: AccountEntry = await invoke('add_account_for_password', { mnemonic, password, innerId: accountName });
|
||||
return res;
|
||||
};
|
||||
|
||||
export const listAccounts = async (password: string) => {
|
||||
const res: Account[] = await invoke('list_accounts_for_password', { password });
|
||||
export const removeAccount = async ({ password, accountName }: { password: string; accountName: string }) => {
|
||||
await invoke('remove_account_for_password', { password, innerId: accountName });
|
||||
};
|
||||
|
||||
export const listAccounts = async () => {
|
||||
const res: AccountEntry[] = await invoke('list_accounts');
|
||||
return res;
|
||||
};
|
||||
|
||||
export const showMnemonicForAccount = async ({ password, accountName }: { password: string; accountName: string }) => {
|
||||
const res: string = await invoke('show_mnemonic_for_account_in_password', { password, accountId: accountName });
|
||||
return res;
|
||||
};
|
||||
|
||||
export const switchAccount = async (accountId: string) => {
|
||||
await invoke('sign_in_decrypted_account', { accountId });
|
||||
};
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { ApplicationLayout } from 'src/layouts';
|
||||
import { Bond, Balance, Delegate, InternalDocs, Receive, Send, Unbond, Undelegate } from '../pages';
|
||||
|
||||
export const AppRoutes = () => (
|
||||
<Switch>
|
||||
<Route path="/balance">
|
||||
<Balance />
|
||||
</Route>
|
||||
<Route path="/send">
|
||||
<Send />
|
||||
</Route>
|
||||
<Route path="/receive">
|
||||
<Receive />
|
||||
</Route>
|
||||
<Route path="/bond">
|
||||
<Bond />
|
||||
</Route>
|
||||
<Route path="/unbond">
|
||||
<Unbond />
|
||||
</Route>
|
||||
<Route path="/delegate">
|
||||
<Delegate />
|
||||
</Route>
|
||||
<Route path="/undelegate">
|
||||
<Undelegate />
|
||||
</Route>
|
||||
<Route path="/docs">
|
||||
<InternalDocs />
|
||||
</Route>
|
||||
</Switch>
|
||||
<ApplicationLayout>
|
||||
<Switch>
|
||||
<Route path="/balance">
|
||||
<Balance />
|
||||
</Route>
|
||||
<Route path="/send">
|
||||
<Send />
|
||||
</Route>
|
||||
<Route path="/receive">
|
||||
<Receive />
|
||||
</Route>
|
||||
<Route path="/bond">
|
||||
<Bond />
|
||||
</Route>
|
||||
<Route path="/unbond">
|
||||
<Unbond />
|
||||
</Route>
|
||||
<Route path="/delegate">
|
||||
<Delegate />
|
||||
</Route>
|
||||
<Route path="/undelegate">
|
||||
<Undelegate />
|
||||
</Route>
|
||||
<Route path="/docs">
|
||||
<InternalDocs />
|
||||
</Route>
|
||||
</Switch>
|
||||
</ApplicationLayout>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { AuthProvider } from 'src/context';
|
||||
import { AuthLayout } from 'src/layouts/AuthLayout';
|
||||
import {
|
||||
CreateMnemonic,
|
||||
CreatePassword,
|
||||
ExistingAccount,
|
||||
SignInMnemonic,
|
||||
SignInPassword,
|
||||
VerifyMnemonic,
|
||||
WelcomeContent,
|
||||
ConnectPassword,
|
||||
} from 'src/pages/auth/pages';
|
||||
import { ConfirmMnemonic } from 'src/pages/auth/pages/confirm-mnemonic';
|
||||
import { AuthTheme } from 'src/theme';
|
||||
|
||||
export const AuthRoutes = () => (
|
||||
<AuthProvider>
|
||||
<AuthTheme>
|
||||
<AuthLayout>
|
||||
<Switch>
|
||||
<Route path="/" exact>
|
||||
<WelcomeContent />
|
||||
</Route>
|
||||
<Route path="/existing-account">
|
||||
<ExistingAccount />
|
||||
</Route>
|
||||
<Route path="/create-mnemonic">
|
||||
<CreateMnemonic />
|
||||
</Route>
|
||||
<Route path="/verify-mnemonic">
|
||||
<VerifyMnemonic />
|
||||
</Route>
|
||||
<Route path="/create-password">
|
||||
<CreatePassword />
|
||||
</Route>
|
||||
<Route path="/sign-in-mnemonic">
|
||||
<SignInMnemonic />
|
||||
</Route>
|
||||
<Route path="/sign-in-password">
|
||||
<SignInPassword />
|
||||
</Route>
|
||||
<Route path="/confirm-mnemonic">
|
||||
<ConfirmMnemonic />
|
||||
</Route>
|
||||
<Route path="/connect-password">
|
||||
<ConnectPassword />
|
||||
</Route>
|
||||
</Switch>
|
||||
</AuthLayout>
|
||||
</AuthTheme>
|
||||
</AuthProvider>
|
||||
);
|
||||
@@ -1,2 +1,10 @@
|
||||
export * from './app';
|
||||
export * from './sign-in';
|
||||
import React, { useContext } from 'react';
|
||||
import { AppContext } from 'src/context';
|
||||
import { AppRoutes } from './app';
|
||||
import { AuthRoutes } from './auth';
|
||||
|
||||
export const Routes = () => {
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
if (!clientDetails) return <AuthRoutes />;
|
||||
return <AppRoutes />;
|
||||
};
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { PageLayout } from 'src/pages/sign-in/components';
|
||||
import {
|
||||
CreateMnemonic,
|
||||
CreatePassword,
|
||||
ExistingAccount,
|
||||
SignInMnemonic,
|
||||
SignInPassword,
|
||||
VerifyMnemonic,
|
||||
WelcomeContent,
|
||||
ConnectPassword,
|
||||
} from 'src/pages/sign-in/pages';
|
||||
import { ConfirmMnemonic } from 'src/pages/sign-in/pages/confirm-mnemonic';
|
||||
|
||||
export const SignInRoutes = () => (
|
||||
<PageLayout>
|
||||
<Switch>
|
||||
<Route path="/welcome">
|
||||
<WelcomeContent />
|
||||
</Route>
|
||||
<Route path="/existing-account">
|
||||
<ExistingAccount />
|
||||
</Route>
|
||||
<Route path="/create-mnemonic">
|
||||
<CreateMnemonic />
|
||||
</Route>
|
||||
<Route path="/verify-mnemonic">
|
||||
<VerifyMnemonic />
|
||||
</Route>
|
||||
<Route path="/create-password">
|
||||
<CreatePassword />
|
||||
</Route>
|
||||
<Route path="/sign-in-mnemonic">
|
||||
<SignInMnemonic />
|
||||
</Route>
|
||||
<Route path="/sign-in-password">
|
||||
<SignInPassword />
|
||||
</Route>
|
||||
<Route path="/confirm-mnemonic">
|
||||
<ConfirmMnemonic />
|
||||
</Route>
|
||||
<Route path="/connect-password">
|
||||
<ConnectPassword />
|
||||
</Route>
|
||||
</Switch>
|
||||
</PageLayout>
|
||||
);
|
||||
@@ -2,7 +2,7 @@ import React, { useContext } from 'react';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import { getDesignTokens } from './theme';
|
||||
import { ClientContext } from '../context/main';
|
||||
import { AppContext } from '../context/main';
|
||||
import { NymWalletThemeWithMode } from './NymWalletTheme';
|
||||
|
||||
/**
|
||||
@@ -10,11 +10,11 @@ import { NymWalletThemeWithMode } from './NymWalletTheme';
|
||||
*/
|
||||
|
||||
export const NymWalletTheme: React.FC = ({ children }) => {
|
||||
const { mode } = useContext(ClientContext);
|
||||
const { mode } = useContext(AppContext);
|
||||
return <NymWalletThemeWithMode mode={mode}>{children}</NymWalletThemeWithMode>;
|
||||
};
|
||||
|
||||
export const WelcomeTheme: React.FC = ({ children }) => {
|
||||
export const AuthTheme: React.FC = ({ children }) => {
|
||||
const theme = createTheme(getDesignTokens('dark'));
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
|
||||
@@ -78,4 +78,5 @@ export type Period = 'Before' | { In: number } | 'After';
|
||||
export type TAccount = {
|
||||
name: string;
|
||||
address: string;
|
||||
mnemonic: string;
|
||||
};
|
||||
|
||||
@@ -23,3 +23,4 @@ export * from './vestingperiod';
|
||||
export * from './pendingundelegate';
|
||||
export * from './delegationevent';
|
||||
export * from './epoch';
|
||||
export * from './accountentry';
|
||||
|
||||
@@ -13,7 +13,8 @@ export const CopyToClipboard: React.FC<{
|
||||
}> = ({ value, tooltip, onCopy, sx }) => {
|
||||
const copy = useClipboard();
|
||||
const [showConfirmation, setShowConfirmation] = React.useState<boolean>(false);
|
||||
const handleCopy = () => {
|
||||
const handleCopy = (e: React.MouseEvent<SVGSVGElement>) => {
|
||||
e.stopPropagation();
|
||||
setShowConfirmation(true);
|
||||
copy.copy(value);
|
||||
if (onCopy) {
|
||||
|
||||
Reference in New Issue
Block a user