From be9b83a87d277d4c904f59f4ddf9d57a25efa4b4 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 27 Apr 2022 11:19:19 +0100 Subject: [PATCH 01/24] set up account switching mechanism --- nym-wallet/src/components/LoadingPage.tsx | 32 +++++++++++++++++++ nym-wallet/src/context/main.tsx | 25 +++++++++++---- nym-wallet/src/index.tsx | 17 +++++----- .../pages/sign-in/pages/signin-mnemonic.tsx | 11 ++----- .../pages/sign-in/pages/signin-password.tsx | 11 ++----- 5 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 nym-wallet/src/components/LoadingPage.tsx diff --git a/nym-wallet/src/components/LoadingPage.tsx b/nym-wallet/src/components/LoadingPage.tsx new file mode 100644 index 0000000000..f34e74f17b --- /dev/null +++ b/nym-wallet/src/components/LoadingPage.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Box, LinearProgress, Stack } from '@mui/material'; +import { NymWordmark } from '@nymproject/react'; +export const LoadingPage = () => ( + + + + + + + + + + +); diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index dc6091c8de..f8cbc5ab6e 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -42,6 +42,7 @@ type TClientContext = { logIn: (opts: { type: 'mnemonic' | 'password'; value: string }) => void; signInWithPassword: (password: string) => void; logOut: () => void; + onAccountChange: (mnemonic: string) => void; }; export const ClientContext = createContext({} as TClientContext); @@ -61,6 +62,15 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode const history = useHistory(); const { enqueueSnackbar } = useSnackbar(); + const clearState = () => { + userBalance.clearAll(); + setClientDetails(undefined); + setNetwork(undefined); + setError(undefined); + setIsLoading(false); + setMixnodeDetails(undefined); + }; + const loadAccount = async (n: Network) => { try { const client = await selectNetwork(n); @@ -116,16 +126,18 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode }; const logOut = async () => { - userBalance.clearAll(); - setClientDetails(undefined); - setNetwork(undefined); - setError(undefined); - setIsLoading(false); - setMixnodeDetails(undefined); + clearState(); await signOut(); enqueueSnackbar('Successfully logged out', { variant: 'success' }); }; + const onAccountChange = async (value: string) => { + clearState(); + await signOut(); + await logIn({ type: 'mnemonic', value }); + enqueueSnackbar('Account switch success', { variant: 'success' }); + }; + const handleShowAdmin = () => setShowAdmin((show) => !show); const handleShowSettings = () => setShowSettings((show) => !show); const switchNetwork = (_network: Network) => setNetwork(_network); @@ -151,6 +163,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode handleShowAdmin, logIn, logOut, + onAccountChange, }), [mode, isLoading, error, clientDetails, mixnodeDetails, userBalance, showAdmin, showSettings, network, currency], ); diff --git a/nym-wallet/src/index.tsx b/nym-wallet/src/index.tsx index e16fbcdab0..2ff0e2c071 100644 --- a/nym-wallet/src/index.tsx +++ b/nym-wallet/src/index.tsx @@ -11,21 +11,22 @@ import { ErrorFallback } from './components'; import { NymWalletTheme, WelcomeTheme } from './theme'; import { maximizeWindow } from './utils'; import { SignInProvider } from './context'; +import { LoadingPage } from './components/LoadingPage'; const App = () => { - const { clientDetails } = useContext(ClientContext); + const { clientDetails, isLoading } = useContext(ClientContext); useEffect(() => { maximizeWindow(); }, []); - return !clientDetails ? ( - - - - - - ) : ( + if (!clientDetails) + return ( + + {!isLoading ? : } + + ); + return ( diff --git a/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx b/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx index 491895ad96..4118b7c9d8 100644 --- a/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx +++ b/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx @@ -1,13 +1,13 @@ 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 { 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 { setError, logIn, error } = useContext(ClientContext); const [passwordExists, setPasswordExists] = useState(true); const history = useHistory(); @@ -25,13 +25,6 @@ export const SignInMnemonic = () => { checkForPassword(); }, []); - if (isLoading) - return ( - - - - ); - return ( diff --git a/nym-wallet/src/pages/sign-in/pages/signin-password.tsx b/nym-wallet/src/pages/sign-in/pages/signin-password.tsx index 64f3a83677..aed85dcff0 100644 --- a/nym-wallet/src/pages/sign-in/pages/signin-password.tsx +++ b/nym-wallet/src/pages/sign-in/pages/signin-password.tsx @@ -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'; export const SignInPassword = () => { const [password, setPassword] = useState(''); - const { setError, logIn, error, isLoading } = useContext(ClientContext); + const { setError, logIn, error } = useContext(ClientContext); const history = useHistory(); - if (isLoading) - return ( - - - - ); - return ( From 1b945ae918bc33756bf629cb688566b407a76d32 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 27 Apr 2022 13:43:39 +0100 Subject: [PATCH 02/24] use account switch function --- .../Accounts/stories/Accounts.stories.tsx | 13 +++++++------ nym-wallet/src/components/AppBar.tsx | 10 ++++++++-- nym-wallet/src/context/main.tsx | 2 +- nym-wallet/src/requests/account.ts | 13 ++++++++++--- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx index 4b8b87ee4d..680b14f96d 100644 --- a/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx @@ -3,23 +3,24 @@ import { Box } from '@mui/material'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { v4 as uuid4 } from 'uuid'; -import { AccountsContainer } from '../AccountContainer'; +import { Accounts } from '../Accounts'; export default { title: 'Wallet / Multi Account', - component: AccountsContainer, -} as ComponentMeta; + component: Accounts, +} as ComponentMeta; -const Template: ComponentStory = (args) => ( +const Template: ComponentStory = (args) => ( - + ); export const Default = Template.bind({}); Default.args = { - storedAccounts: [ + accounts: [ { name: 'Account 1', address: uuid4() }, { name: 'Account 2', address: uuid4() }, ], + selectedAccount: { name: 'Account 1', address: uuid4() }, }; diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index b5ee0ab55d..6d7c2d3820 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -4,6 +4,7 @@ import { Logout } from '@mui/icons-material'; import { ClientContext } from '../context/main'; import { NetworkSelector } from './NetworkSelector'; import { Node as NodeIcon } from '../svg-icons/node'; +import { AccountsContainer } from './Accounts/AccountContainer'; export const AppBar = () => { const { showSettings, logOut, handleShowSettings } = useContext(ClientContext); @@ -12,8 +13,13 @@ export const AppBar = () => { - - + + + + + + + diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index f8cbc5ab6e..0a53ff3e95 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -135,7 +135,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode clearState(); await signOut(); await logIn({ type: 'mnemonic', value }); - enqueueSnackbar('Account switch success', { variant: 'success' }); + enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true }); }; const handleShowAdmin = () => setShowAdmin((show) => !show); diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index b9223581e8..6fcf7c6628 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -31,9 +31,16 @@ export const isPasswordCreated = async (): Promise => { return res; }; -export const createNewAccount = async (mnemonic: string): Promise => { - const res: Account = await invoke('create_new_account', { mnemonic }); - return res; +export const addAccount = async ({ + mnemonic, + password, + accountName, +}: { + mnemonic: string; + password: string; + accountName: string; +}): Promise => { + await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); }; export const listAccounts = async (password: string) => { From 66b9b13edcfa214e784321ee119e61b64ecf1354 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 29 Apr 2022 10:05:18 +0100 Subject: [PATCH 03/24] refactor and add more mocks --- .../src/components/Accounts/AccountAvatar.tsx | 2 +- .../components/Accounts/AccountContainer.tsx | 18 ++- .../src/components/Accounts/AccountItem.tsx | 22 +++- .../src/components/Accounts/Accounts.tsx | 121 +++++++++--------- .../src/components/Accounts/AccountsModal.tsx | 8 +- .../components/Accounts/AddAccountModal.tsx | 2 +- .../src/components/Accounts/ShowMnemonic.tsx | 41 ++++++ nym-wallet/src/components/Accounts/mocks.ts | 14 ++ .../Accounts/stories/Accounts.stories.tsx | 10 +- .../Accounts/stories/EditAccount.stories.tsx | 2 +- nym-wallet/src/components/AppBar.tsx | 10 +- nym-wallet/src/context/main.tsx | 19 ++- nym-wallet/src/types/global.ts | 1 + 13 files changed, 184 insertions(+), 86 deletions(-) create mode 100644 nym-wallet/src/components/Accounts/ShowMnemonic.tsx create mode 100644 nym-wallet/src/components/Accounts/mocks.ts diff --git a/nym-wallet/src/components/Accounts/AccountAvatar.tsx b/nym-wallet/src/components/Accounts/AccountAvatar.tsx index fb2ba5da4b..0854917e0a 100644 --- a/nym-wallet/src/components/Accounts/AccountAvatar.tsx +++ b/nym-wallet/src/components/Accounts/AccountAvatar.tsx @@ -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) => ( +export const AccountAvatar = ({ name, address }: Pick) => ( {name.split('')[0]} ); diff --git a/nym-wallet/src/components/Accounts/AccountContainer.tsx b/nym-wallet/src/components/Accounts/AccountContainer.tsx index eb61715f20..c00230099c 100644 --- a/nym-wallet/src/components/Accounts/AccountContainer.tsx +++ b/nym-wallet/src/components/Accounts/AccountContainer.tsx @@ -4,20 +4,26 @@ 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 [accounts, setAccounts] = useState(storedAccounts); + const [selectedAccount, setSelectedAccount] = useState(storedAccounts[0]); const [accountToEdit, setAccountToEdit] = useState(); const [dialogToDisplay, setDialogToDisplay] = useState(); useEffect(() => { - const selected = accounts.find((acc) => acc.address === selectedAccount.address); + 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))); + setAccounts((accs) => accs?.map((acc) => (acc.address === account.address ? account : acc))); const importAccount = (account: TAccount) => setAccounts((accs) => [...accs, account]); + const handleAccountToEdit = (accountName: string) => + setAccountToEdit(accounts.find((acc) => acc.name === accountName)); + const handleSelectedAccount = (accountName: string) => { + const match = accounts.find((acc) => acc.name === accountName); + if (match) setSelectedAccount(match); + }; return ( ); diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index b8dd61279c..d76b6c5916 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -1,7 +1,17 @@ import React from 'react'; -import { IconButton, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; +import { + Box, + IconButton, + ListItem, + ListItemAvatar, + ListItemButton, + ListItemIcon, + ListItemText, + Typography, +} from '@mui/material'; import { Edit } from '@mui/icons-material'; import { AccountAvatar } from './AccountAvatar'; +import { ShowMnemonic } from './ShowMnemonic'; export const AccountItem = ({ name, @@ -21,7 +31,15 @@ export const AccountItem = ({ - + + {address} + + + } + /> { diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index 194ec48b51..665022c6c2 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Button } from '@mui/material'; import { v4 as uuidv4 } from 'uuid'; import { TAccount } from 'src/types'; +import { createMnemonic } from 'src/requests'; import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; import { AccountsModal } from './AccountsModal'; @@ -21,69 +22,71 @@ export const Accounts = ({ setSelectedAccount, setDialogToDisplay, }: { - accounts: TAccount[]; + 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; + setAccountToEdit: (accountName: string) => void; + setSelectedAccount: (accountName: string) => void; setDialogToDisplay: (dialog: TDialog | undefined) => void; -}) => ( - <> - - setDialogToDisplay(undefined)} - accounts={accounts} - onAccountSelect={(acc) => setSelectedAccount(acc)} - selectedAccount={selectedAccount.address} - onAdd={() => { - setDialogToDisplay('Add'); - }} - onEdit={(acc) => { - setAccountToEdit(acc); - setDialogToDisplay('Edit'); - }} - onImport={() => setDialogToDisplay('Import')} - /> - { - setDialogToDisplay('Accounts'); - }} - onAdd={(name) => { - addAccount({ name, address: uuidv4() }); - setDialogToDisplay('Accounts'); - }} - /> - { - setDialogToDisplay('Accounts'); - }} - onEdit={(account) => { - editAccount(account); - setDialogToDisplay('Accounts'); - }} - /> - setDialogToDisplay('Accounts')} - onImport={() => { - importAccount({ name: 'New Account', address: uuidv4() }); - setDialogToDisplay('Accounts'); - }} - /> - -); +}) => + accounts ? ( + <> + + setDialogToDisplay(undefined)} + accounts={accounts} + onAccountSelect={(accountName) => setSelectedAccount(accountName)} + selectedAccount={selectedAccount.address} + onAdd={() => { + setDialogToDisplay('Add'); + }} + onEdit={(accountName) => { + setAccountToEdit(accountName); + setDialogToDisplay('Edit'); + }} + onImport={() => setDialogToDisplay('Import')} + /> + { + setDialogToDisplay('Accounts'); + }} + onAdd={async (name) => { + const mnemonic = await createMnemonic(); + addAccount({ name, address: uuidv4(), mnemonic }); + setDialogToDisplay('Accounts'); + }} + /> + { + setDialogToDisplay('Accounts'); + }} + onEdit={(account) => { + editAccount(account); + setDialogToDisplay('Accounts'); + }} + /> + setDialogToDisplay('Accounts')} + onImport={(mnemonic) => { + importAccount({ name: 'New Account', address: uuidv4(), mnemonic }); + setDialogToDisplay('Accounts'); + }} + /> + + ) : null; diff --git a/nym-wallet/src/components/Accounts/AccountsModal.tsx b/nym-wallet/src/components/Accounts/AccountsModal.tsx index 4b4c9205aa..9127ebfba6 100644 --- a/nym-wallet/src/components/Accounts/AccountsModal.tsx +++ b/nym-wallet/src/components/Accounts/AccountsModal.tsx @@ -18,9 +18,9 @@ export const AccountsModal = ({ accounts: TAccount[]; selectedAccount: TAccount['address']; onClose: () => void; - onAccountSelect: (account: TAccount) => void; + onAccountSelect: (accountName: string) => void; onAdd: () => void; - onEdit: (acc: TAccount) => void; + onEdit: (accoutnName: string) => void; onImport: () => void; }) => ( @@ -41,10 +41,10 @@ export const AccountsModal = ({ name={name} address={address} onSelect={() => { - onAccountSelect({ name, address }); + onAccountSelect(name); onClose(); }} - onEdit={() => onEdit({ name, address })} + onEdit={() => onEdit(name)} selected={selectedAccount === address} key={address} /> diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 60d1963618..70f9b8d454 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { Alert, Box, diff --git a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx new file mode 100644 index 0000000000..f193333430 --- /dev/null +++ b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx @@ -0,0 +1,41 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Typography } from '@mui/material'; +import { accounts } from './mocks'; + +const fetchMnemonic = (accountName: string): Promise => + new Promise((res) => { + const account = accounts.find((acc) => acc.name === accountName); + if (account) setTimeout(() => res(account.mnemonic), 0); + else res('n/a'); + }); + +export const ShowMnemonic = ({ accountName }: { accountName: string }) => { + const [showMnemonic, setShowMnemonic] = useState(); + const [mnemonic, setMnemonic] = useState(); + + useEffect(() => { + const getMnemonic = async () => { + const mnic = await fetchMnemonic(accountName); + setMnemonic(mnic); + }; + + if (showMnemonic) getMnemonic(); + else setMnemonic(undefined); + }, [showMnemonic]); + + return ( + + { + e.stopPropagation(); + setShowMnemonic((show) => (!show ? accountName : undefined)); + }} + > + {`${showMnemonic ? 'Hide' : 'Show'} mnemonic`} + + {mnemonic && {mnemonic}} + + ); +}; diff --git a/nym-wallet/src/components/Accounts/mocks.ts b/nym-wallet/src/components/Accounts/mocks.ts new file mode 100644 index 0000000000..154af2e816 --- /dev/null +++ b/nym-wallet/src/components/Accounts/mocks.ts @@ -0,0 +1,14 @@ +export const accounts = [ + { + name: 'Account 1', + address: 'n107wsxkj08hycflnkp5ayfg6rt3pt0psm7w2t9r', + mnemonic: + 'ski shield hurry home trust sample napkin silver buzz peasant ability unlock chair lazy degree metal verify glimpse whisper primary youth cart pave peace', + }, + { + name: 'Account 2', + address: 'n1dgp04lqaasnzaww66zwdp6u24smqe7ltuny8vk', + mnemonic: + 'fire olympic connect title case across border route food this brief used cause ghost panic profit rather ritual nothing crystal fury into kangaroo rich', + }, +]; diff --git a/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx index 680b14f96d..c6a7ae19a8 100644 --- a/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/Accounts.stories.tsx @@ -1,9 +1,8 @@ import React from 'react'; import { Box } from '@mui/material'; import { ComponentMeta, ComponentStory } from '@storybook/react'; - -import { v4 as uuid4 } from 'uuid'; import { Accounts } from '../Accounts'; +import { accounts } from '../mocks'; export default { title: 'Wallet / Multi Account', @@ -18,9 +17,6 @@ const Template: ComponentStory = (args) => ( export const Default = Template.bind({}); Default.args = { - accounts: [ - { name: 'Account 1', address: uuid4() }, - { name: 'Account 2', address: uuid4() }, - ], - selectedAccount: { name: 'Account 1', address: uuid4() }, + accounts, + selectedAccount: accounts[0], }; diff --git a/nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx b/nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx index a75139e3c0..0430ac2950 100644 --- a/nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/EditAccount.stories.tsx @@ -17,7 +17,7 @@ const Template: ComponentStory = (args) => ( export const Default = Template.bind({}); Default.args = { - account: { name: 'Account 1', address: uuid() }, + account: { name: 'Account 1', address: uuid(), mnemonic: 'abc123' }, show: true, onClose: () => {}, }; diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index 6d7c2d3820..ffd583ec0a 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -7,16 +7,18 @@ import { Node as NodeIcon } from '../svg-icons/node'; import { AccountsContainer } from './Accounts/AccountContainer'; export const AppBar = () => { - const { showSettings, logOut, handleShowSettings } = useContext(ClientContext); + const { showSettings, storedAccounts, logOut, handleShowSettings } = useContext(ClientContext); return ( - - - + {storedAccounts && ( + + + + )} diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 0a53ff3e95..213717faf4 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -2,7 +2,8 @@ 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 { accounts } from 'src/components/Accounts/mocks'; +import { Account, Network, TAccount, TCurrency, TMixnodeBondDetails } from '../types'; import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance'; import { config } from '../../config'; import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signInWithPassword, signOut } from '../requests'; @@ -22,9 +23,15 @@ export const urls = (networkName?: Network) => networkExplorer: `https://${networkName}-explorer.nymtech.net`, }; +const getAccounts = (): Promise => + new Promise((res) => { + setTimeout(() => res(accounts), 3000); + }); + type TClientContext = { mode: 'light' | 'dark'; clientDetails?: Account; + storedAccounts?: TAccount[]; mixnodeDetails?: TMixnodeBondDetails | null; userBalance: TUseuserBalance; showAdmin: boolean; @@ -49,6 +56,7 @@ export const ClientContext = createContext({} as TClientContext); export const ClientContextProvider = ({ children }: { children: React.ReactNode }) => { const [clientDetails, setClientDetails] = useState(); + const [storedAccounts, setStoredAccounts] = useState(); const [mixnodeDetails, setMixnodeDetails] = useState(); const [network, setNetwork] = useState(); const [currency, setCurrency] = useState(); @@ -104,6 +112,14 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode refreshAccount(); }, [network]); + useEffect(() => { + const fetchAccounts = async () => { + const accs = await getAccounts(); + setStoredAccounts(accs); + }; + fetchAccounts(); + }, []); + const logIn = async ({ type, value }: { type: TLoginType; value: string }) => { if (value.length === 0) { setError(`A ${type} must be provided`); @@ -148,6 +164,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode isLoading, error, clientDetails, + storedAccounts, mixnodeDetails, userBalance, showAdmin, diff --git a/nym-wallet/src/types/global.ts b/nym-wallet/src/types/global.ts index 758cd2095f..3f2abc8569 100644 --- a/nym-wallet/src/types/global.ts +++ b/nym-wallet/src/types/global.ts @@ -78,4 +78,5 @@ export type Period = 'Before' | { In: number } | 'After'; export type TAccount = { name: string; address: string; + mnemonic: string; }; From 31c4fc68076d8bf865d282d6c575987f6be8af1c Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 29 Apr 2022 10:45:23 +0100 Subject: [PATCH 04/24] update display mnemonic component --- nym-wallet/src/components/Accounts/AccountItem.tsx | 4 +++- nym-wallet/src/components/Accounts/ShowMnemonic.tsx | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index d76b6c5916..a019c76d57 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -36,7 +36,9 @@ export const AccountItem = ({ secondary={ {address} - + + + } /> diff --git a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx index f193333430..d107e7c83b 100644 --- a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx +++ b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Box, Typography } from '@mui/material'; import { accounts } from './mocks'; +import { CopyToClipboard } from '@nymproject/react'; const fetchMnemonic = (accountName: string): Promise => new Promise((res) => { @@ -27,7 +28,7 @@ export const ShowMnemonic = ({ accountName }: { accountName: string }) => { { e.stopPropagation(); setShowMnemonic((show) => (!show ? accountName : undefined)); @@ -35,7 +36,12 @@ export const ShowMnemonic = ({ accountName }: { accountName: string }) => { > {`${showMnemonic ? 'Hide' : 'Show'} mnemonic`} - {mnemonic && {mnemonic}} + {mnemonic && ( + + {mnemonic} + + + )} ); }; From 8bbffb6a88d2941275e4b6b430d813f4ad66bf03 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 29 Apr 2022 10:45:43 +0100 Subject: [PATCH 05/24] prevent bubbling --- .../src/components/clipboard/CopyToClipboard.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx b/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx index 344241e566..2bcc6852fc 100644 --- a/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx +++ b/ts-packages/react-components/src/components/clipboard/CopyToClipboard.tsx @@ -13,7 +13,8 @@ export const CopyToClipboard: React.FC<{ }> = ({ value, tooltip, onCopy, sx }) => { const copy = useClipboard(); const [showConfirmation, setShowConfirmation] = React.useState(false); - const handleCopy = () => { + const handleCopy = (e: React.MouseEvent) => { + e.stopPropagation(); setShowConfirmation(true); copy.copy(value); if (onCopy) { From 580656c00284c9715df898238cf6b989cec5702d Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 29 Apr 2022 14:12:47 +0100 Subject: [PATCH 06/24] refactor + wip --- nym-wallet/src-tauri/src/main.rs | 2 +- .../components/Accounts/AccountContainer.tsx | 38 ++++++--- .../src/components/Accounts/AccountItem.tsx | 6 +- .../src/components/Accounts/Accounts.tsx | 28 +++---- .../src/components/Accounts/AccountsModal.tsx | 16 ++-- .../components/Accounts/AddAccountModal.tsx | 80 ++++++++++++++++--- .../components/Accounts/EditAccountModal.tsx | 10 +-- .../src/components/Accounts/ShowMnemonic.tsx | 18 ----- nym-wallet/src/components/Accounts/mocks.ts | 8 +- .../Accounts/stories/EditAccount.stories.tsx | 2 +- nym-wallet/src/context/main.tsx | 23 +++--- nym-wallet/src/requests/account.ts | 5 +- nym-wallet/src/types/rust/index.ts | 1 + 13 files changed, 142 insertions(+), 95 deletions(-) diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index d5c7d9ccf8..eab7759a91 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -43,7 +43,7 @@ fn main() { mixnet::account::get_balance, mixnet::account::get_validator_api_urls, mixnet::account::get_validator_nymd_urls, - mixnet::account::list_accounts_for_password, + mixnet::account::list_accounts, mixnet::account::sign_in_decrypted_account, mixnet::account::logout, mixnet::account::remove_account_for_password, diff --git a/nym-wallet/src/components/Accounts/AccountContainer.tsx b/nym-wallet/src/components/Accounts/AccountContainer.tsx index c00230099c..0a4f8b95e4 100644 --- a/nym-wallet/src/components/Accounts/AccountContainer.tsx +++ b/nym-wallet/src/components/Accounts/AccountContainer.tsx @@ -1,27 +1,41 @@ import React, { useEffect, useState } from 'react'; -import { TAccount } from 'src/types'; +import { AccountEntry } from 'src/types'; +import { addAccount as addAccountRequest } from 'src/requests'; import { Accounts } from './Accounts'; import { TDialog } from './types'; -export const AccountsContainer = ({ storedAccounts }: { storedAccounts: TAccount[] }) => { - const [accounts, setAccounts] = useState(storedAccounts); - const [selectedAccount, setSelectedAccount] = useState(storedAccounts[0]); - const [accountToEdit, setAccountToEdit] = useState(); +export const AccountsContainer = ({ storedAccounts }: { storedAccounts: AccountEntry[] }) => { + const [accounts, setAccounts] = useState(storedAccounts); + const [selectedAccount, setSelectedAccount] = useState(storedAccounts[0]); + const [accountToEdit, setAccountToEdit] = useState(); const [dialogToDisplay, setDialogToDisplay] = useState(); useEffect(() => { const selected = accounts?.find((acc) => acc.address === selectedAccount?.address); if (selected) setSelectedAccount(selected); - }, [accounts]); + }, [accounts, storedAccounts]); - const addAccount = (account: TAccount) => setAccounts((accs) => [...accs, account]); - const editAccount = (account: TAccount) => + const addAccount = async ({ + accountName, + mnemonic, + password, + }: { + accountName: string; + mnemonic: string; + password: string; + }) => { + await addAccountRequest({ + accountName, + mnemonic, + password, + }); + }; + const editAccount = (account: AccountEntry) => setAccounts((accs) => accs?.map((acc) => (acc.address === account.address ? account : acc))); - const importAccount = (account: TAccount) => setAccounts((accs) => [...accs, account]); - const handleAccountToEdit = (accountName: string) => - setAccountToEdit(accounts.find((acc) => acc.name === accountName)); + const importAccount = (account: AccountEntry) => setAccounts((accs) => [...accs, account]); + const handleAccountToEdit = (accountName: string) => setAccountToEdit(accounts.find((acc) => acc.id === accountName)); const handleSelectedAccount = (accountName: string) => { - const match = accounts.find((acc) => acc.name === accountName); + const match = accounts.find((acc) => acc.id === accountName); if (match) setSelectedAccount(match); }; diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index a019c76d57..a1fdb2f1cd 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -16,17 +16,17 @@ import { ShowMnemonic } from './ShowMnemonic'; export const AccountItem = ({ name, address, - selected, + isSelected, onSelect, onEdit, }: { name: string; address: string; - selected: boolean; + isSelected: boolean; onSelect: () => void; onEdit: () => void; }) => ( - + diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index 665022c6c2..e0fd917b64 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -1,8 +1,7 @@ import React from 'react'; import { Button } from '@mui/material'; import { v4 as uuidv4 } from 'uuid'; -import { TAccount } from 'src/types'; -import { createMnemonic } from 'src/requests'; +import { AccountEntry } from 'src/types'; import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; import { AccountsModal } from './AccountsModal'; @@ -22,13 +21,13 @@ export const Accounts = ({ setSelectedAccount, setDialogToDisplay, }: { - accounts?: TAccount[]; - selectedAccount: TAccount; - accountToEdit?: TAccount; + accounts?: AccountEntry[]; + selectedAccount: AccountEntry; + accountToEdit?: AccountEntry; dialogToDisplay?: TDialog; - addAccount: (acc: TAccount) => void; - editAccount: (acc: TAccount) => void; - importAccount: (acc: TAccount) => void; + addAccount: (acc: { accountName: string; mnemonic: string; password: string }) => Promise; + editAccount: (acc: AccountEntry) => void; + importAccount: (acc: AccountEntry & { mnemonic: string }) => void; setAccountToEdit: (accountName: string) => void; setSelectedAccount: (accountName: string) => void; setDialogToDisplay: (dialog: TDialog | undefined) => void; @@ -36,19 +35,19 @@ export const Accounts = ({ accounts ? ( <> setDialogToDisplay(undefined)} accounts={accounts} onAccountSelect={(accountName) => setSelectedAccount(accountName)} - selectedAccount={selectedAccount.address} + selectedAccount={selectedAccount.id} onAdd={() => { setDialogToDisplay('Add'); }} @@ -63,9 +62,8 @@ export const Accounts = ({ onClose={() => { setDialogToDisplay('Accounts'); }} - onAdd={async (name) => { - const mnemonic = await createMnemonic(); - addAccount({ name, address: uuidv4(), mnemonic }); + onAdd={async (data) => { + addAccount(data); setDialogToDisplay('Accounts'); }} /> @@ -84,7 +82,7 @@ export const Accounts = ({ show={dialogToDisplay === 'Import'} onClose={() => setDialogToDisplay('Accounts')} onImport={(mnemonic) => { - importAccount({ name: 'New Account', address: uuidv4(), mnemonic }); + importAccount({ id: 'New Account', address: uuidv4(), mnemonic }); setDialogToDisplay('Accounts'); }} /> diff --git a/nym-wallet/src/components/Accounts/AccountsModal.tsx b/nym-wallet/src/components/Accounts/AccountsModal.tsx index 9127ebfba6..bc2de38243 100644 --- a/nym-wallet/src/components/Accounts/AccountsModal.tsx +++ b/nym-wallet/src/components/Accounts/AccountsModal.tsx @@ -1,7 +1,7 @@ import React 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 { AccountEntry } from 'src/types'; import { AccountItem } from './AccountItem'; export const AccountsModal = ({ @@ -15,8 +15,8 @@ export const AccountsModal = ({ onImport, }: { show: boolean; - accounts: TAccount[]; - selectedAccount: TAccount['address']; + accounts: AccountEntry[]; + selectedAccount: AccountEntry['id']; onClose: () => void; onAccountSelect: (accountName: string) => void; onAdd: () => void; @@ -36,16 +36,16 @@ export const AccountsModal = ({ - {accounts.map(({ name, address }) => ( + {accounts.map(({ id, address }) => ( { - onAccountSelect(name); + onAccountSelect(id); onClose(); }} - onEdit={() => onEdit(name)} - selected={selectedAccount === address} + onEdit={() => onEdit(id)} + isSelected={selectedAccount === id} key={address} /> ))} diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 70f9b8d454..6a45c6213f 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Alert, Box, @@ -14,8 +14,9 @@ import { } from '@mui/material'; import { Check, Close, ContentCopySharp } from '@mui/icons-material'; import { useClipboard } from 'use-clipboard-copy'; +import { createMnemonic } from 'src/requests'; -const createAccountSteps = ['Save and copy mnemonic for your new account', 'Name your new account']; +const createAccountSteps = ['Save and copy mnemonic for your new account', 'Name your new account', 'Confirm password']; const passwordCreationSteps = [ 'Log out', @@ -49,7 +50,7 @@ const NoPassword = ({ onClose }: { onClose: () => void }) => ( ); -const MnemonicStep = ({ mnemonic, onSave }: { mnemonic: string; onSave: () => void }) => { +const MnemonicStep = ({ mnemonic, onNext }: { mnemonic: string; onNext: () => void }) => { const { copy, copied } = useClipboard({ copiedTimeout: 5000 }); return ( @@ -80,7 +81,7 @@ const MnemonicStep = ({ mnemonic, onSave }: { mnemonic: string; onSave: () => vo - @@ -88,7 +89,7 @@ const MnemonicStep = ({ mnemonic, onSave }: { mnemonic: string; onSave: () => vo ); }; -const NameAccount = ({ onAdd }: { onAdd: (value: string) => void }) => { +const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { const [value, setValue] = useState(''); return ( @@ -102,9 +103,32 @@ const NameAccount = ({ onAdd }: { onAdd: (value: string) => void }) => { disableElevation variant="contained" size="large" - onClick={() => onAdd(value)} + onClick={() => onNext(value)} > - Add + Next + + + + ); +}; + +const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void }) => { + const [value, setValue] = useState(''); + return ( + + + setValue(e.target.value)} fullWidth /> + + + @@ -120,9 +144,26 @@ export const AddAccountModal = ({ show: boolean; withoutPassword?: boolean; onClose: () => void; - onAdd: (accountName: string) => void; + onAdd: (data: { accountName: string; mnemonic: string; password: string }) => void; }) => { const [step, setStep] = useState(0); + const [data, setData] = useState<{ mnemonic?: string; accountName?: string }>({ + mnemonic: undefined, + accountName: undefined, + }); + + const generateMnemonic = async () => { + const mnemon = await createMnemonic(); + setData((d) => ({ ...d, mnemonic: mnemon })); + }; + + useEffect(() => { + if (show) generateMnemonic(); + else { + setData({ mnemonic: undefined, accountName: undefined }); + setStep(0); + } + }, [show]); return ( @@ -142,17 +183,30 @@ export const AddAccountModal = ({ {withoutPassword && } {!withoutPassword && + data.mnemonic && (() => { switch (step) { case 0: + return setStep((s) => s + 1)} />; + case 1: return ( - setStep((s) => s + 1)} + { + setData((d) => ({ ...d, accountName })); + setStep((s) => s + 1); + }} + /> + ); + case 2: + return ( + { + if (data.accountName && data.mnemonic) { + onAdd({ accountName: data.accountName, mnemonic: data.mnemonic, password }); + } + }} /> ); - case 1: - return ; default: return null; } diff --git a/nym-wallet/src/components/Accounts/EditAccountModal.tsx b/nym-wallet/src/components/Accounts/EditAccountModal.tsx index 1f290c6f0f..4ab69ea75f 100644 --- a/nym-wallet/src/components/Accounts/EditAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/EditAccountModal.tsx @@ -11,7 +11,7 @@ import { Typography, } from '@mui/material'; import { Close } from '@mui/icons-material'; -import { TAccount } from 'src/types'; +import { AccountEntry } from 'src/types'; export const EditAccountModal = ({ account, @@ -19,15 +19,15 @@ export const EditAccountModal = ({ onClose, onEdit, }: { - account?: TAccount; + account?: AccountEntry; show: boolean; onClose: () => void; - onEdit: (account: TAccount) => void; + onEdit: (account: AccountEntry) => void; }) => { const [accountName, setAccountName] = useState(''); useEffect(() => { - setAccountName(account ? account?.name : ''); + setAccountName(account ? account?.id : ''); }, [account]); return ( @@ -60,7 +60,7 @@ export const EditAccountModal = ({ disableElevation variant="contained" size="large" - onClick={() => account && onEdit({ ...account, name: accountName })} + onClick={() => account && onEdit({ ...account, id: accountName })} disabled={!accountName?.length} > Edit diff --git a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx index d107e7c83b..00cce65f44 100644 --- a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx +++ b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx @@ -1,29 +1,11 @@ import React, { useEffect, useState } from 'react'; import { Box, Typography } from '@mui/material'; -import { accounts } from './mocks'; import { CopyToClipboard } from '@nymproject/react'; -const fetchMnemonic = (accountName: string): Promise => - new Promise((res) => { - const account = accounts.find((acc) => acc.name === accountName); - if (account) setTimeout(() => res(account.mnemonic), 0); - else res('n/a'); - }); - export const ShowMnemonic = ({ accountName }: { accountName: string }) => { const [showMnemonic, setShowMnemonic] = useState(); const [mnemonic, setMnemonic] = useState(); - useEffect(() => { - const getMnemonic = async () => { - const mnic = await fetchMnemonic(accountName); - setMnemonic(mnic); - }; - - if (showMnemonic) getMnemonic(); - else setMnemonic(undefined); - }, [showMnemonic]); - return ( = (args) => ( export const Default = Template.bind({}); Default.args = { - account: { name: 'Account 1', address: uuid(), mnemonic: 'abc123' }, + account: { id: 'Account 1', address: uuid() }, show: true, onClose: () => {}, }; diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 213717faf4..08fc81be6b 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -2,11 +2,17 @@ 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 { accounts } from 'src/components/Accounts/mocks'; -import { Account, Network, TAccount, 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, +} from '../requests'; import { currencyMap } from '../utils'; import { Console } from '../utils/console'; @@ -23,15 +29,10 @@ export const urls = (networkName?: Network) => networkExplorer: `https://${networkName}-explorer.nymtech.net`, }; -const getAccounts = (): Promise => - new Promise((res) => { - setTimeout(() => res(accounts), 3000); - }); - type TClientContext = { mode: 'light' | 'dark'; clientDetails?: Account; - storedAccounts?: TAccount[]; + storedAccounts?: AccountEntry[]; mixnodeDetails?: TMixnodeBondDetails | null; userBalance: TUseuserBalance; showAdmin: boolean; @@ -56,7 +57,7 @@ export const ClientContext = createContext({} as TClientContext); export const ClientContextProvider = ({ children }: { children: React.ReactNode }) => { const [clientDetails, setClientDetails] = useState(); - const [storedAccounts, setStoredAccounts] = useState(); + const [storedAccounts, setStoredAccounts] = useState(); const [mixnodeDetails, setMixnodeDetails] = useState(); const [network, setNetwork] = useState(); const [currency, setCurrency] = useState(); @@ -114,7 +115,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode useEffect(() => { const fetchAccounts = async () => { - const accs = await getAccounts(); + const accs = await listAccounts(); setStoredAccounts(accs); }; fetchAccounts(); diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index 6fcf7c6628..914fa74061 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -1,4 +1,5 @@ import { invoke } from '@tauri-apps/api'; +import { AccountEntry } from 'src/types/rust/accountentry'; import { Account } from '../types'; export const createMnemonic = async (): Promise => invoke('create_new_mnemonic'); @@ -43,7 +44,7 @@ export const addAccount = async ({ await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); }; -export const listAccounts = async (password: string) => { - const res: Account[] = await invoke('list_accounts_for_password', { password }); +export const listAccounts = async () => { + const res: AccountEntry[] = await invoke('list_accounts'); return res; }; diff --git a/nym-wallet/src/types/rust/index.ts b/nym-wallet/src/types/rust/index.ts index d82e3f75c5..c9e0b3e2a0 100644 --- a/nym-wallet/src/types/rust/index.ts +++ b/nym-wallet/src/types/rust/index.ts @@ -23,3 +23,4 @@ export * from './vestingperiod'; export * from './pendingundelegate'; export * from './delegationevent'; export * from './epoch'; +export * from './accountentry'; From b15cc094ea1e01af62d76b6ff66456b790647b90 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 10:00:17 +0100 Subject: [PATCH 07/24] small refactors --- .../components/Accounts/AccountContainer.tsx | 4 ++++ .../src/components/Accounts/Accounts.tsx | 4 ++-- nym-wallet/src/context/main.tsx | 20 +++++++++---------- nym-wallet/src/index.tsx | 12 +++++------ .../pages/sign-in/pages/signin-mnemonic.tsx | 5 +++-- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AccountContainer.tsx b/nym-wallet/src/components/Accounts/AccountContainer.tsx index 0a4f8b95e4..75c10316df 100644 --- a/nym-wallet/src/components/Accounts/AccountContainer.tsx +++ b/nym-wallet/src/components/Accounts/AccountContainer.tsx @@ -15,6 +15,10 @@ export const AccountsContainer = ({ storedAccounts }: { storedAccounts: AccountE if (selected) setSelectedAccount(selected); }, [accounts, storedAccounts]); + useEffect(() => { + setAccounts(storedAccounts); + }, [storedAccounts]); + const addAccount = async ({ accountName, mnemonic, diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index e0fd917b64..04d27f0dfb 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -22,7 +22,7 @@ export const Accounts = ({ setDialogToDisplay, }: { accounts?: AccountEntry[]; - selectedAccount: AccountEntry; + selectedAccount?: AccountEntry; accountToEdit?: AccountEntry; dialogToDisplay?: TDialog; addAccount: (acc: { accountName: string; mnemonic: string; password: string }) => Promise; @@ -32,7 +32,7 @@ export const Accounts = ({ setSelectedAccount: (accountName: string) => void; setDialogToDisplay: (dialog: TDialog | undefined) => void; }) => - accounts ? ( + accounts && selectedAccount ? ( <> - setDialogToDisplay(undefined)} - accounts={accounts} - onAccountSelect={(accountName) => setSelectedAccount(accountName)} - selectedAccount={selectedAccount.id} - onAdd={() => { - setDialogToDisplay('Add'); - }} - onEdit={(accountName) => { - setAccountToEdit(accountName); - setDialogToDisplay('Edit'); - }} - onImport={() => setDialogToDisplay('Import')} - /> - { - setDialogToDisplay('Accounts'); - }} - onAdd={async (data) => { - addAccount(data); - setDialogToDisplay('Accounts'); - }} - /> - { - setDialogToDisplay('Accounts'); - }} - onEdit={(account) => { - editAccount(account); - setDialogToDisplay('Accounts'); - }} - /> - setDialogToDisplay('Accounts')} - onImport={(mnemonic) => { - importAccount({ id: 'New Account', address: uuidv4(), mnemonic }); - setDialogToDisplay('Accounts'); - }} - /> + + + + ) : null; +}; diff --git a/nym-wallet/src/components/Accounts/AccountsModal.tsx b/nym-wallet/src/components/Accounts/AccountsModal.tsx index bc2de38243..8210b3c562 100644 --- a/nym-wallet/src/components/Accounts/AccountsModal.tsx +++ b/nym-wallet/src/components/Accounts/AccountsModal.tsx @@ -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 { AccountEntry } 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: AccountEntry[]; - selectedAccount: AccountEntry['id']; - onClose: () => void; - onAccountSelect: (accountName: string) => void; - onAdd: () => void; - onEdit: (accoutnName: string) => void; - onImport: () => void; -}) => ( - - - - Accounts - - - - - - Switch between accounts - - - - {accounts.map(({ id, address }) => ( - { - onAccountSelect(id); - onClose(); - }} - onEdit={() => onEdit(id)} - isSelected={selectedAccount === id} - key={address} - /> - ))} - - - - - - -); +export const AccountsModal = ({ onClose }: { onClose?: () => void }) => { + const { accounts, dialogToDisplay, setDialogToDisplay } = useContext(AccountsContext); + + const handleClose = () => { + setDialogToDisplay(undefined); + onClose?.(); + }; + + return ( + + + + Accounts + + + + + + Switch between accounts + + + + {accounts?.map(({ id, address }) => ( + + ))} + + + + + + + ); +}; diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 6a45c6213f..4745dd4590 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Alert, Box, @@ -15,6 +15,7 @@ import { import { Check, Close, ContentCopySharp } from '@mui/icons-material'; import { useClipboard } from 'use-clipboard-copy'; import { createMnemonic } from 'src/requests'; +import { AccountsContext } from 'src/context'; const createAccountSteps = ['Save and copy mnemonic for your new account', 'Name your new account', 'Confirm password']; @@ -135,42 +136,36 @@ const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void ); }; -export const AddAccountModal = ({ - show, - withoutPassword, - onClose, - onAdd, -}: { - show: boolean; - withoutPassword?: boolean; - onClose: () => void; - onAdd: (data: { accountName: string; mnemonic: string; password: string }) => void; -}) => { +export const AddAccountModal = ({ withoutPassword }: { withoutPassword?: boolean }) => { const [step, setStep] = useState(0); const [data, setData] = useState<{ mnemonic?: string; accountName?: string }>({ mnemonic: undefined, accountName: undefined, }); + const { dialogToDisplay, setDialogToDisplay, handleAddAccount } = useContext(AccountsContext); + const generateMnemonic = async () => { const mnemon = await createMnemonic(); setData((d) => ({ ...d, mnemonic: mnemon })); }; + const handleClose = () => { + setDialogToDisplay('Accounts'); + setData({ mnemonic: undefined, accountName: undefined }); + setStep(0); + }; + useEffect(() => { - if (show) generateMnemonic(); - else { - setData({ mnemonic: undefined, accountName: undefined }); - setStep(0); - } - }, [show]); + if (dialogToDisplay === 'Accounts') generateMnemonic(); + }, [dialogToDisplay]); return ( - + Add new account - + @@ -181,7 +176,7 @@ export const AddAccountModal = ({ )} {createAccountSteps[step]} - {withoutPassword && } + {withoutPassword && } {!withoutPassword && data.mnemonic && (() => { @@ -202,7 +197,7 @@ export const AddAccountModal = ({ { if (data.accountName && data.mnemonic) { - onAdd({ accountName: data.accountName, mnemonic: data.mnemonic, password }); + handleAddAccount({ accountName: data.accountName, mnemonic: data.mnemonic, password }); } }} /> diff --git a/nym-wallet/src/components/Accounts/EditAccountModal.tsx b/nym-wallet/src/components/Accounts/EditAccountModal.tsx index 4ab69ea75f..c5fec1d28b 100644 --- a/nym-wallet/src/components/Accounts/EditAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/EditAccountModal.tsx @@ -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 { AccountEntry } from 'src/types'; +import { AccountsContext } from 'src/context'; -export const EditAccountModal = ({ - account, - show, - onClose, - onEdit, -}: { - account?: AccountEntry; - show: boolean; - onClose: () => void; - onEdit: (account: AccountEntry) => void; -}) => { +export const EditAccountModal = () => { const [accountName, setAccountName] = useState(''); + const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount } = useContext(AccountsContext); + useEffect(() => { - setAccountName(account ? account?.id : ''); - }, [account]); + setAccountName(accountToEdit ? accountToEdit?.id : ''); + }, [accountToEdit]); return ( - + setDialogToDisplay('Accounts')} fullWidth hideBackdrop> Edit account name - + setDialogToDisplay('Accounts')}> @@ -60,7 +52,12 @@ export const EditAccountModal = ({ disableElevation variant="contained" size="large" - onClick={() => account && onEdit({ ...account, id: accountName })} + onClick={() => { + if (accountToEdit) { + handleEditAccount({ ...accountToEdit, id: accountName }); + setDialogToDisplay('Accounts'); + } + }} disabled={!accountName?.length} > Edit diff --git a/nym-wallet/src/components/Accounts/ImportAccountModal.tsx b/nym-wallet/src/components/Accounts/ImportAccountModal.tsx index 8222f330bf..2e61066f71 100644 --- a/nym-wallet/src/components/Accounts/ImportAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/ImportAccountModal.tsx @@ -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 ( - + Import account - + @@ -59,7 +55,7 @@ export const ImportAccountModal = ({ disableElevation variant="contained" size="large" - onClick={() => onImport(mnemonic)} + onClick={() => handleImportAccount({ id: '', address: '' })} disabled={!mnemonic.length} > Import account diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index a97b3ae0a4..ef3bec09d5 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; -import { Account, AccountEntry, TAccountsDialog } from 'src/types'; +import { AccountEntry } from 'src/types'; import { addAccount as addAccountRequest } from 'src/requests'; import { ClientContext } from './main'; @@ -16,6 +16,8 @@ type TAccounts = { handleImportAccount: (account: AccountEntry) => void; }; +export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import'; + export const AccountsContext = createContext({} as TAccounts); export const AccountsProvider: React.FC = ({ children }) => { diff --git a/nym-wallet/src/context/index.tsx b/nym-wallet/src/context/index.tsx index fe68268d78..e6b2ab0a8e 100644 --- a/nym-wallet/src/context/index.tsx +++ b/nym-wallet/src/context/index.tsx @@ -1,2 +1,3 @@ export * from './main'; export * from './sign-in'; +export * from './accounts'; diff --git a/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx b/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx index 1b81179917..51e4377bb4 100644 --- a/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx +++ b/nym-wallet/src/pages/sign-in/pages/signin-mnemonic.tsx @@ -1,9 +1,9 @@ import React, { useContext, useState, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { Box, Button, FormControl, Stack } from '@mui/material'; +import { ClientContext } from 'src/context'; import { isPasswordCreated } from 'src/requests'; import { MnemonicInput, Subtitle } from '../components'; -import { ClientContext } from 'src/context'; export const SignInMnemonic = () => { const [mnemonic, setMnemonic] = useState(''); From 7a0dff5f00f4ac76c925f752349874a7a934df3c Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 16:22:00 +0100 Subject: [PATCH 12/24] update main state to respond to account changes --- nym-wallet/src/context/main.tsx | 42 +++++++++++++++++++++--------- nym-wallet/src/requests/account.ts | 21 ++++++++++++--- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 2c1b8120a3..00fcd0df83 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -12,6 +12,7 @@ import { signInWithMnemonic, signInWithPassword, signOut, + switchAccount, } from '../requests'; import { currencyMap } from '../utils'; import { Console } from '../utils/console'; @@ -50,7 +51,7 @@ type TClientContext = { logIn: (opts: { type: 'mnemonic' | 'password'; value: string }) => void; signInWithPassword: (password: string) => void; logOut: () => void; - onAccountChange: (mnemonic: string) => void; + onAccountChange: (accountId: string) => void; }; export const ClientContext = createContext({} as TClientContext); @@ -92,6 +93,11 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode } }; + const loadStoredAccounts = async () => { + const accounts = await listAccounts(); + setStoredAccounts(accounts); + }; + const getBondDetails = async () => { setMixnodeDetails(undefined); try { @@ -111,11 +117,6 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode refreshAccount(); }, [network]); - const loadAccounts = async () => { - const accs = await listAccounts(); - setStoredAccounts(accs); - }; - useEffect(() => { if (!clientDetails) clearState(); }, [clientDetails]); @@ -131,7 +132,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode await signInWithMnemonic(value); } else { await signInWithPassword(value); - await loadAccounts(); + await loadStoredAccounts(); } setNetwork('MAINNET'); history.push('/balance'); @@ -148,11 +149,14 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode enqueueSnackbar('Successfully logged out', { variant: 'success' }); }; - const onAccountChange = async (value: string) => { - clearState(); - await signOut(); - await logIn({ type: 'mnemonic', value }); - enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true }); + const onAccountChange = async (accountId: string) => { + if (network) { + setIsLoading(true); + await switchAccount(accountId); + await loadAccount(network); + setIsLoading(false); + enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: true }); + } }; const handleShowAdmin = () => setShowAdmin((show) => !show); @@ -183,7 +187,19 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode logOut, onAccountChange, }), - [mode, isLoading, error, clientDetails, mixnodeDetails, userBalance, showAdmin, showSettings, network, currency], + [ + mode, + isLoading, + error, + clientDetails, + mixnodeDetails, + userBalance, + showAdmin, + showSettings, + network, + currency, + storedAccounts, + ], ); return {children}; diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index 914fa74061..603bf9e1b2 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -13,13 +13,13 @@ export const signInWithMnemonic = async (mnemonic: string): Promise => return res; }; -export const validateMnemonic = async (mnemonic: string): Promise => { - const res: boolean = await invoke('validate_mnemonic', { mnemonic }); +export const signInWithPassword = async (password: string): Promise => { + const res: Account = await invoke('sign_in_with_password', { password }); return res; }; -export const signInWithPassword = async (password: string): Promise => { - const res: Account = await invoke('sign_in_with_password', { password }); +export const validateMnemonic = async (mnemonic: string): Promise => { + const res: boolean = await invoke('validate_mnemonic', { mnemonic }); return res; }; @@ -44,7 +44,20 @@ export const addAccount = async ({ await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); }; +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, innerId: accountName }); + return res; +}; + +export const switchAccount = async (accountId: string) => { + await invoke('sign_in_decrypted_account', { accountId }); +}; From 72c7049fca0c2faf76c7b9c9b52bd1ef745e117b Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 20:45:33 +0100 Subject: [PATCH 13/24] set up loading page for account switch --- nym-wallet/src/components/AppBar.tsx | 5 +--- nym-wallet/src/components/LoadingPage.tsx | 2 +- nym-wallet/src/context/accounts.tsx | 31 +++++++++++++---------- nym-wallet/src/context/main.tsx | 1 + nym-wallet/src/index.tsx | 18 ++++++++----- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index 2cec6a9fbc..43a22955d5 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -1,7 +1,6 @@ import React, { useContext } from 'react'; import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material'; import { Logout } from '@mui/icons-material'; -import { AccountsProvider } from 'src/context/accounts'; import { ClientContext } from '../context/main'; import { NetworkSelector } from './NetworkSelector'; import { Node as NodeIcon } from '../svg-icons/node'; @@ -16,9 +15,7 @@ export const AppBar = () => { - - - + diff --git a/nym-wallet/src/components/LoadingPage.tsx b/nym-wallet/src/components/LoadingPage.tsx index 90782bd833..62e4f4be50 100644 --- a/nym-wallet/src/components/LoadingPage.tsx +++ b/nym-wallet/src/components/LoadingPage.tsx @@ -23,7 +23,7 @@ export const LoadingPage = () => ( }} > - + diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index ef3bec09d5..908428516b 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -37,11 +37,15 @@ export const AccountsProvider: React.FC = ({ children }) => { mnemonic: string; password: string; }) => { - await addAccountRequest({ - accountName, - mnemonic, - password, - }); + try { + await addAccountRequest({ + accountName, + mnemonic, + password, + }); + } catch (e) { + console.log('Error adding account'); + } }; const handleEditAccount = (account: AccountEntry) => setAccounts((accs) => accs?.map((acc) => (acc.address === account.address ? account : acc))); @@ -52,20 +56,21 @@ export const AccountsProvider: React.FC = ({ children }) => { setAccountToEdit(accounts?.find((acc) => acc.id === accountName)); const handleSelectAccount = async (accountName: string) => { - const match = accounts?.find((acc) => acc.id === accountName); - if (match) { - try { - await onAccountChange(match.id); - setSelectedAccount(match); - } catch (e) { - console.log('Error swtiching account'); - } + try { + await onAccountChange(accountName); + const match = accounts?.find((acc) => acc.id === accountName); + setSelectedAccount(match); + } catch (e) { + console.log('Error swtiching account'); } }; useEffect(() => { if (storedAccounts) { setAccounts(storedAccounts); + } + + if (storedAccounts && !selectedAccount) { setSelectedAccount(storedAccounts[0]); } }, [storedAccounts]); diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 00fcd0df83..18faa9f4c8 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -112,6 +112,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode const refreshAccount = async () => { if (network) { await loadAccount(network); + await loadStoredAccounts(); } }; refreshAccount(); diff --git a/nym-wallet/src/index.tsx b/nym-wallet/src/index.tsx index 23cfd9b928..3be7eb6e86 100644 --- a/nym-wallet/src/index.tsx +++ b/nym-wallet/src/index.tsx @@ -10,7 +10,7 @@ import { Admin, Settings } from './pages'; import { ErrorFallback } from './components'; import { NymWalletTheme, WelcomeTheme } from './theme'; import { maximizeWindow } from './utils'; -import { SignInProvider } from './context'; +import { AccountsProvider, SignInProvider } from './context'; import { LoadingPage } from './components/LoadingPage'; const App = () => { @@ -26,11 +26,17 @@ const App = () => { ) : ( - - - - - + + {isLoading ? ( + + ) : ( + + + + + + )} + ); }; From ef4af0a1db8643d61f808e2933de1e30adaaf266 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 20:49:19 +0100 Subject: [PATCH 14/24] display account errors --- nym-wallet/src/context/accounts.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index 908428516b..ffd5a10757 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -1,6 +1,7 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { AccountEntry } from 'src/types'; import { addAccount as addAccountRequest } from 'src/requests'; +import { useSnackbar } from 'notistack'; import { ClientContext } from './main'; type TAccounts = { @@ -27,6 +28,7 @@ export const AccountsProvider: React.FC = ({ children }) => { const [dialogToDisplay, setDialogToDisplay] = useState(); const { onAccountChange, storedAccounts } = useContext(ClientContext); + const { enqueueSnackbar } = useSnackbar(); const handleAddAccount = async ({ accountName, @@ -44,7 +46,7 @@ export const AccountsProvider: React.FC = ({ children }) => { password, }); } catch (e) { - console.log('Error adding account'); + enqueueSnackbar('Error adding account', { variant: 'error' }); } }; const handleEditAccount = (account: AccountEntry) => @@ -61,7 +63,7 @@ export const AccountsProvider: React.FC = ({ children }) => { const match = accounts?.find((acc) => acc.id === accountName); setSelectedAccount(match); } catch (e) { - console.log('Error swtiching account'); + enqueueSnackbar('Error swtiching account', { variant: 'error' }); } }; From a1961dbc2f86ff60561b021d6712cb90ec75cd12 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 21:34:42 +0100 Subject: [PATCH 15/24] add accounts wip --- .../components/Accounts/AddAccountModal.tsx | 6 ++++- nym-wallet/src/context/accounts.tsx | 20 ++++++++++++----- nym-wallet/src/context/main.tsx | 13 +++++++---- nym-wallet/src/requests/account.ts | 22 +++++++++++-------- 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 4745dd4590..4bac2e5660 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -3,6 +3,7 @@ import { Alert, Box, Button, + CircularProgress, Dialog, DialogActions, DialogContent, @@ -115,6 +116,8 @@ const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void }) => { const [value, setValue] = useState(''); + const { isLoading } = useContext(AccountsContext); + return ( @@ -122,12 +125,13 @@ const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index ffd5a10757..a2699446e1 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -9,6 +9,7 @@ type TAccounts = { selectedAccount?: AccountEntry; accountToEdit?: AccountEntry; dialogToDisplay?: TAccountsDialog; + isLoading: boolean; handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void; setDialogToDisplay: (dialog?: TAccountsDialog) => void; handleSelectAccount: (accountId: string) => void; @@ -22,11 +23,11 @@ export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import'; export const AccountsContext = createContext({} as TAccounts); export const AccountsProvider: React.FC = ({ children }) => { - const [accounts, setAccounts] = useState(); + const [accounts, setAccounts] = useState([]); const [selectedAccount, setSelectedAccount] = useState(); const [accountToEdit, setAccountToEdit] = useState(); const [dialogToDisplay, setDialogToDisplay] = useState(); - + const [isLoading, setIsLoading] = useState(false); const { onAccountChange, storedAccounts } = useContext(ClientContext); const { enqueueSnackbar } = useSnackbar(); @@ -39,14 +40,20 @@ export const AccountsProvider: React.FC = ({ children }) => { mnemonic: string; password: string; }) => { + setIsLoading(true); try { - await addAccountRequest({ + const newAccount = await addAccountRequest({ accountName, mnemonic, password, }); + setAccounts((accs) => [...accs, newAccount]); + enqueueSnackbar('New account created', { variant: 'success' }); + setDialogToDisplay('Accounts'); } catch (e) { - enqueueSnackbar('Error adding account', { variant: 'error' }); + enqueueSnackbar(`Error adding account: ${e}`, { variant: 'error' }); + } finally { + setIsLoading(false); } }; const handleEditAccount = (account: AccountEntry) => @@ -63,7 +70,7 @@ export const AccountsProvider: React.FC = ({ children }) => { const match = accounts?.find((acc) => acc.id === accountName); setSelectedAccount(match); } catch (e) { - enqueueSnackbar('Error swtiching account', { variant: 'error' }); + console.log('boom!'); } }; @@ -86,13 +93,14 @@ export const AccountsProvider: React.FC = ({ children }) => { accountToEdit, dialogToDisplay, setDialogToDisplay, + isLoading, handleAddAccount, handleEditAccount, handleAccountToEdit, handleSelectAccount, handleImportAccount, }), - [accounts, selectedAccount, accountToEdit, dialogToDisplay], + [accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading], )} > {children} diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 18faa9f4c8..be20a1a0f1 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -153,10 +153,15 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode const onAccountChange = async (accountId: string) => { if (network) { setIsLoading(true); - await switchAccount(accountId); - await loadAccount(network); - setIsLoading(false); - enqueueSnackbar('Account switch success', { variant: 'success', preventDuplicate: 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); + } } }; diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index 603bf9e1b2..f40eb6465c 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -2,32 +2,35 @@ import { invoke } from '@tauri-apps/api'; import { AccountEntry } from 'src/types/rust/accountentry'; import { Account } from '../types'; -export const createMnemonic = async (): Promise => 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 => { +export const createPassword = async ({ mnemonic, password }: { mnemonic: string; password: string }) => { await invoke('create_password', { mnemonic, password }); }; -export const signInWithMnemonic = async (mnemonic: string): Promise => { +export const signInWithMnemonic = async (mnemonic: string) => { const res: Account = await invoke('connect_with_mnemonic', { mnemonic }); return res; }; -export const signInWithPassword = async (password: string): Promise => { +export const signInWithPassword = async (password: string) => { const res: Account = await invoke('sign_in_with_password', { password }); return res; }; -export const validateMnemonic = async (mnemonic: string): Promise => { +export const validateMnemonic = async (mnemonic: string) => { const res: boolean = await invoke('validate_mnemonic', { mnemonic }); return res; }; -export const signOut = async (): Promise => { +export const signOut = async () => { await invoke('logout'); }; -export const isPasswordCreated = async (): Promise => { +export const isPasswordCreated = async () => { const res: boolean = await invoke('does_password_file_exist'); return res; }; @@ -40,8 +43,9 @@ export const addAccount = async ({ mnemonic: string; password: string; accountName: string; -}): Promise => { - await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); +}) => { + const res: AccountEntry = await invoke('add_account_for_password', { mnemonic, password, innerId: accountName }); + return res; }; export const removeAccount = async ({ password, accountName }: { password: string; accountName: string }) => { From ecb27e2cc2fd7a8b9a93242471cb171ae910340a Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 4 May 2022 22:36:09 +0100 Subject: [PATCH 16/24] import accounts wip --- .../src/components/Accounts/Accounts.tsx | 2 - .../components/Accounts/AddAccountModal.tsx | 70 ++++++++++++++++--- nym-wallet/src/context/accounts.tsx | 10 +-- nym-wallet/src/context/main.tsx | 27 ++++--- nym-wallet/src/pages/sign-in/types.ts | 2 - 5 files changed, 80 insertions(+), 31 deletions(-) diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index 2dd50102d4..9845ec1269 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -4,7 +4,6 @@ import { AccountsContext } from 'src/context'; import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; import { AccountsModal } from './AccountsModal'; -import { ImportAccountModal } from './ImportAccountModal'; import { AccountAvatar } from './AccountAvatar'; export const Accounts = () => { @@ -23,7 +22,6 @@ export const Accounts = () => { - ) : null; }; diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 4bac2e5660..b21327a7df 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -19,6 +19,11 @@ import { createMnemonic } from 'src/requests'; import { AccountsContext } from 'src/context'; const createAccountSteps = ['Save and copy mnemonic for your new account', 'Name your new account', 'Confirm password']; +const importAccountSteps = [ + 'Provide mnemonic of account you want to import', + 'Name your new account', + 'Confirm password', +]; const passwordCreationSteps = [ 'Log out', @@ -91,6 +96,36 @@ const MnemonicStep = ({ mnemonic, onNext }: { mnemonic: string; onNext: () => vo ); }; +const ImportMnemonic = ({ + value, + onChange, + onNext, +}: { + value: string; + onChange: (value: string) => void; + onNext: () => void; +}) => ( + + + + onChange(e.target.value)} fullWidth /> + + + + + + +); + const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { const [value, setValue] = useState(''); return ( @@ -142,9 +177,9 @@ const ConfirmPassword = ({ onConfirm }: { onConfirm: (password: string) => void export const AddAccountModal = ({ withoutPassword }: { withoutPassword?: boolean }) => { const [step, setStep] = useState(0); - const [data, setData] = useState<{ mnemonic?: string; accountName?: string }>({ - mnemonic: undefined, - accountName: undefined, + const [data, setData] = useState({ + mnemonic: '', + accountName: '', }); const { dialogToDisplay, setDialogToDisplay, handleAddAccount } = useContext(AccountsContext); @@ -156,19 +191,25 @@ export const AddAccountModal = ({ withoutPassword }: { withoutPassword?: boolean const handleClose = () => { setDialogToDisplay('Accounts'); - setData({ mnemonic: undefined, accountName: undefined }); + setData({ mnemonic: '', accountName: '' }); setStep(0); }; useEffect(() => { - if (dialogToDisplay === 'Accounts') generateMnemonic(); + if (dialogToDisplay === 'Add') generateMnemonic(); + else setData({ mnemonic: '', accountName: '' }); }, [dialogToDisplay]); return ( - + - Add new account + {`${dialogToDisplay} new account`} @@ -178,15 +219,24 @@ export const AddAccountModal = ({ withoutPassword }: { withoutPassword?: boolean {`Step ${step + 1}/${createAccountSteps.length}`} )} - {createAccountSteps[step]} + + {dialogToDisplay === 'Add' ? createAccountSteps[step] : importAccountSteps[step]} + {withoutPassword && } {!withoutPassword && - data.mnemonic && (() => { switch (step) { case 0: - return setStep((s) => s + 1)} />; + return dialogToDisplay === 'Add' ? ( + setStep((s) => s + 1)} /> + ) : ( + setData((d) => ({ ...d, mnemonic: value }))} + onNext={() => setStep((s) => s + 1)} + /> + ); case 1: return ( { setAccountToEdit(accounts?.find((acc) => acc.id === accountName)); const handleSelectAccount = async (accountName: string) => { - try { - await onAccountChange(accountName); - const match = accounts?.find((acc) => acc.id === accountName); - setSelectedAccount(match); - } catch (e) { - console.log('boom!'); - } + await onAccountChange(accountName); + const match = accounts?.find((acc) => acc.id === accountName); + setSelectedAccount(match); }; useEffect(() => { diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index be20a1a0f1..2e3332ed96 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -1,7 +1,6 @@ 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, AccountEntry } from '../types'; import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance'; import { config } from '../../config'; @@ -30,6 +29,8 @@ export const urls = (networkName?: Network) => networkExplorer: `https://${networkName}-explorer.nymtech.net`, }; +type TLoginType = 'mnemonic' | 'password'; + type TClientContext = { mode: 'light' | 'dark'; clientDetails?: Account; @@ -42,13 +43,14 @@ type TClientContext = { currency?: TCurrency; isLoading: boolean; error?: string; + loginType?: TLoginType; setIsLoading: (isLoading: boolean) => void; setError: (value?: string) => void; switchNetwork: (network: Network) => void; getBondDetails: () => Promise; 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; @@ -65,6 +67,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode 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(); @@ -108,14 +111,15 @@ 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 loadStoredAccounts(); - } - }; - refreshAccount(); + if (network) refreshAccount(network); }, [network]); useEffect(() => { @@ -131,9 +135,10 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode setIsLoading(true); if (type === 'mnemonic') { await signInWithMnemonic(value); + setLoginType('mnemonic'); } else { await signInWithPassword(value); - await loadStoredAccounts(); + setLoginType('password'); } setNetwork('MAINNET'); history.push('/balance'); @@ -182,6 +187,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode showSettings, network, currency, + loginType, setIsLoading, setError, signInWithPassword, @@ -194,6 +200,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode onAccountChange, }), [ + loginType, mode, isLoading, error, diff --git a/nym-wallet/src/pages/sign-in/types.ts b/nym-wallet/src/pages/sign-in/types.ts index d021361a10..2f5bc6ad8a 100644 --- a/nym-wallet/src/pages/sign-in/types.ts +++ b/nym-wallet/src/pages/sign-in/types.ts @@ -19,5 +19,3 @@ export type TMnemonicWords = TMnemonicWord[]; export type THiddenMnemonicWord = { hidden: boolean } & TMnemonicWord; export type THiddenMnemonicWords = THiddenMnemonicWord[]; - -export type TLoginType = 'mnemonic' | 'password'; From 6e9eab4edbd78a7eeb8e75630691d9aebacfd05a Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 5 May 2022 11:15:56 +0100 Subject: [PATCH 17/24] allow access to prev steps when creating account --- .../src/components/Accounts/AddAccountModal.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index b21327a7df..c6010357c3 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -126,7 +126,7 @@ const ImportMnemonic = ({ ); -const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { +const NameAccount = ({ onPrev, onNext }: { onPrev: () => void; onNext: (value: string) => void }) => { const [value, setValue] = useState(''); return ( @@ -134,6 +134,9 @@ const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { setValue(e.target.value)} fullWidth /> + ); }; diff --git a/nym-wallet/src/components/Accounts/stories/AddAccount.stories.tsx b/nym-wallet/src/components/Accounts/stories/AddAccount.stories.tsx index a1cb49cf40..af7e10ad8c 100644 --- a/nym-wallet/src/components/Accounts/stories/AddAccount.stories.tsx +++ b/nym-wallet/src/components/Accounts/stories/AddAccount.stories.tsx @@ -8,9 +8,9 @@ export default { component: AddAccountModal, } as ComponentMeta; -const Template: ComponentStory = (args) => ( +const Template: ComponentStory = () => ( - + ); diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index 88acd38f9d..e71b893048 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; +import React, { createContext, Dispatch, SetStateAction, useContext, useEffect, useMemo, useState } from 'react'; import { AccountEntry } from 'src/types'; import { addAccount as addAccountRequest } from 'src/requests'; import { useSnackbar } from 'notistack'; @@ -10,6 +10,8 @@ type TAccounts = { accountToEdit?: AccountEntry; dialogToDisplay?: TAccountsDialog; isLoading: boolean; + error?: string; + setError: Dispatch>; handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void; setDialogToDisplay: (dialog?: TAccountsDialog) => void; handleSelectAccount: (accountId: string) => void; @@ -27,6 +29,7 @@ export const AccountsProvider: React.FC = ({ children }) => { const [selectedAccount, setSelectedAccount] = useState(); const [accountToEdit, setAccountToEdit] = useState(); const [dialogToDisplay, setDialogToDisplay] = useState(); + const [error, setError] = useState(); const [isLoading, setIsLoading] = useState(false); const { onAccountChange, storedAccounts } = useContext(ClientContext); const { enqueueSnackbar } = useSnackbar(); @@ -51,7 +54,7 @@ export const AccountsProvider: React.FC = ({ children }) => { enqueueSnackbar('New account created', { variant: 'success' }); setDialogToDisplay('Accounts'); } catch (e) { - enqueueSnackbar(`Error adding account: ${e}`, { variant: 'error' }); + setError(`Error adding account: ${e}`); } finally { setIsLoading(false); } @@ -84,6 +87,8 @@ export const AccountsProvider: React.FC = ({ children }) => { ({ + error, + setError, accounts, selectedAccount, accountToEdit, @@ -96,7 +101,7 @@ export const AccountsProvider: React.FC = ({ children }) => { handleSelectAccount, handleImportAccount, }), - [accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading], + [accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error], )} > {children} From 0e4787f0783555f256d3a34f0bf8940aac4d9f80 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 6 May 2022 15:54:38 +0100 Subject: [PATCH 19/24] minor refactors --- .../src/components/Accounts/AccountItem.tsx | 17 ++- .../src/components/Accounts/Accounts.tsx | 2 + .../components/Accounts/AddAccountModal.tsx | 58 +--------- .../src/components/Accounts/MnemonicModal.tsx | 102 ++++++++++++++++++ .../src/components/Accounts/ShowMnemonic.tsx | 29 ----- .../components/Accounts/ShowMnemonicModal.tsx | 47 -------- .../Accounts/stories/ShowMnemonic.stories.tsx | 23 ---- nym-wallet/src/components/Mnemonic.tsx | 37 +++++++ nym-wallet/src/context/accounts.tsx | 29 ++++- nym-wallet/src/requests/account.ts | 2 +- 10 files changed, 184 insertions(+), 162 deletions(-) create mode 100644 nym-wallet/src/components/Accounts/MnemonicModal.tsx delete mode 100644 nym-wallet/src/components/Accounts/ShowMnemonic.tsx delete mode 100644 nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx delete mode 100644 nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx create mode 100644 nym-wallet/src/components/Mnemonic.tsx diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index 940da46285..584fc37231 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -12,10 +12,10 @@ import { import { Edit } from '@mui/icons-material'; import { AccountsContext } from 'src/context'; import { AccountAvatar } from './AccountAvatar'; -import { ShowMnemonic } from './ShowMnemonic'; export const AccountItem = ({ name, address }: { name: string; address: string }) => { - const { selectedAccount, handleSelectAccount, handleAccountToEdit } = useContext(AccountsContext); + const { selectedAccount, handleSelectAccount, handleAccountToEdit, setDialogToDisplay, setAccountMnemonic } = + useContext(AccountsContext); return ( {address} - + ) => { + e.stopPropagation(); + setDialogToDisplay('Mnemonic'); + setAccountMnemonic((accountMnemonic) => ({ ...accountMnemonic, accountName: name })); + }} + > + Show mnemonic + } diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index 9845ec1269..77fd2eba9f 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -5,6 +5,7 @@ import { EditAccountModal } from './EditAccountModal'; import { AddAccountModal } from './AddAccountModal'; import { AccountsModal } from './AccountsModal'; import { AccountAvatar } from './AccountAvatar'; +import { MnemonicModal } from './MnemonicModal'; export const Accounts = () => { const { accounts, selectedAccount, setDialogToDisplay } = useContext(AccountsContext); @@ -22,6 +23,7 @@ export const Accounts = () => { + ) : null; }; diff --git a/nym-wallet/src/components/Accounts/AddAccountModal.tsx b/nym-wallet/src/components/Accounts/AddAccountModal.tsx index 3fae200f38..78a44b96f3 100644 --- a/nym-wallet/src/components/Accounts/AddAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/AddAccountModal.tsx @@ -17,6 +17,7 @@ import { Check, Close, ContentCopySharp } from '@mui/icons-material'; import { useClipboard } from 'use-clipboard-copy'; import { createMnemonic } from 'src/requests'; import { AccountsContext } from 'src/context'; +import { Mnemonic } from '../Mnemonic'; const createAccountSteps = [ 'Copy and save mnemonic for your new account', @@ -29,67 +30,12 @@ const importAccountSteps = [ 'Confirm the password used to login to your wallet', ]; -const passwordCreationSteps = [ - 'Log out', - 'During sign in screen click “Sign in with mnemonic” button', - 'On next screen click “Create a password for your account”', - 'Sign in to wallet with your new password', - 'Now you can create multiple accounts', -]; - -const NoPassword = ({ onClose }: { onClose: () => void }) => ( - - - - - - You can’t add new accounts if your wallet doesn’t have a password. - - Follow steps below to create password. - - How to create password to your account - {passwordCreationSteps.map((step, i) => ( - {`${i + 1}. ${step}`} - ))} - - - - - - -); - const MnemonicStep = ({ mnemonic, onNext }: { mnemonic: string; onNext: () => void }) => { const { copy, copied } = useClipboard({ copiedTimeout: 5000 }); return ( - - - - Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the - future - - - - - - + + )} + + + ); +}; diff --git a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx b/nym-wallet/src/components/Accounts/ShowMnemonic.tsx deleted file mode 100644 index 00cce65f44..0000000000 --- a/nym-wallet/src/components/Accounts/ShowMnemonic.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Box, Typography } from '@mui/material'; -import { CopyToClipboard } from '@nymproject/react'; - -export const ShowMnemonic = ({ accountName }: { accountName: string }) => { - const [showMnemonic, setShowMnemonic] = useState(); - const [mnemonic, setMnemonic] = useState(); - - return ( - - { - e.stopPropagation(); - setShowMnemonic((show) => (!show ? accountName : undefined)); - }} - > - {`${showMnemonic ? 'Hide' : 'Show'} mnemonic`} - - {mnemonic && ( - - {mnemonic} - - - )} - - ); -}; diff --git a/nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx b/nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx deleted file mode 100644 index 392bd4e841..0000000000 --- a/nym-wallet/src/components/Accounts/ShowMnemonicModal.tsx +++ /dev/null @@ -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; -}) => ( - - - - Show mnemonic - - - - - - - - - DO NOT share this phrase with anyone! - These words can be used to steal all your accounts. - - } icon={false}> - Mnemonic - {mnemonic} - - - - -); diff --git a/nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx b/nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx deleted file mode 100644 index 0db1a2046a..0000000000 --- a/nym-wallet/src/components/Accounts/stories/ShowMnemonic.stories.tsx +++ /dev/null @@ -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; - -const Template: ComponentStory = (args) => ( - - - -); - -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: () => {}, -}; diff --git a/nym-wallet/src/components/Mnemonic.tsx b/nym-wallet/src/components/Mnemonic.tsx new file mode 100644 index 0000000000..e49c2b8042 --- /dev/null +++ b/nym-wallet/src/components/Mnemonic.tsx @@ -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; +}) => ( + + + + Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future + + + + + + +); diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index e71b893048..d98a7d697f 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -1,6 +1,6 @@ import React, { createContext, Dispatch, SetStateAction, useContext, useEffect, useMemo, useState } from 'react'; import { AccountEntry } from 'src/types'; -import { addAccount as addAccountRequest } from 'src/requests'; +import { addAccount as addAccountRequest, showMnemonicForAccount } from 'src/requests'; import { useSnackbar } from 'notistack'; import { ClientContext } from './main'; @@ -11,16 +11,20 @@ type TAccounts = { dialogToDisplay?: TAccountsDialog; isLoading: boolean; error?: string; + accountMnemonic: TAccountMnemonic; setError: Dispatch>; + setAccountMnemonic: Dispatch>; handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void; setDialogToDisplay: (dialog?: TAccountsDialog) => void; handleSelectAccount: (accountId: string) => void; handleAccountToEdit: (accountId: string) => void; handleEditAccount: (account: AccountEntry) => void; handleImportAccount: (account: AccountEntry) => void; + handleGetAcccountMnemonic: (data: { password: string; accountName: string }) => void; }; -export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import'; +export type TAccountsDialog = 'Accounts' | 'Add' | 'Edit' | 'Import' | 'Mnemonic'; +export type TAccountMnemonic = { value?: string; accountName?: string }; export const AccountsContext = createContext({} as TAccounts); @@ -29,6 +33,10 @@ export const AccountsProvider: React.FC = ({ children }) => { const [selectedAccount, setSelectedAccount] = useState(); const [accountToEdit, setAccountToEdit] = useState(); const [dialogToDisplay, setDialogToDisplay] = useState(); + const [accountMnemonic, setAccountMnemonic] = useState({ + value: undefined, + accountName: undefined, + }); const [error, setError] = useState(); const [isLoading, setIsLoading] = useState(false); const { onAccountChange, storedAccounts } = useContext(ClientContext); @@ -73,6 +81,18 @@ export const AccountsProvider: React.FC = ({ children }) => { setSelectedAccount(match); }; + const handleGetAcccountMnemonic = async ({ password, accountName }: { password: string; accountName: string }) => { + try { + setIsLoading(true); + const mnemonic = await showMnemonicForAccount({ password, accountName }); + setAccountMnemonic({ value: mnemonic, accountName }); + } catch (e) { + setError(e as string); + } finally { + setIsLoading(false); + } + }; + useEffect(() => { if (storedAccounts) { setAccounts(storedAccounts); @@ -93,15 +113,18 @@ export const AccountsProvider: React.FC = ({ children }) => { selectedAccount, accountToEdit, dialogToDisplay, + accountMnemonic, setDialogToDisplay, + setAccountMnemonic, isLoading, handleAddAccount, handleEditAccount, handleAccountToEdit, handleSelectAccount, handleImportAccount, + handleGetAcccountMnemonic, }), - [accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error], + [accounts, selectedAccount, accountToEdit, dialogToDisplay, isLoading, error, accountMnemonic], )} > {children} diff --git a/nym-wallet/src/requests/account.ts b/nym-wallet/src/requests/account.ts index f40eb6465c..bc85fe30ad 100644 --- a/nym-wallet/src/requests/account.ts +++ b/nym-wallet/src/requests/account.ts @@ -58,7 +58,7 @@ export const listAccounts = async () => { }; export const showMnemonicForAccount = async ({ password, accountName }: { password: string; accountName: string }) => { - const res: string = await invoke('show_mnemonic_for_account_in_password', { password, innerId: accountName }); + const res: string = await invoke('show_mnemonic_for_account_in_password', { password, accountId: accountName }); return res; }; From f2fa2214894fb1031d67a8fe887e9e0393e2db31 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Sat, 7 May 2022 17:53:46 +0100 Subject: [PATCH 20/24] big tidy and some refactoring of high level component hierachy --- .../src/components/Accounts/AccountItem.tsx | 17 ++++- .../components/Accounts/AddAccountModal.tsx | 33 +++------ .../src/components/Accounts/MnemonicModal.tsx | 4 +- nym-wallet/src/components/Accounts/index.tsx | 9 +++ nym-wallet/src/components/AppBar.tsx | 20 ++++-- nym-wallet/src/components/ClientAddress.tsx | 4 +- nym-wallet/src/components/Fee.tsx | 4 +- nym-wallet/src/components/LoadingPage.tsx | 45 ++++++------ nym-wallet/src/components/Nav.tsx | 4 +- nym-wallet/src/components/NetworkSelector.tsx | 4 +- .../src/components/TokenPoolSelector.tsx | 4 +- nym-wallet/src/components/index.ts | 1 + nym-wallet/src/context/accounts.tsx | 12 ++-- .../src/context/{sign-in.tsx => auth.tsx} | 19 ++---- nym-wallet/src/context/index.tsx | 2 +- nym-wallet/src/context/main.tsx | 19 +++--- nym-wallet/src/hooks/useCheckOwnership.ts | 4 +- nym-wallet/src/hooks/useGetBalance.ts | 6 +- nym-wallet/src/index.tsx | 68 ++++++------------- nym-wallet/src/layouts/AppLayout.tsx | 64 +++++++++-------- nym-wallet/src/layouts/AuthLayout.tsx | 41 +++++++++++ nym-wallet/src/pages/Admin/index.tsx | 4 +- .../{sign-in => auth}/components/error.tsx | 0 .../{sign-in => auth}/components/heading.tsx | 0 .../{sign-in => auth}/components/index.ts | 2 - .../components/password-strength.tsx | 0 .../{sign-in => auth}/components/step.tsx | 0 .../components/textfields.tsx | 0 .../components/word-tiles.tsx | 0 nym-wallet/src/pages/auth/index.tsx | 9 +++ .../pages/confirm-mnemonic.tsx | 4 +- .../pages/connect-password.tsx | 4 +- .../pages/create-mnemonic.tsx | 4 +- .../pages/create-password.tsx | 5 +- .../pages/existing-account.tsx | 2 +- .../pages/{sign-in => auth}/pages/index.ts | 0 .../pages/signin-mnemonic.tsx | 4 +- .../pages/signin-password.tsx | 4 +- .../pages/verify-mnemonic.tsx | 4 +- .../pages/{sign-in => auth}/pages/welcome.tsx | 0 .../src/pages/{sign-in => auth}/types.ts | 0 nym-wallet/src/pages/balance/balance.tsx | 4 +- .../balance/components/vesting-timeline.tsx | 4 +- nym-wallet/src/pages/balance/index.tsx | 4 +- nym-wallet/src/pages/balance/vesting.tsx | 8 +-- nym-wallet/src/pages/bond/BondForm.tsx | 4 +- nym-wallet/src/pages/bond/SuccessView.tsx | 4 +- .../src/pages/delegate/DelegateForm.tsx | 4 +- nym-wallet/src/pages/delegate/SuccessView.tsx | 4 +- nym-wallet/src/pages/delegate/index.tsx | 4 +- nym-wallet/src/pages/index.ts | 2 +- nym-wallet/src/pages/internal-docs/index.tsx | 4 +- nym-wallet/src/pages/receive/index.tsx | 4 +- .../src/pages/send/SendConfirmation.tsx | 4 +- nym-wallet/src/pages/send/SendForm.tsx | 4 +- nym-wallet/src/pages/send/SendReview.tsx | 4 +- nym-wallet/src/pages/send/SendWizard.tsx | 4 +- nym-wallet/src/pages/send/index.tsx | 4 +- nym-wallet/src/pages/settings/index.tsx | 4 +- nym-wallet/src/pages/settings/node-stats.tsx | 4 +- nym-wallet/src/pages/settings/profile.tsx | 4 +- .../src/pages/settings/system-variables.tsx | 4 +- .../src/pages/settings/useSettingsState.ts | 4 +- .../pages/sign-in/components/page-layout.tsx | 33 --------- .../pages/sign-in/components/render-page.tsx | 11 --- nym-wallet/src/pages/sign-in/index.tsx | 1 - nym-wallet/src/pages/unbond/index.tsx | 4 +- nym-wallet/src/pages/undelegate/index.tsx | 4 +- nym-wallet/src/routes/app.tsx | 57 ++++++++-------- nym-wallet/src/routes/auth.tsx | 54 +++++++++++++++ nym-wallet/src/routes/index.tsx | 12 +++- nym-wallet/src/routes/sign-in.tsx | 48 ------------- nym-wallet/src/theme/index.tsx | 6 +- 73 files changed, 387 insertions(+), 363 deletions(-) create mode 100644 nym-wallet/src/components/Accounts/index.tsx rename nym-wallet/src/context/{sign-in.tsx => auth.tsx} (79%) create mode 100644 nym-wallet/src/layouts/AuthLayout.tsx rename nym-wallet/src/pages/{sign-in => auth}/components/error.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/components/heading.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/components/index.ts (73%) rename nym-wallet/src/pages/{sign-in => auth}/components/password-strength.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/components/step.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/components/textfields.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/components/word-tiles.tsx (100%) create mode 100644 nym-wallet/src/pages/auth/index.tsx rename nym-wallet/src/pages/{sign-in => auth}/pages/confirm-mnemonic.tsx (96%) rename nym-wallet/src/pages/{sign-in => auth}/pages/connect-password.tsx (97%) rename nym-wallet/src/pages/{sign-in => auth}/pages/create-mnemonic.tsx (96%) rename nym-wallet/src/pages/{sign-in => auth}/pages/create-password.tsx (93%) rename nym-wallet/src/pages/{sign-in => auth}/pages/existing-account.tsx (99%) rename nym-wallet/src/pages/{sign-in => auth}/pages/index.ts (100%) rename nym-wallet/src/pages/{sign-in => auth}/pages/signin-mnemonic.tsx (94%) rename nym-wallet/src/pages/{sign-in => auth}/pages/signin-password.tsx (93%) rename nym-wallet/src/pages/{sign-in => auth}/pages/verify-mnemonic.tsx (96%) rename nym-wallet/src/pages/{sign-in => auth}/pages/welcome.tsx (100%) rename nym-wallet/src/pages/{sign-in => auth}/types.ts (100%) delete mode 100644 nym-wallet/src/pages/sign-in/components/page-layout.tsx delete mode 100644 nym-wallet/src/pages/sign-in/components/render-page.tsx delete mode 100644 nym-wallet/src/pages/sign-in/index.tsx create mode 100644 nym-wallet/src/routes/auth.tsx delete mode 100644 nym-wallet/src/routes/sign-in.tsx diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index 584fc37231..99cf45f121 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -7,15 +7,18 @@ import { ListItemButton, ListItemIcon, ListItemText, + Tooltip, Typography, } from '@mui/material'; import { Edit } from '@mui/icons-material'; +import { useClipboard } from 'use-clipboard-copy'; import { AccountsContext } from 'src/context'; import { AccountAvatar } from './AccountAvatar'; export const AccountItem = ({ name, address }: { name: string; address: string }) => { const { selectedAccount, handleSelectAccount, handleAccountToEdit, setDialogToDisplay, setAccountMnemonic } = useContext(AccountsContext); + const { copy, copied } = useClipboard({ copiedTimeout: 1000 }); return ( - {address} + + ) => { + e.stopPropagation(); + copy(address); + }} + sx={{ '&:hover': { color: 'grey.900' } }} + > + {address} + + ); -const NameAccount = ({ onPrev, onNext }: { onPrev: () => void; onNext: (value: string) => void }) => { +const NameAccount = ({ onNext }: { onNext: (value: string) => void }) => { const [value, setValue] = useState(''); return ( @@ -91,9 +90,6 @@ const NameAccount = ({ onPrev, onNext }: { onPrev: () => void; onNext: (value: s setValue(e.target.value)} fullWidth /> - - +); diff --git a/nym-wallet/src/components/Accounts/Accounts.tsx b/nym-wallet/src/components/Accounts/Accounts.tsx index 77fd2eba9f..a59b59c95f 100644 --- a/nym-wallet/src/components/Accounts/Accounts.tsx +++ b/nym-wallet/src/components/Accounts/Accounts.tsx @@ -1,25 +1,18 @@ -import React, { useContext } from 'react'; -import { Button } from '@mui/material'; -import { AccountsContext } from 'src/context'; +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 { AccountAvatar } from './AccountAvatar'; import { MnemonicModal } from './MnemonicModal'; +import { AccountOverview } from './AccountOverview'; +import { MultiAccountHowTo } from './MultiAccountHowTo'; export const Accounts = () => { const { accounts, selectedAccount, setDialogToDisplay } = useContext(AccountsContext); return accounts && selectedAccount ? ( <> - + setDialogToDisplay('Accounts')} /> @@ -27,3 +20,17 @@ export const Accounts = () => { ) : null; }; + +export const SingleAccount = () => { + const [showHowToDialog, setShowHowToDialog] = useState(false); + const { clientDetails } = useContext(AppContext); + return ( + <> + setShowHowToDialog(true)} + /> + setShowHowToDialog(false)} /> + + ); +}; diff --git a/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx b/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx new file mode 100644 index 0000000000..4f9f514c47 --- /dev/null +++ b/nym-wallet/src/components/Accounts/MultiAccountHowTo.tsx @@ -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 }) => ( + + + + Multi accounts + + + + + + How to set up multiple accounts + + + + + + In order to create multiple accounts your wallet need password. + Follow steps below to create password. + + How to create a password for your account + {passwordCreationSteps.map((step, index) => ( + {`${index + 1}. ${step}`} + ))} + + + +); diff --git a/nym-wallet/src/components/Accounts/index.tsx b/nym-wallet/src/components/Accounts/index.tsx index 94e4348091..9e02c40d2b 100644 --- a/nym-wallet/src/components/Accounts/index.tsx +++ b/nym-wallet/src/components/Accounts/index.tsx @@ -1,9 +1,16 @@ -import React from 'react'; -import { AccountsProvider } from 'src/context'; -import { Accounts } from './Accounts'; +import React, { useContext } from 'react'; +import { AccountsProvider, AppContext } from 'src/context'; +import { Accounts, SingleAccount } from './Accounts'; -export const MultiAccounts = () => ( - - - -); +export const MultiAccounts = () => { + const { loginType } = useContext(AppContext); + + if (loginType === 'password') { + return ( + + + + ); + } + return ; +}; From b6b757436e160a13ce9741284b62664e43495cbb Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 9 May 2022 13:36:38 +0100 Subject: [PATCH 23/24] remove edit button until feature delivered --- nym-wallet/src/components/Accounts/AccountItem.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index 99cf45f121..d23d1d37cb 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -63,7 +63,8 @@ export const AccountItem = ({ name, address }: { name: string; address: string } } /> - + {/* edit and remove accounts todo */} + {/* { e.stopPropagation(); @@ -72,7 +73,7 @@ export const AccountItem = ({ name, address }: { name: string; address: string } > - + */} ); From 575845af38a1589bf5a69f68ea036d64fe3b9b84 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 9 May 2022 13:38:38 +0100 Subject: [PATCH 24/24] fix lint errors --- .../src/components/Accounts/AccountItem.tsx | 16 ++-------------- nym-wallet/src/context/mocks/accounts.tsx | 12 ++---------- nym-wallet/src/hooks/useGetBalance.ts | 2 +- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index d23d1d37cb..8344d355a4 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -1,23 +1,11 @@ import React, { useContext } from 'react'; -import { - Box, - IconButton, - ListItem, - ListItemAvatar, - ListItemButton, - ListItemIcon, - ListItemText, - Tooltip, - Typography, -} from '@mui/material'; -import { Edit } from '@mui/icons-material'; +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 }: { name: string; address: string }) => { - const { selectedAccount, handleSelectAccount, handleAccountToEdit, setDialogToDisplay, setAccountMnemonic } = - useContext(AccountsContext); + const { selectedAccount, handleSelectAccount, setDialogToDisplay, setAccountMnemonic } = useContext(AccountsContext); const { copy, copied } = useClipboard({ copiedTimeout: 1000 }); return ( { const [error, setError] = useState(); const [isLoading, setIsLoading] = useState(false); - const handleAddAccount = async ({ - accountName, - mnemonic, - password, - }: { - accountName: string; - mnemonic: string; - password: string; - }) => { + const handleAddAccount = async ({ accountName }: { accountName: string; mnemonic: string; password: string }) => { setIsLoading(true); try { setAccounts((accs) => [...accs, { address: 'abc123', id: accountName }]); @@ -51,7 +43,7 @@ export const MockAccountsProvider: React.FC = ({ children }) => { } }; - const handleGetAcccountMnemonic = async ({ password, accountName }: { password: string; accountName: string }) => { + const handleGetAcccountMnemonic = async ({ accountName }: { password: string; accountName: string }) => { try { setIsLoading(true); const mnemonic = 'test mnemonic'; diff --git a/nym-wallet/src/hooks/useGetBalance.ts b/nym-wallet/src/hooks/useGetBalance.ts index a6d1c8321d..9056f1d4d2 100644 --- a/nym-wallet/src/hooks/useGetBalance.ts +++ b/nym-wallet/src/hooks/useGetBalance.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { invoke } from '@tauri-apps/api'; import { VestingAccountInfo } from 'src/types/rust/vestingaccountinfo'; -import { Balance, Coin, Network, OriginalVestingResponse, Period } from '../types'; +import { Balance, Coin, OriginalVestingResponse, Period } from '../types'; import { getVestingCoins, getVestedCoins,