Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ac4755a03 | |||
| f9d5ef144b | |||
| 8cc4a9e230 | |||
| bf87a4c5ce | |||
| 60d0775785 | |||
| 745243ebd2 | |||
| abe5a5f5d3 | |||
| ec8d659a46 | |||
| 824822c19a | |||
| d594d85d1c | |||
| 770b78f21d | |||
| 068361b044 | |||
| e1239663ef | |||
| a1cd454dc2 | |||
| d22f990300 | |||
| a562984bc5 | |||
| 155f8401f1 | |||
| ce8d74fd69 | |||
| 53573d7c21 | |||
| 42a0bd0492 | |||
| 1bdf8ab6c1 | |||
| cf7315f680 | |||
| 07101a9dc5 | |||
| 35efd07eda | |||
| 522d67670e | |||
| ee6fd8808f | |||
| eec722744c | |||
| 0c3e30ee2c | |||
| 9f104a70cf | |||
| 7aca9848dd | |||
| 7082a12eae | |||
| e5898c9053 |
@@ -1,4 +1,4 @@
|
|||||||
<svg width="210" height="56" viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
|
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
|
||||||
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
|
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
|
||||||
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>
|
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1011 B |
@@ -38,6 +38,7 @@
|
|||||||
"react-hook-form": "^7.14.2",
|
"react-hook-form": "^7.14.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"semver": "^6.3.0",
|
"semver": "^6.3.0",
|
||||||
|
"use-clipboard-copy": "^0.2.0",
|
||||||
"yup": "^0.32.9"
|
"yup": "^0.32.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import { useSnackbar } from 'notistack';
|
|||||||
import { Account, Network, TCurrency, TMixnodeBondDetails } from '../types';
|
import { Account, Network, TCurrency, TMixnodeBondDetails } from '../types';
|
||||||
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
|
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
|
||||||
import { config } from '../../config';
|
import { config } from '../../config';
|
||||||
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signOut } from '../requests';
|
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signInWithPassword, signOut } from '../requests';
|
||||||
import { currencyMap } from '../utils';
|
import { currencyMap } from '../utils';
|
||||||
import { Console } from '../utils/console';
|
import { Console } from '../utils/console';
|
||||||
|
import { TLoginType } from 'src/pages/welcome/types';
|
||||||
|
|
||||||
export const { ADMIN_ADDRESS, IS_DEV_MODE } = config;
|
export const { ADMIN_ADDRESS, IS_DEV_MODE } = config;
|
||||||
|
|
||||||
@@ -32,11 +33,14 @@ type TClientContext = {
|
|||||||
currency?: TCurrency;
|
currency?: TCurrency;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
setIsLoading: (isLoading: boolean) => void;
|
||||||
|
setError: (value?: string) => void;
|
||||||
switchNetwork: (network: Network) => void;
|
switchNetwork: (network: Network) => void;
|
||||||
getBondDetails: () => Promise<void>;
|
getBondDetails: () => Promise<void>;
|
||||||
handleShowSettings: () => void;
|
handleShowSettings: () => void;
|
||||||
handleShowAdmin: () => void;
|
handleShowAdmin: () => void;
|
||||||
logIn: (mnemonic: string) => void;
|
logIn: (opts: { type: 'mnemonic' | 'password'; value: string }) => void;
|
||||||
|
signInWithPassword: (password: string) => void;
|
||||||
logOut: () => void;
|
logOut: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,16 +94,23 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
|||||||
refreshAccount();
|
refreshAccount();
|
||||||
}, [network]);
|
}, [network]);
|
||||||
|
|
||||||
const logIn = async (mnemonic: string) => {
|
const logIn = async ({ type, value }: { type: TLoginType; value: string }) => {
|
||||||
|
if (value.length === 0) {
|
||||||
|
setError(`A ${type} must be provided`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await signInWithMnemonic(mnemonic || '');
|
if (type === 'mnemonic') {
|
||||||
await getBondDetails();
|
await signInWithMnemonic(value);
|
||||||
|
} else {
|
||||||
|
await signInWithPassword(value);
|
||||||
|
}
|
||||||
setNetwork('MAINNET');
|
setNetwork('MAINNET');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setIsLoading(false);
|
|
||||||
setError(e as string);
|
setError(e as string);
|
||||||
} finally {
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
history.push('/balance');
|
history.push('/balance');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -131,6 +142,9 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
|||||||
showSettings,
|
showSettings,
|
||||||
network,
|
network,
|
||||||
currency,
|
currency,
|
||||||
|
setIsLoading,
|
||||||
|
setError,
|
||||||
|
signInWithPassword,
|
||||||
switchNetwork,
|
switchNetwork,
|
||||||
getBondDetails,
|
getBondDetails,
|
||||||
handleShowSettings,
|
handleShowSettings,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { Admin, Welcome, Settings } from './pages';
|
|||||||
import { ErrorFallback } from './components';
|
import { ErrorFallback } from './components';
|
||||||
import { NymWalletTheme, WelcomeTheme } from './theme';
|
import { NymWalletTheme, WelcomeTheme } from './theme';
|
||||||
import { maximizeWindow } from './utils';
|
import { maximizeWindow } from './utils';
|
||||||
|
import { SignInProvider } from './pages/welcome/context';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const { clientDetails } = useContext(ClientContext);
|
const { clientDetails } = useContext(ClientContext);
|
||||||
@@ -20,7 +21,9 @@ const App = () => {
|
|||||||
|
|
||||||
return !clientDetails ? (
|
return !clientDetails ? (
|
||||||
<WelcomeTheme>
|
<WelcomeTheme>
|
||||||
<Welcome />
|
<SignInProvider>
|
||||||
|
<Welcome />
|
||||||
|
</SignInProvider>
|
||||||
</WelcomeTheme>
|
</WelcomeTheme>
|
||||||
) : (
|
) : (
|
||||||
<NymWalletTheme>
|
<NymWalletTheme>
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export const SendConfirmation = ({
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { userBalance, currency, network } = useContext(ClientContext);
|
const { userBalance, currency, network } = useContext(ClientContext);
|
||||||
|
|
||||||
|
if (!data && !error && !isLoading) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@@ -128,13 +128,9 @@ export const SendWizard = () => {
|
|||||||
px: 3,
|
px: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{activeStep === 0 ? (
|
{activeStep === 0 && <SendForm />}
|
||||||
<SendForm />
|
{activeStep === 1 && <SendReview transferFee={transferFee} />}
|
||||||
) : activeStep === 1 ? (
|
<SendConfirmation data={confirmedData} isLoading={isLoading} error={requestError} />
|
||||||
<SendReview transferFee={transferFee} />
|
|
||||||
) : (
|
|
||||||
<SendConfirmation data={confirmedData} isLoading={isLoading} error={requestError} />
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -154,7 +150,16 @@ export const SendWizard = () => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
disableElevation
|
disableElevation
|
||||||
data-testid="button"
|
data-testid="button"
|
||||||
onClick={activeStep === 0 ? handleNextStep : activeStep === 1 ? handleSend : handleFinish}
|
onClick={() => {
|
||||||
|
switch (activeStep) {
|
||||||
|
case 0:
|
||||||
|
return handleNextStep();
|
||||||
|
case 1:
|
||||||
|
return handleSend();
|
||||||
|
default:
|
||||||
|
return handleFinish();
|
||||||
|
}
|
||||||
|
}}
|
||||||
disabled={!!(methods.formState.errors.amount || methods.formState.errors.to || isLoading)}
|
disabled={!!(methods.formState.errors.amount || methods.formState.errors.to || isLoading)}
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -139,13 +139,13 @@ export const SystemVariables = ({
|
|||||||
pt: 0,
|
pt: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{nodeUpdateResponse === 'success' ? (
|
{nodeUpdateResponse === 'success' && (
|
||||||
<Typography sx={{ color: 'success.main', fontWeight: 600 }}>Node successfully updated</Typography>
|
<Typography sx={{ color: 'success.main', fontWeight: 600 }}>Node successfully updated</Typography>
|
||||||
) : nodeUpdateResponse === 'failed' ? (
|
|
||||||
<Typography sx={{ color: 'error.main', fontWeight: 600 }}>Node update failed</Typography>
|
|
||||||
) : (
|
|
||||||
<Fee feeType="UpdateMixnodeConfig" />
|
|
||||||
)}
|
)}
|
||||||
|
{nodeUpdateResponse === 'failed' && (
|
||||||
|
<Typography sx={{ color: 'error.main', fontWeight: 600 }}>Node update failed</Typography>
|
||||||
|
)}
|
||||||
|
{!nodeUpdateResponse && <Fee feeType="UpdateMixnodeConfig" />}
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const SignInContent: React.FC = () => {
|
|||||||
setInputError(undefined);
|
setInputError(undefined);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await logIn(mnemonic || '');
|
await logIn({ type: 'mnemonic', value: mnemonic });
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Alert } from '@mui/material';
|
||||||
|
|
||||||
|
export const Error = ({ message }: { message: string }) => (
|
||||||
|
<Alert severity="error" variant="outlined" data-testid="error" sx={{ color: 'error.light', width: '100%' }}>
|
||||||
|
{message}
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
@@ -2,3 +2,6 @@ export * from './heading';
|
|||||||
export * from './word-tiles';
|
export * from './word-tiles';
|
||||||
export * from './render-page';
|
export * from './render-page';
|
||||||
export * from './password-strength';
|
export * from './password-strength';
|
||||||
|
export * from './error';
|
||||||
|
export * from './textfields';
|
||||||
|
export * from './step';
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { LinearProgress, Stack, Typography, Box } from '@mui/material';
|
|||||||
|
|
||||||
type TStrength = 'weak' | 'medium' | 'strong' | 'init';
|
type TStrength = 'weak' | 'medium' | 'strong' | 'init';
|
||||||
|
|
||||||
const strong = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/;
|
const strong = /^(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/;
|
||||||
const medium = /^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})/;
|
const medium = /^(((?=.*[a-z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[0-9])))(?=.{6,})/;
|
||||||
|
|
||||||
const colorMap = {
|
const colorMap = {
|
||||||
init: 'inherit' as 'inherit',
|
init: 'inherit' as 'inherit',
|
||||||
@@ -41,7 +41,24 @@ const getTextColor = (strength: TStrength) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PasswordStrength = ({ password }: { password: string }) => {
|
const getPasswordStrength = (strength: TStrength) => {
|
||||||
|
switch (strength) {
|
||||||
|
case 'strong':
|
||||||
|
return 100;
|
||||||
|
case 'medium':
|
||||||
|
return 50;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PasswordStrength = ({
|
||||||
|
password,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
password: string;
|
||||||
|
onChange: (isStrong: boolean) => void;
|
||||||
|
}) => {
|
||||||
const [strength, setStrength] = useState<TStrength>('init');
|
const [strength, setStrength] = useState<TStrength>('init');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -62,13 +79,17 @@ export const PasswordStrength = ({ password }: { password: string }) => {
|
|||||||
setStrength('weak');
|
setStrength('weak');
|
||||||
}, [password]);
|
}, [password]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (strength === 'strong') {
|
||||||
|
onChange(true);
|
||||||
|
} else {
|
||||||
|
onChange(false);
|
||||||
|
}
|
||||||
|
}, [strength]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={0.5}>
|
<Stack spacing={0.5}>
|
||||||
<LinearProgress
|
<LinearProgress variant="determinate" color={colorMap[strength]} value={getPasswordStrength(strength)} />
|
||||||
variant="determinate"
|
|
||||||
color={colorMap[strength]}
|
|
||||||
value={strength === 'strong' ? 100 : strength === 'medium' ? 50 : 0}
|
|
||||||
/>
|
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<LockOutlined sx={{ fontSize: 15, color: getTextColor(strength) }} />
|
<LockOutlined sx={{ fontSize: 15, color: getTextColor(strength) }} />
|
||||||
<Typography variant="caption" sx={{ ml: 0.5, color: getTextColor(strength) }}>
|
<Typography variant="caption" sx={{ ml: 0.5, color: getTextColor(strength) }}>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { Typography } from '@mui/material';
|
||||||
|
import { TPages } from '../types';
|
||||||
|
|
||||||
|
export const Step = ({ currentPage, totalSteps }: { currentPage: TPages; totalSteps: number }) => {
|
||||||
|
const mapPage = useCallback(() => {
|
||||||
|
switch (currentPage) {
|
||||||
|
case 'create mnemonic':
|
||||||
|
return 1;
|
||||||
|
case 'verify mnemonic':
|
||||||
|
return 2;
|
||||||
|
case 'create password':
|
||||||
|
return 3;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
|
if (mapPage() === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Typography sx={{ color: 'grey.400' }}>
|
||||||
|
Create account. Step {mapPage()}/{totalSteps}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Box, IconButton, Link, Stack, TextField, Typography } from '@mui/material';
|
||||||
|
import { Visibility, VisibilityOff } from '@mui/icons-material';
|
||||||
|
import { Error } from './error';
|
||||||
|
|
||||||
|
export const MnemonicInput: React.FC<{
|
||||||
|
mnemonic: string;
|
||||||
|
error?: string;
|
||||||
|
onUpdateMnemonic: (mnemonic: string) => void;
|
||||||
|
}> = ({ mnemonic, error, onUpdateMnemonic }) => {
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
return (
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<TextField
|
||||||
|
label="Mnemonic"
|
||||||
|
type={showPassword ? 'input' : 'password'}
|
||||||
|
value={mnemonic}
|
||||||
|
onChange={(e) => onUpdateMnemonic(e.target.value)}
|
||||||
|
multiline={!!showPassword}
|
||||||
|
rows={4}
|
||||||
|
autoFocus
|
||||||
|
fullWidth
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<IconButton onClick={() => setShowPassword((show) => !show)}>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{error && <Error message={error} />}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PasswordInput: React.FC<{
|
||||||
|
password: string;
|
||||||
|
error?: string;
|
||||||
|
label: string;
|
||||||
|
showForgottenPassword?: boolean;
|
||||||
|
autoFocus?: boolean;
|
||||||
|
onUpdatePassword: (password: string) => void;
|
||||||
|
}> = ({ password, label, error, showForgottenPassword, autoFocus, onUpdatePassword }) => {
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Box>
|
||||||
|
<TextField
|
||||||
|
label={label}
|
||||||
|
fullWidth
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => onUpdatePassword(e.target.value)}
|
||||||
|
type={showPassword ? 'input' : 'password'}
|
||||||
|
autoFocus={autoFocus}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<IconButton onClick={() => setShowPassword((show) => !show)}>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{showForgottenPassword && (
|
||||||
|
<Link
|
||||||
|
underline="none"
|
||||||
|
variant="body2"
|
||||||
|
component="div"
|
||||||
|
sx={{ mt: 1, textAlign: 'right', color: 'info.main', cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
Forgotten password?
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{error && <Error message={error} />}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -7,17 +7,19 @@ export const WordTile = ({
|
|||||||
index,
|
index,
|
||||||
disabled,
|
disabled,
|
||||||
onClick,
|
onClick,
|
||||||
|
button,
|
||||||
}: {
|
}: {
|
||||||
mnemonicWord: string;
|
mnemonicWord: string;
|
||||||
index?: number;
|
index?: number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onClick?: boolean;
|
onClick?: boolean;
|
||||||
|
button?: boolean;
|
||||||
}) => (
|
}) => (
|
||||||
<Card
|
<Card
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{
|
sx={{
|
||||||
background: '#151A2C',
|
background: button ? '#151A2C' : 'transparent',
|
||||||
border: '1px solid #3A4053',
|
border: button ? '1px solid #3A4053' : 'none',
|
||||||
cursor: onClick ? 'pointer' : 'default',
|
cursor: onClick ? 'pointer' : 'default',
|
||||||
opacity: disabled ? 0.2 : 1,
|
opacity: disabled ? 0.2 : 1,
|
||||||
}}
|
}}
|
||||||
@@ -40,10 +42,12 @@ export const WordTiles = ({
|
|||||||
mnemonicWords,
|
mnemonicWords,
|
||||||
showIndex,
|
showIndex,
|
||||||
onClick,
|
onClick,
|
||||||
|
buttons,
|
||||||
}: {
|
}: {
|
||||||
mnemonicWords?: TMnemonicWords;
|
mnemonicWords?: TMnemonicWords;
|
||||||
showIndex?: boolean;
|
showIndex?: boolean;
|
||||||
onClick?: ({ name, index }: { name: string; index: number }) => void;
|
onClick?: ({ name, index }: { name: string; index: number }) => void;
|
||||||
|
buttons?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
if (mnemonicWords) {
|
if (mnemonicWords) {
|
||||||
return (
|
return (
|
||||||
@@ -55,6 +59,7 @@ export const WordTiles = ({
|
|||||||
index={showIndex ? index : undefined}
|
index={showIndex ? index : undefined}
|
||||||
onClick={!!onClick}
|
onClick={!!onClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
button={buttons}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import React, { createContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { createMnemonic, signInWithMnemonic } from 'src/requests';
|
||||||
|
import { TMnemonicWords } from '../types';
|
||||||
|
|
||||||
|
export const SignInContext = createContext({} as TSignInContent);
|
||||||
|
|
||||||
|
export type TSignInContent = {
|
||||||
|
error?: string;
|
||||||
|
password: string;
|
||||||
|
mnemonic: string;
|
||||||
|
mnemonicWords: TMnemonicWords;
|
||||||
|
setError: (err?: string) => void;
|
||||||
|
setMnemonic: (mnc: string) => void;
|
||||||
|
generateMnemonic: () => Promise<void>;
|
||||||
|
validateMnemonic: () => Promise<void>;
|
||||||
|
setPassword: (paswd: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mnemonicToArray = (mnemonic: string): TMnemonicWords =>
|
||||||
|
mnemonic
|
||||||
|
.split(' ')
|
||||||
|
.reduce((a, c: string, index) => [...a, { name: c, index: index + 1, disabled: false }], [] as TMnemonicWords);
|
||||||
|
|
||||||
|
export const SignInProvider: React.FC = ({ children }) => {
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [mnemonic, setMnemonic] = useState('');
|
||||||
|
const [mnemonicWords, setMnemonicWords] = useState<TMnemonicWords>([]);
|
||||||
|
const [error, setError] = useState<string>();
|
||||||
|
|
||||||
|
const generateMnemonic = async () => {
|
||||||
|
const mnemonicPhrase = await createMnemonic();
|
||||||
|
setMnemonic(mnemonicPhrase);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateMnemonic = async () => {
|
||||||
|
try {
|
||||||
|
await signInWithMnemonic(mnemonic);
|
||||||
|
} catch (e) {
|
||||||
|
setError(e as string);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mnemonic.length > 0) {
|
||||||
|
const mnemonicArray = mnemonicToArray(mnemonic);
|
||||||
|
setMnemonicWords(mnemonicArray);
|
||||||
|
} else {
|
||||||
|
setMnemonicWords([]);
|
||||||
|
}
|
||||||
|
}, [mnemonic]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SignInContext.Provider
|
||||||
|
value={useMemo(
|
||||||
|
() => ({
|
||||||
|
error,
|
||||||
|
password,
|
||||||
|
mnemonic,
|
||||||
|
mnemonicWords,
|
||||||
|
setError,
|
||||||
|
setMnemonic,
|
||||||
|
generateMnemonic,
|
||||||
|
validateMnemonic,
|
||||||
|
setPassword,
|
||||||
|
}),
|
||||||
|
[error, password, mnemonic, mnemonicWords],
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SignInContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,31 +1,24 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { NymLogo } from '@nymproject/react';
|
import { Stack, Box, CircularProgress } from '@mui/material';
|
||||||
import { CircularProgress, Stack, Box } from '@mui/material';
|
import { NymWordmark } from '@nymproject/react';
|
||||||
import { ExistingAccount, WelcomeContent } from './pages';
|
import {
|
||||||
import { TPages } from './types';
|
CreatePassword,
|
||||||
import { RenderPage } from './components';
|
ExistingAccount,
|
||||||
import { CreateAccountContent } from './_legacy_create-account';
|
CreateMnemonic,
|
||||||
|
VerifyMnemonic,
|
||||||
|
WelcomeContent,
|
||||||
|
SignInMnemonic,
|
||||||
|
} from './pages';
|
||||||
|
import { TLoginType, TPages } from './types';
|
||||||
|
import { RenderPage, Step } from './components';
|
||||||
import { ClientContext } from '../../context/main';
|
import { ClientContext } from '../../context/main';
|
||||||
|
import { SignInPassword } from './pages/signin-password';
|
||||||
// const testMnemonic =
|
|
||||||
// 'futuristic big receptive caption saw hug odd spoon internal dime bike rake helpless left distribution gusty eyes beg enormous word influence trashy pets curl';
|
|
||||||
//
|
|
||||||
// const mnemonicToArray = (mnemonic: string): TMnemonicWords =>
|
|
||||||
// mnemonic
|
|
||||||
// .split(' ')
|
|
||||||
// .reduce((a, c: string, index) => [...a, { name: c, index: index + 1, disabled: false }], [] as TMnemonicWords);
|
|
||||||
|
|
||||||
export const Welcome = () => {
|
export const Welcome = () => {
|
||||||
const [page, setPage] = useState<TPages>('welcome');
|
const [page, setPage] = useState<TPages>('welcome');
|
||||||
// const [mnemonicWords, setMnemonicWords] = useState<TMnemonicWords>();
|
const [loginType, setLoginType] = useState<TLoginType>('mnemonic');
|
||||||
|
|
||||||
const { isLoading } = useContext(ClientContext);
|
const { isLoading } = useContext(ClientContext);
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// const mnemonicArray = mnemonicToArray(testMnemonic)
|
|
||||||
// setMnemonicWords(mnemonicArray)
|
|
||||||
// }, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -50,28 +43,39 @@ export const Welcome = () => {
|
|||||||
<CircularProgress size={72} />
|
<CircularProgress size={72} />
|
||||||
) : (
|
) : (
|
||||||
<Stack spacing={3} alignItems="center" sx={{ width: 1080 }}>
|
<Stack spacing={3} alignItems="center" sx={{ width: 1080 }}>
|
||||||
<NymLogo width={75} />
|
<NymWordmark width={75} />
|
||||||
|
<Step currentPage={page} totalSteps={3} />
|
||||||
<RenderPage page={page}>
|
<RenderPage page={page}>
|
||||||
<WelcomeContent
|
<WelcomeContent
|
||||||
onUseExisting={() => setPage('existing account')}
|
onUseExisting={() => setPage('existing account')}
|
||||||
onCreateAccountComplete={() => setPage('legacy create account')}
|
onCreateAccount={() => setPage('create mnemonic')}
|
||||||
page="welcome"
|
page="welcome"
|
||||||
/>
|
/>
|
||||||
|
<CreateMnemonic
|
||||||
<CreateAccountContent page="legacy create account" showSignIn={() => setPage('existing account')} />
|
onNext={() => setPage('verify mnemonic')}
|
||||||
{/* <MnemonicWords
|
onPrev={() => setPage('create password')}
|
||||||
mnemonicWords={mnemonicWords}
|
page="create mnemonic"
|
||||||
onNext={() => setPage('verify mnemonic')}
|
/>
|
||||||
onPrev={() => setPage('welcome')}
|
<VerifyMnemonic onNext={() => setPage('create password')} onPrev={() => {}} page="verify mnemonic" />
|
||||||
page="create account"
|
<CreatePassword
|
||||||
/>
|
onSkip={() => {
|
||||||
<VerifyMnemonic
|
setLoginType('mnemonic');
|
||||||
mnemonicWords={mnemonicWords}
|
setPage('existing account');
|
||||||
onComplete={() => setPage('create password')}
|
}}
|
||||||
page="verify mnemonic"
|
onNext={() => {
|
||||||
/>
|
setLoginType('password');
|
||||||
<CreatePassword page="create password" /> */}
|
setPage('existing account');
|
||||||
<ExistingAccount onPrev={() => setPage('welcome')} page="existing account" />
|
}}
|
||||||
|
page="create password"
|
||||||
|
/>
|
||||||
|
<ExistingAccount
|
||||||
|
onPrev={() => setPage('welcome')}
|
||||||
|
page="existing account"
|
||||||
|
loginType={loginType}
|
||||||
|
setLoginType={(loginType) => setLoginType(loginType)}
|
||||||
|
/>
|
||||||
|
<SignInMnemonic onPrev={() => setPage('welcome')} page="sign in with mnemonic" />
|
||||||
|
<SignInPassword onPrev={() => setPage('welcome')} page="sign in with password" />
|
||||||
</RenderPage>
|
</RenderPage>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { useContext, useEffect } from 'react';
|
||||||
|
import { Alert, Button, Grid, Stack, Typography } from '@mui/material';
|
||||||
|
import { Check, ContentCopySharp } from '@mui/icons-material';
|
||||||
|
import { useClipboard } from 'use-clipboard-copy';
|
||||||
|
import { WordTiles } from '../components';
|
||||||
|
import { TPages } from '../types';
|
||||||
|
import { SignInContext } from '../context';
|
||||||
|
|
||||||
|
export const CreateMnemonic = ({ onNext }: { page: TPages; onNext: () => void; onPrev: () => void }) => {
|
||||||
|
const { mnemonic, mnemonicWords, generateMnemonic, validateMnemonic, setMnemonic, setError } =
|
||||||
|
useContext(SignInContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
generateMnemonic();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { copy, copied } = useClipboard({ copiedTimeout: 5000 });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack alignItems="center" spacing={3}>
|
||||||
|
<Typography sx={{ color: 'common.white', fontWeight: 600 }} textAlign="center">
|
||||||
|
Write down your mnemonic
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Alert variant="outlined" severity="warning" sx={{ textAlign: 'center' }}>
|
||||||
|
<Typography>Below is your 24 word mnemonic, please store the mnemonic in a safe place.</Typography>
|
||||||
|
<Typography>This is the only way to access your wallet!</Typography>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<WordTiles mnemonicWords={mnemonicWords} showIndex />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
color="inherit"
|
||||||
|
disableElevation
|
||||||
|
size="large"
|
||||||
|
onClick={() => {
|
||||||
|
copy(mnemonic);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
width: 250,
|
||||||
|
}}
|
||||||
|
endIcon={!copied ? <ContentCopySharp /> : <Check color="success" />}
|
||||||
|
>
|
||||||
|
Copy mnemonic
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disableElevation
|
||||||
|
size="large"
|
||||||
|
onClick={onNext}
|
||||||
|
sx={{ width: 250 }}
|
||||||
|
disabled={!copied}
|
||||||
|
>
|
||||||
|
I saved my mnemonic
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,60 +1,74 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { Button, FormControl, Grid, IconButton, Stack, TextField } from '@mui/material';
|
import { Alert, Button, FormControl, Stack } from '@mui/material';
|
||||||
import { VisibilityOff, Visibility } from '@mui/icons-material';
|
import { useSnackbar } from 'notistack';
|
||||||
|
import { TPages } from '../types';
|
||||||
import { Subtitle, Title, PasswordStrength } from '../components';
|
import { Subtitle, Title, PasswordStrength } from '../components';
|
||||||
|
import { PasswordInput } from '../components/textfields';
|
||||||
|
import { SignInContext } from '../context';
|
||||||
|
import { createPassword } from '../../../requests';
|
||||||
|
|
||||||
export const CreatePassword = () => {
|
export const CreatePassword = ({ onSkip, onNext }: { page: TPages; onNext: () => void; onSkip: () => void }) => {
|
||||||
const [password, setPassword] = useState<string>('');
|
const { password, setPassword } = useContext(SignInContext);
|
||||||
const [confirmedPassword, setConfirmedPassword] = useState<string>();
|
const [confirmedPassword, setConfirmedPassword] = useState<string>('');
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [isStrongPassword, setIsStrongPassword] = useState(false);
|
||||||
const [showConfirmedPassword, setShowConfirmedPassword] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const { mnemonic } = useContext(SignInContext);
|
||||||
|
|
||||||
|
const handleSkip = () => {
|
||||||
|
setPassword('');
|
||||||
|
onSkip();
|
||||||
|
};
|
||||||
|
|
||||||
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
|
||||||
|
const storePassword = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
await createPassword({ mnemonic, password });
|
||||||
|
enqueueSnackbar('Password successfully created', { variant: 'success' });
|
||||||
|
setPassword('');
|
||||||
|
onNext();
|
||||||
|
} catch (e) {
|
||||||
|
enqueueSnackbar(e as string, { variant: 'error' });
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Stack spacing={3} alignItems="center" minWidth="50%">
|
||||||
<Title title="Create password" />
|
<Title title="Create optional password" />
|
||||||
<Subtitle subtitle="Create a strong password. Min 8 characters, at least one capital letter, number and special symbol" />
|
<Subtitle subtitle="Password should be min 8 characters, at least one number and one symbol" />
|
||||||
<Grid container justifyContent="center">
|
<FormControl fullWidth>
|
||||||
<Grid item xs={6}>
|
<Stack spacing={2}>
|
||||||
<FormControl fullWidth>
|
<>
|
||||||
<Stack spacing={2}>
|
<PasswordInput
|
||||||
<TextField
|
password={password}
|
||||||
label="Password"
|
onUpdatePassword={(pswd) => setPassword(pswd)}
|
||||||
value={password}
|
label="Password"
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
autoFocus
|
||||||
type={showPassword ? 'input' : 'password'}
|
/>
|
||||||
InputProps={{
|
<PasswordStrength password={password} onChange={(isStrong) => setIsStrongPassword(isStrong)} />
|
||||||
endAdornment: (
|
</>
|
||||||
<IconButton onClick={() => setShowPassword((show) => !show)}>
|
<PasswordInput
|
||||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
password={confirmedPassword}
|
||||||
</IconButton>
|
onUpdatePassword={(pswd) => setConfirmedPassword(pswd)}
|
||||||
),
|
label="Confirm password"
|
||||||
}}
|
/>
|
||||||
/>
|
<Button
|
||||||
<PasswordStrength password={password} />
|
size="large"
|
||||||
<TextField
|
variant="contained"
|
||||||
label="Confirm password"
|
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
|
||||||
value={confirmedPassword}
|
onClick={storePassword}
|
||||||
onChange={(e) => setConfirmedPassword(e.target.value)}
|
>
|
||||||
type={showConfirmedPassword ? 'input' : 'password'}
|
Next
|
||||||
InputProps={{
|
</Button>
|
||||||
endAdornment: (
|
<Button size="large" color="info" onClick={handleSkip}>
|
||||||
<IconButton onClick={() => setShowConfirmedPassword((show) => !show)}>
|
Skip and sign in with mnemonic
|
||||||
{showConfirmedPassword ? <VisibilityOff /> : <Visibility />}
|
</Button>
|
||||||
</IconButton>
|
</Stack>
|
||||||
),
|
</FormControl>
|
||||||
}}
|
</Stack>
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="large"
|
|
||||||
variant="contained"
|
|
||||||
disabled={password !== confirmedPassword || password.length === 0}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</FormControl>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,55 +1,78 @@
|
|||||||
/* eslint-disable react/no-unused-prop-types */
|
/* eslint-disable react/no-unused-prop-types */
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { Alert, Button, Stack, TextField } from '@mui/material';
|
import { Alert, Button, FormControl, Stack, ToggleButton, ToggleButtonGroup } from '@mui/material';
|
||||||
import { Subtitle } from '../components';
|
import { ClientContext } from 'src/context/main';
|
||||||
import { ClientContext } from '../../../context/main';
|
import { Subtitle, MnemonicInput, PasswordInput } from '../components';
|
||||||
import { TPages } from '../types';
|
import { TLoginType, TPages } from '../types';
|
||||||
|
|
||||||
export const ExistingAccount: React.FC<{ page: TPages; onPrev: () => void }> = ({ onPrev }) => {
|
export const ExistingAccount: React.FC<{
|
||||||
const [mnemonic, setMnemonic] = useState<string>('');
|
page: TPages;
|
||||||
|
loginType: TLoginType;
|
||||||
const { logIn, error } = useContext(ClientContext);
|
setLoginType: (type: 'mnemonic' | 'password') => void;
|
||||||
|
onPrev: () => void;
|
||||||
const handleSignIn = async () => {
|
}> = ({ loginType, setLoginType, onPrev }) => {
|
||||||
await logIn(mnemonic);
|
const [password, setPassword] = useState('');
|
||||||
};
|
const [mnemonic, setMnemonic] = useState('');
|
||||||
|
const { setError, logIn, error } = useContext(ClientContext);
|
||||||
const handleSignInOnEnter = ({ key }: React.KeyboardEvent<HTMLDivElement>) => {
|
|
||||||
if (key.toLowerCase() === 'enter') {
|
|
||||||
logIn(mnemonic);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={2} sx={{ width: 400 }} alignItems="center">
|
<>
|
||||||
<Subtitle subtitle="Enter your mnemonic from existing wallet" />
|
<Subtitle subtitle={`Enter your ${loginType} for existing wallet`} />
|
||||||
<TextField
|
<Alert variant="outlined" severity="info">
|
||||||
value={mnemonic}
|
You can use either a mnemonic or a password to access your wallet
|
||||||
onChange={(e) => setMnemonic(e.target.value)}
|
</Alert>
|
||||||
multiline
|
<Stack spacing={2} minWidth="50%">
|
||||||
rows={5}
|
<ToggleButtonGroup
|
||||||
fullWidth
|
fullWidth
|
||||||
onKeyDown={handleSignInOnEnter}
|
value={loginType}
|
||||||
/>
|
exclusive
|
||||||
{error && (
|
onChange={(_: React.MouseEvent<HTMLElement>, value: TLoginType) => {
|
||||||
<Alert severity="error" variant="outlined" data-testid="error" sx={{ color: 'error.light', width: '100%' }}>
|
setError(undefined);
|
||||||
{error}
|
setLoginType(value);
|
||||||
</Alert>
|
}}
|
||||||
)}
|
>
|
||||||
|
<ToggleButton value="mnemonic">Mnemonic</ToggleButton>
|
||||||
|
<ToggleButton value="password">Password</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
{loginType === 'mnemonic' && (
|
||||||
|
<MnemonicInput mnemonic={mnemonic} onUpdateMnemonic={(mnc) => setMnemonic(mnc)} error={error} />
|
||||||
|
)}
|
||||||
|
{loginType === 'password' && (
|
||||||
|
<PasswordInput
|
||||||
|
password={password}
|
||||||
|
onUpdatePassword={(pswd) => setPassword(pswd)}
|
||||||
|
label="Password"
|
||||||
|
autoFocus
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button variant="contained" size="large" fullWidth onClick={handleSignIn}>
|
<Button
|
||||||
Sign in
|
variant="contained"
|
||||||
</Button>
|
size="large"
|
||||||
<Button
|
fullWidth
|
||||||
variant="outlined"
|
onClick={() => logIn({ type: loginType, value: loginType === 'mnemonic' ? mnemonic : password })}
|
||||||
disableElevation
|
>
|
||||||
size="large"
|
{`Sign in with ${loginType}`}
|
||||||
onClick={onPrev}
|
</Button>
|
||||||
fullWidth
|
<Button
|
||||||
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' } }}
|
variant="outlined"
|
||||||
>
|
disableElevation
|
||||||
Back
|
size="large"
|
||||||
</Button>
|
onClick={() => {
|
||||||
</Stack>
|
setError(undefined);
|
||||||
|
onPrev();
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' } }}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</FormControl>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from './welcome-content';
|
export * from './welcome-content';
|
||||||
export * from './mnemonic-words';
|
export * from './create-mnemonic';
|
||||||
export * from './verify-mnemonic';
|
export * from './verify-mnemonic';
|
||||||
export * from './create-password';
|
export * from './create-password';
|
||||||
export * from './existing-account';
|
export * from './existing-account';
|
||||||
|
export * from './signin-mnemonic';
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Alert, Button, Typography } from '@mui/material';
|
|
||||||
import { WordTiles } from '../components';
|
|
||||||
import { TMnemonicWords } from '../types';
|
|
||||||
|
|
||||||
export const MnemonicWords = ({
|
|
||||||
mnemonicWords,
|
|
||||||
onNext,
|
|
||||||
onPrev,
|
|
||||||
}: {
|
|
||||||
mnemonicWords?: TMnemonicWords;
|
|
||||||
onNext: () => void;
|
|
||||||
onPrev: () => void;
|
|
||||||
}) => (
|
|
||||||
<>
|
|
||||||
<Typography sx={{ color: 'common.white', fontWeight: 600 }}>Write down your mnemonic</Typography>
|
|
||||||
<Alert icon={false} severity="info" sx={{ bgcolor: '#18263B', color: '#50ABFF', width: 625 }}>
|
|
||||||
Please store your mnemonic in a safe place. This is the only way to access your wallet!
|
|
||||||
</Alert>
|
|
||||||
<WordTiles mnemonicWords={mnemonicWords} showIndex />
|
|
||||||
<Button variant="contained" color="primary" disableElevation size="large" onClick={onNext} sx={{ width: 250 }}>
|
|
||||||
Verify mnemonic
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
disableElevation
|
|
||||||
size="large"
|
|
||||||
onClick={onPrev}
|
|
||||||
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' }, width: 250 }}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { Button, FormControl, Stack } from '@mui/material';
|
||||||
|
import { MnemonicInput, Subtitle } from '../components';
|
||||||
|
import { ClientContext } from '../../../context/main';
|
||||||
|
import { TPages } from '../types';
|
||||||
|
|
||||||
|
export const SignInMnemonic = ({ page, onPrev }: { page: TPages; onPrev: () => void }) => {
|
||||||
|
const [mnemonic, setMnemonic] = useState('');
|
||||||
|
const { setError, logIn, error } = useContext(ClientContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing={2} alignItems="center" minWidth="50%">
|
||||||
|
<Subtitle subtitle="Enter a mnemonic to sign in" />
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<MnemonicInput mnemonic={mnemonic} onUpdateMnemonic={(mnc) => setMnemonic(mnc)} error={error} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => logIn({ type: 'mnemonic', value: mnemonic })}
|
||||||
|
>
|
||||||
|
{`Sign in with mnemonic`}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
disableElevation
|
||||||
|
size="large"
|
||||||
|
onClick={() => {
|
||||||
|
setError(undefined);
|
||||||
|
onPrev();
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' } }}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</FormControl>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { Button, FormControl, Stack } from '@mui/material';
|
||||||
|
import { PasswordInput, Subtitle } from '../components';
|
||||||
|
import { ClientContext } from '../../../context/main';
|
||||||
|
import { TPages } from '../types';
|
||||||
|
|
||||||
|
export const SignInPassword = ({ onPrev }: { page: TPages; onPrev: () => void }) => {
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const { setError, logIn, error } = useContext(ClientContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack spacing={2} alignItems="center" minWidth="50%">
|
||||||
|
<Subtitle subtitle="Enter a password to sign in" />
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<PasswordInput
|
||||||
|
label="Enter password"
|
||||||
|
password={password}
|
||||||
|
onUpdatePassword={(pswd) => setPassword(pswd)}
|
||||||
|
error={error}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => logIn({ type: 'password', value: password })}
|
||||||
|
>
|
||||||
|
{`Sign in with password`}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
disableElevation
|
||||||
|
size="large"
|
||||||
|
onClick={() => {
|
||||||
|
setError(undefined);
|
||||||
|
onPrev();
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' } }}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</FormControl>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,22 +1,19 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { Button } from '@mui/material';
|
import { Button, Stack } from '@mui/material';
|
||||||
import { HiddenWords, Subtitle, Title, WordTiles } from '../components';
|
import { HiddenWords, Subtitle, Title, WordTiles } from '../components';
|
||||||
import { THiddenMnemonicWord, THiddenMnemonicWords, TMnemonicWord, TMnemonicWords } from '../types';
|
import { THiddenMnemonicWord, THiddenMnemonicWords, TMnemonicWord, TMnemonicWords, TPages } from '../types';
|
||||||
import { randomNumberBetween } from '../../../utils';
|
import { randomNumberBetween } from '../../../utils';
|
||||||
|
import { SignInContext } from '../context';
|
||||||
|
|
||||||
const numberOfRandomWords = 4;
|
const numberOfRandomWords = 6;
|
||||||
|
|
||||||
export const VerifyMnemonic = ({
|
export const VerifyMnemonic = ({ onNext }: { page: TPages; onNext: () => void; onPrev: () => void }) => {
|
||||||
mnemonicWords,
|
|
||||||
onComplete,
|
|
||||||
}: {
|
|
||||||
mnemonicWords?: TMnemonicWords;
|
|
||||||
onComplete: () => void;
|
|
||||||
}) => {
|
|
||||||
const [randomWords, setRandomWords] = useState<TMnemonicWords>();
|
const [randomWords, setRandomWords] = useState<TMnemonicWords>();
|
||||||
const [hiddenRandomWords, setHiddenRandomWords] = useState<THiddenMnemonicWords>();
|
const [hiddenRandomWords, setHiddenRandomWords] = useState<THiddenMnemonicWords>();
|
||||||
const [currentSelection, setCurrentSelection] = useState(0);
|
const [currentSelection, setCurrentSelection] = useState(0);
|
||||||
|
|
||||||
|
const { mnemonicWords } = useContext(SignInContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mnemonicWords) {
|
if (mnemonicWords) {
|
||||||
const newRandomWords = getRandomEntriesFromArray<TMnemonicWord>(mnemonicWords, numberOfRandomWords);
|
const newRandomWords = getRandomEntriesFromArray<TMnemonicWord>(mnemonicWords, numberOfRandomWords);
|
||||||
@@ -48,16 +45,21 @@ export const VerifyMnemonic = ({
|
|||||||
<WordTiles
|
<WordTiles
|
||||||
mnemonicWords={randomWords}
|
mnemonicWords={randomWords}
|
||||||
onClick={currentSelection !== numberOfRandomWords ? revealWord : undefined}
|
onClick={currentSelection !== numberOfRandomWords ? revealWord : undefined}
|
||||||
|
buttons
|
||||||
/>
|
/>
|
||||||
<Button
|
<Stack spacing={3} sx={{ width: 300 }}>
|
||||||
variant="contained"
|
<Button
|
||||||
sx={{ width: 300 }}
|
variant="contained"
|
||||||
size="large"
|
fullWidth
|
||||||
disabled={currentSelection !== numberOfRandomWords}
|
size="large"
|
||||||
onClick={onComplete}
|
disabled={currentSelection !== numberOfRandomWords}
|
||||||
>
|
onClick={() => {
|
||||||
Next
|
onNext();
|
||||||
</Button>
|
}}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,36 +7,18 @@ import { TPages } from '../types';
|
|||||||
export const WelcomeContent: React.FC<{
|
export const WelcomeContent: React.FC<{
|
||||||
page: TPages;
|
page: TPages;
|
||||||
onUseExisting: () => void;
|
onUseExisting: () => void;
|
||||||
onCreateAccountComplete: () => void;
|
onCreateAccount: () => void;
|
||||||
}> = ({ onUseExisting, onCreateAccountComplete }) => (
|
}> = ({ onUseExisting, onCreateAccount }) => (
|
||||||
<>
|
<>
|
||||||
<Title title="Welcome to NYM" />
|
<Title title="Welcome to NYM" />
|
||||||
<SubtitleSlick subtitle="Next generation of privacy" />
|
<SubtitleSlick subtitle="Next generation of privacy" />
|
||||||
<Stack spacing={3} sx={{ width: 300 }}>
|
<Stack spacing={3} minWidth={300}>
|
||||||
<Button
|
<Button fullWidth color="primary" variant="contained" size="large" onClick={onUseExisting}>
|
||||||
fullWidth
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
disableElevation
|
|
||||||
size="large"
|
|
||||||
onClick={onCreateAccountComplete}
|
|
||||||
>
|
|
||||||
Create Account
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
size="large"
|
|
||||||
sx={{
|
|
||||||
color: 'common.white',
|
|
||||||
border: '1px solid white',
|
|
||||||
'&:hover': { border: '1px solid white', '&:hover': { background: 'none' } },
|
|
||||||
}}
|
|
||||||
onClick={onUseExisting}
|
|
||||||
disableRipple
|
|
||||||
>
|
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button fullWidth color="inherit" disableElevation size="large" onClick={onCreateAccount}>
|
||||||
|
Create account
|
||||||
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
export type TPages =
|
export type TPages =
|
||||||
| 'welcome'
|
| 'welcome'
|
||||||
| 'create account'
|
| 'create mnemonic'
|
||||||
| 'verify mnemonic'
|
| 'verify mnemonic'
|
||||||
| 'create password'
|
| 'create password'
|
||||||
| 'existing account'
|
| 'existing account'
|
||||||
| 'select network'
|
| 'select network'
|
||||||
| 'legacy create account';
|
| 'legacy create account'
|
||||||
|
| 'sign in with mnemonic'
|
||||||
|
| 'sign in with password';
|
||||||
|
|
||||||
export type TMnemonicWord = {
|
export type TMnemonicWord = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -17,3 +19,5 @@ export type TMnemonicWords = TMnemonicWord[];
|
|||||||
export type THiddenMnemonicWord = { hidden: boolean } & TMnemonicWord;
|
export type THiddenMnemonicWord = { hidden: boolean } & TMnemonicWord;
|
||||||
|
|
||||||
export type THiddenMnemonicWords = THiddenMnemonicWord[];
|
export type THiddenMnemonicWords = THiddenMnemonicWord[];
|
||||||
|
|
||||||
|
export type TLoginType = 'mnemonic' | 'password';
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { Account, TCreateAccount } from '../types';
|
import { Account } from '../types';
|
||||||
|
|
||||||
export const createAccount = async (): Promise<TCreateAccount> => {
|
export const createMnemonic = async (): Promise<string> => invoke('create_new_mnemonic');
|
||||||
const res: TCreateAccount = await invoke('create_new_account');
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createMnemonic = async (): Promise<string> => {
|
export const createPassword = async ({ mnemonic, password }: { mnemonic: string; password: string }): Promise<void> => {
|
||||||
const res: string = await invoke('create_new_mnemonic');
|
await invoke('create_password', { mnemonic, password });
|
||||||
return res;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signInWithMnemonic = async (mnemonic: string): Promise<Account> => {
|
export const signInWithMnemonic = async (mnemonic: string): Promise<Account> => {
|
||||||
@@ -16,6 +12,11 @@ export const signInWithMnemonic = async (mnemonic: string): Promise<Account> =>
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const signInWithPassword = async (password: string): Promise<Account> => {
|
||||||
|
const res: Account = await invoke('sign_in_with_password', { password });
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const signOut = async (): Promise<void> => {
|
export const signOut = async (): Promise<void> => {
|
||||||
await invoke('logout');
|
await invoke('logout');
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user