Add onboarding component and generate token step

This commit is contained in:
Reckless_Satoshi 2023-02-16 14:12:34 -08:00
parent 45af12535d
commit 1ce2dc4200
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
7 changed files with 537 additions and 205 deletions

View File

@ -352,6 +352,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
<RobotPage
setPage={setPage}
setCurrentOrder={setCurrentOrder}
windowSize={windowSize}
robot={robot}
setRobot={setRobot}
baseUrl={baseUrl}

View File

@ -0,0 +1,158 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Alert, Button, Collapse, Grid, Typography, useTheme } from '@mui/material';
import { useParams } from 'react-router-dom';
import { Page } from '../NavBar';
import { Robot } from '../../models';
import { Casino, Download, ContentCopy, SmartToy, Bolt, Check } from '@mui/icons-material';
import RobotAvatar from '../../components/RobotAvatar';
import TokenInput from './TokenInput';
import { genBase62Token } from '../../utils';
interface OnboardingProps {
robot: Robot;
inputToken: string;
setInputToken: (state: string) => void;
getGenerateRobot: (token: string) => void;
badRequest: string | undefined;
setPage: (state: Page) => void;
baseUrl: string;
}
const Onboarding = ({
robot,
inputToken,
setInputToken,
setRobot,
badRequest,
getGenerateRobot,
setPage,
baseUrl,
}: OnboardingProps): JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
const [step, setStep] = useState<'1' | '2' | '3'>('1');
const [generatedToken, setGeneratedToken] = useState<boolean>(false);
return (
<Grid container direction='column' alignItems='center' spacing={2}>
<Grid item>
<Typography variant='h5' color={step == '1' ? 'text.primary' : 'text.disabled'}>
{t('1. Generate a token')}
</Typography>
<Collapse in={step == '1'}>
<Grid container direction='column' alignItems='center' spacing={1} paddingLeft={4}>
<Grid item>
<Typography>
{t(
'This temporary key gives you access to a unique and private robot identity for your trade.',
)}
</Typography>
</Grid>
{!generatedToken ? (
<Grid item>
<Button
autoFocus
onClick={() => {
setGeneratedToken(true);
setInputToken(genBase62Token(36));
}}
variant='contained'
size='large'
>
<Casino />
{t('Generate token')}
</Button>
</Grid>
) : (
<Grid item>
<Collapse in={generatedToken}>
<Grid container direction='column' alignItems='center' spacing={1}>
<Grid item>
<Alert variant='outlined' severity='info'>
<b>{`${t('Store it somewhere safe!')} `}</b>
{t(
`This token is the one and only key to your robot and trade. You will need it later to recover your order or check it's status.`,
)}
</Alert>
</Grid>
<Grid item sx={{ width: '100%' }}>
<TokenInput
inputToken={inputToken}
setInputToken={setInputToken}
setRobot={setRobot}
badRequest={badRequest}
robot={robot}
onPressEnter={() => null}
badRequest={badRequest}
/>
</Grid>
<Grid item>
<Typography>
{t(
'You can also add your own random characters into the token or roll the dice again',
)}
<Button
size='small'
onClick={() => {
setGeneratedToken(true);
setInputToken(genBase62Token(36));
}}
>
<Casino />
{t('Generate another one')}
</Button>
</Typography>
</Grid>
<Grid item>
<Button
onClick={() => {
setStep('2');
getGenerateRobot(inputToken);
}}
variant='contained'
size='large'
>
<Check />
{t('Continue')}
</Button>
</Grid>
</Grid>
</Collapse>
</Grid>
)}
</Grid>
</Collapse>
<Grid item>
<Typography variant='h5' color={step == '2' ? 'text.primary' : 'text.disabled'}>
{t('2. Meet your robot identity')}
</Typography>
</Grid>
<Collapse in={step == '2'}>
<Grid container direction='column' alignItems='center' spacing={1} paddingLeft={4}>
<Grid item>
<Typography>{t('Your robot is under consctruction!')}</Typography>
</Grid>
<Grid item>
<Typography>{t('Hi! My name is')}</Typography>
</Grid>
</Grid>
</Collapse>
<Grid item>
<Typography variant='h5' color={step == '3' ? 'text.primary' : 'text.disabled'}>
{t('3. Browse or create an order')}
</Typography>
</Grid>
</Grid>
</Grid>
);
};
export default Onboarding;

View File

@ -0,0 +1,38 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Collapse, Grid, Typography, useTheme } from '@mui/material';
import { useParams } from 'react-router-dom';
import { Page } from '../NavBar';
import { Robot } from '../../models';
import { Casino, Download, ContentCopy, SmartToy, Bolt } from '@mui/icons-material';
import RobotAvatar from '../../components/RobotAvatar';
interface RecoveryProps {
robot: Robot;
inputToken: string;
setInputToken: (state: string) => void;
getGenerateRobot: (token: string) => void;
setPage: (state: Page) => void;
baseUrl: string;
}
const Recovery = ({
robot,
inputToken,
setInputToken,
getGenerateRobot,
setPage,
baseUrl,
}: RecoveryProps): JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
return (
<Grid container direction='column' alignItems='center' spacing={1}>
<Grid item></Grid>
</Grid>
);
};
export default Recovery;

View File

@ -0,0 +1,104 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Button,
Collapse,
Grid,
IconButton,
TextField,
Tooltip,
Typography,
useTheme,
} from '@mui/material';
import { useParams } from 'react-router-dom';
import { Page } from '../NavBar';
import { Robot } from '../../models';
import { Casino, Download, ContentCopy, SmartToy, Bolt } from '@mui/icons-material';
import RobotAvatar from '../../components/RobotAvatar';
import { systemClient } from '../../services/System';
import { saveAsJson } from '../../utils';
interface TokenInputProps {
robot: Robot;
showDownload?: boolean;
fullWidth?: boolean;
setRobot: (state: Robot) => void;
inputToken: string;
onPressEnter: () => void;
badRequest: string | undefined;
setInputToken: (state: string) => void;
}
const TokenInput = ({
robot,
setRobot,
showDownload = false,
fullWidth = true,
onPressEnter,
inputToken,
badRequest,
setInputToken,
}: TokenInputProps): JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
const createJsonFile = () => {
return {
token: robot.token,
token_shannon_entropy: robot.shannonEntropy,
token_bit_entropy: robot.bitsEntropy,
public_key: robot.pubKey,
encrypted_private_key: robot.encPrivKey,
};
};
return (
<TextField
error={!!badRequest}
required={true}
value={inputToken}
autoFocus
fullWidth={fullWidth}
sx={{ borderColor: 'primary' }}
variant='outlined'
helperText={badRequest}
size='medium'
onChange={(e) => setInputToken(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter') {
onPressEnter();
}
}}
InputProps={{
startAdornment: showDownload ? (
<Tooltip enterTouchDelay={250} title={t('Save token to file')}>
<span>
<IconButton
color='primary'
onClick={() => saveAsJson(robot.nickname + '.json', createJsonFile())}
>
<Download sx={{ width: '1em', height: '1em' }} />
</IconButton>
</span>
</Tooltip>
) : null,
endAdornment: (
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
<IconButton
color={robot.copiedToken ? 'inherit' : 'primary'}
onClick={() => {
systemClient.copyToClipboard(inputToken);
setRobot({ ...robot, copiedToken: true });
}}
>
<ContentCopy sx={{ width: '1em', height: '1em' }} />
</IconButton>
</Tooltip>
),
}}
/>
);
};
export default TokenInput;

View File

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Button,
CircularProgress,
Paper,
Grid,
IconButton,
TextField,
@ -21,12 +21,14 @@ import { genKey } from '../../pgp';
import { sha256 } from 'js-sha256';
import { Casino, Download, ContentCopy, SmartToy, Bolt } from '@mui/icons-material';
import RobotAvatar from '../../components/RobotAvatar';
import Onboarding from './Onboarding';
interface RobotPageProps {
setPage: (state: Page) => void;
setCurrentOrder: (state: number) => void;
robot: Robot;
setRobot: (state: Robot) => void;
windowSize: { width: number; height: number };
fetchRobot: ({}) => void;
baseUrl: string;
}
@ -34,6 +36,7 @@ interface RobotPageProps {
const RobotPage = ({
setPage,
setCurrentOrder,
windowSize,
robot,
setRobot,
fetchRobot,
@ -43,9 +46,10 @@ const RobotPage = ({
const params = useParams();
const theme = useTheme();
const refCode = params.refCode;
const maxHeight = windowSize.height * 0.85 - 3;
const [robotFound, setRobotFound] = useState<boolean>(false);
const [badRequest, setBadRequest] = useState<string | null>(null);
const [badRequest, setBadRequest] = useState<string | undefined>(undefined);
const [tokenChanged, setTokenChanged] = useState<boolean>(false);
const [inputToken, setInputToken] = useState<string>('');
@ -144,217 +148,244 @@ const RobotPage = ({
const handleClickSubmitToken = () => {};
const handleClickNewRandomToken = () => {};
const createJsonFile = () => {
return {
token: robot.token,
token_shannon_entropy: robot.shannonEntropy,
token_bit_entropy: robot.bitsEntropy,
public_key: robot.pub_key,
encrypted_private_key: robot.enc_priv_key,
};
};
return (
<Grid container spacing={1}>
<Grid item>
<div className='clickTrough' />
</Grid>
<Grid item xs={12} sx={{ width: '26.4em', height: '18.6' }}>
{robot.avatarLoaded && robot.nickname ? (
<div>
<Grid item xs={12}>
<Typography component='h5' variant='h5'>
<b>
{robot.nickname && systemClient.getCookie('sessionid') ? (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexWrap: 'wrap',
height: 45 * 1.000004,
}}
>
<Bolt
sx={{
color: '#fcba03',
height: 33 * 1.000004,
width: 33 * 1.000004,
}}
/>
<a>{robot.nickname}</a>
<Bolt
sx={{
color: '#fcba03',
height: 33 * 1.000004,
width: 33 * 1.000004,
}}
/>
</div>
) : (
''
)}
</b>
</Typography>
</Grid>
<Grid item xs={12}>
<RobotAvatar
nickname={robot.nickname}
smooth={true}
style={{ maxWidth: 203 * 1.000004, maxHeight: 203 * 1.000004 }}
imageStyle={{
transform: '',
border: '2px solid #555',
filter: 'drop-shadow(1px 1px 1px #000000)',
height: `${201 * 1.000004}px`,
width: `${201 * 1.000004}px`,
}}
tooltip={t('This is your trading avatar')}
tooltipPosition='top'
baseUrl={baseUrl}
/>
<br />
</Grid>
</div>
) : (
<CircularProgress sx={{ position: 'relative', top: 100 }} />
)}
</Grid>
{robotFound ? (
<Grid item xs={12}>
<Typography variant='subtitle2' color='primary'>
{t('A robot avatar was found, welcome back!')}
<br />
</Typography>
</Grid>
) : (
<></>
)}
<Grid container>
<Grid item xs={12}>
<TextField
sx={{ maxWidth: 280 * 1.000004 }}
error={!!badRequest}
label={t('Store your token safely')}
required={true}
value={inputToken}
variant='standard'
helperText={badRequest}
size='small'
onChange={handleChangeToken}
onKeyPress={(e) => {
if (e.key === 'Enter') {
handleClickSubmitToken();
}
}}
InputProps={{
startAdornment: (
<div
style={{
width: 50 * 1.000004,
minWidth: 50 * 1.000004,
position: 'relative',
left: -6,
}}
>
<Grid container>
<Grid item xs={6}>
<Tooltip
enterTouchDelay={250}
title={t('Save token and PGP credentials to file')}
>
<span>
<IconButton
color='primary'
disabled={
!robot.avatarLoaded ||
!(systemClient.getItem('robot_token') == inputToken)
}
onClick={() => saveAsJson(robot.nickname + '.json', createJsonFile())}
>
<Download sx={{ width: 22 * 1.000004, height: 22 * 1.000004 }} />
</IconButton>
</span>
</Tooltip>
</Grid>
<Grid item xs={6}>
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
<IconButton
color={robot.copiedToken ? 'inherit' : 'primary'}
disabled={
!robot.avatarLoaded ||
!(systemClient.getItem('robot_token') === inputToken)
}
onClick={() =>
systemClient.copyToClipboard(systemClient.getItem('robot_token')) &
setRobot({ ...robot, copiedToken: true })
}
>
<ContentCopy sx={{ width: 18 * 1.000004, height: 18 * 1.000004 }} />
</IconButton>
</Tooltip>
</Grid>
</Grid>
</div>
),
endAdornment: (
<Tooltip enterTouchDelay={250} title={t('Generate a new token')}>
<IconButton onClick={handleClickNewRandomToken}>
<Casino sx={{ width: 18 * 1.000004, height: 18 * 1.000004 }} />
</IconButton>
</Tooltip>
),
}}
/>
</Grid>
</Grid>
<Grid item xs={12}>
{tokenChanged ? (
<Button type='submit' size='small' onClick={handleClickSubmitToken}>
<SmartToy sx={{ width: 18 * 1.000004, height: 18 * 1.000004 }} />
<span> {t('Generate Robot')}</span>
</Button>
) : (
<Tooltip
enterTouchDelay={0}
enterDelay={500}
enterNextDelay={2000}
title={t('You must enter a new token first')}
>
<div>
<Button disabled={true} type='submit' size='small'>
<SmartToy sx={{ width: 18 * 1.000004, height: 18 * 1.000004 }} />
<span>{t('Generate Robot')}</span>
</Button>
</div>
</Tooltip>
)}
</Grid>
<Grid container direction='column' alignItems='center' spacing={1}>
{/* // Welcome to RoboSats
// Easy and private LN exchange
{/* <Grid item xs={12} align='center' sx={{ width: '26.43em' }}>
<Grid item>
<div style={{ height: '2.143em' }} />
</Grid>
<div style={{ width: '26.43em', left: '2.143em' }}>
<Grid container align='center'>
<Grid item xs={0.8} />
<Grid item xs={7.5} align='right'>
<Typography component='h5' variant='h5'>
{t('Simple and Private LN P2P Exchange')}
</Typography>
</Grid>
<Grid item xs={2.5} align='left'>
<RoboSatsNoTextIcon color='primary' sx={{ height: '3.143em', width: '3.143em' }} />
</Grid>
</Grid>
</div>
</Grid> */}
// I am a newbie
// Skip (fast robot gen)
// Recover existing token */}
<Grid item>
<Paper
elevation={12}
style={{
padding: '1em',
width: `${Math.min(windowSize.width * 0.7, 30)}em`,
maxHeight: `${maxHeight}em`,
overflow: 'auto',
}}
>
<Onboarding
robot={robot}
setRobot={setRobot}
badRequest={badRequest}
inputToken={inputToken}
setInputToken={setInputToken}
getGenerateRobot={getGenerateRobot}
setPage={setPage}
baseUrl={baseUrl}
/>
</Paper>
</Grid>
</Grid>
);
};
export default RobotPage;
// return (
// <Grid container spacing={1}>
// {/* <Grid item>
// <div className='clickTrough' />
// </Grid>
// <Grid item xs={12} sx={{ width: '26.4em', height: '18.6' }}>
// {robot.avatarLoaded && robot.nickname ? (
// <div>
// <Grid item xs={12}>
// <Typography component='h5' variant='h5'>
// <b>
// {robot.nickname && systemClient.getCookie('sessionid') ? (
// <div
// style={{
// display: 'flex',
// alignItems: 'center',
// justifyContent: 'center',
// flexWrap: 'wrap',
// height: 45 * 1.000004,
// }}
// >
// <Bolt
// sx={{
// color: '#fcba03',
// height: 33 * 1.000004,
// width: 33 * 1.000004,
// }}
// />
// <a>{robot.nickname}</a>
// <Bolt
// sx={{
// color: '#fcba03',
// height: 33 * 1.000004,
// width: 33 * 1.000004,
// }}
// />
// </div>
// ) : (
// ''
// )}
// </b>
// </Typography>
// </Grid>
// <Grid item xs={12}>
// <RobotAvatar
// nickname={robot.nickname}
// smooth={true}
// style={{ maxWidth: 203 * 1.000004, maxHeight: 203 * 1.000004 }}
// imageStyle={{
// transform: '',
// border: '2px solid #555',
// filter: 'drop-shadow(1px 1px 1px #000000)',
// height: `${201 * 1.000004}px`,
// width: `${201 * 1.000004}px`,
// }}
// tooltip={t('This is your trading avatar')}
// tooltipPosition='top'
// baseUrl={baseUrl}
// />
// <br />
// </Grid>
// </div>
// ) : (
// <CircularProgress sx={{ position: 'relative', top: 100 }} />
// )}
// </Grid>
// {robotFound ? (
// <Grid item xs={12}>
// <Typography variant='subtitle2' color='primary'>
// {t('A robot avatar was found, welcome back!')}
// <br />
// </Typography>
// </Grid>
// ) : (
// <></>
// )}
// <Grid container>
// <Grid item xs={12}>
// <TextField
// sx={{ maxWidth: 280 * 1.000004 }}
// error={!!badRequest}
// label={t('Store your token safely')}
// required={true}
// value={inputToken}
// variant='standard'
// helperText={badRequest}
// size='small'
// onChange={handleChangeToken}
// onKeyPress={(e) => {
// if (e.key === 'Enter') {
// handleClickSubmitToken();
// }
// }}
// InputProps={{
// startAdornment: (
// <div
// style={{
// width: 50 * 1.000004,
// minWidth: 50 * 1.000004,
// position: 'relative',
// left: -6,
// }}
// >
// <Grid container>
// <Grid item xs={6}>
// <Tooltip
// enterTouchDelay={250}
// title={t('Save token and PGP credentials to file')}
// >
// <span>
// <IconButton
// color='primary'
// disabled={
// !robot.avatarLoaded ||
// !(systemClient.getItem('robot_token') == inputToken)
// }
// onClick={() => saveAsJson(robot.nickname + '.json', createJsonFile())}
// >
// <Download sx={{ width: 22 * 1.000004, height: 22 * 1.000004 }} />
// </IconButton>
// </span>
// </Tooltip>
// </Grid>
// <Grid item xs={6}>
// <Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
// <IconButton
// color={robot.copiedToken ? 'inherit' : 'primary'}
// disabled={
// !robot.avatarLoaded ||
// !(systemClient.getItem('robot_token') === inputToken)
// }
// onClick={() =>
// systemClient.copyToClipboard(systemClient.getItem('robot_token')) &
// setRobot({ ...robot, copiedToken: true })
// }
// >
// <ContentCopy sx={{ width: 18 * 1.000004, height: 18 * 1.000004 }} />
// </IconButton>
// </Tooltip>
// </Grid>
// </Grid>
// </div>
// ),
// endAdornment: (
// <Tooltip enterTouchDelay={250} title={t('Generate a new token')}>
// <IconButton onClick={handleClickNewRandomToken}>
// <Casino sx={{ width: 18 * 1.000004, height: 18 * 1.000004 }} />
// </IconButton>
// </Tooltip>
// ),
// }}
// />
// </Grid>
// </Grid>
// <Grid item xs={12}>
// {tokenChanged ? (
// <Button type='submit' size='small' onClick={handleClickSubmitToken}>
// <SmartToy sx={{ width: 18 * 1.000004, height: 18 * 1.000004 }} />
// <span> {t('Generate Robot')}</span>
// </Button>
// ) : (
// <Tooltip
// enterTouchDelay={0}
// enterDelay={500}
// enterNextDelay={2000}
// title={t('You must enter a new token first')}
// >
// <div>
// <Button disabled={true} type='submit' size='small'>
// <SmartToy sx={{ width: 18 * 1.000004, height: 18 * 1.000004 }} />
// <span>{t('Generate Robot')}</span>
// </Button>
// </div>
// </Tooltip>
// )}
// </Grid>
// {/* <Grid item xs={12} align='center' sx={{ width: '26.43em' }}>
// <Grid item>
// <div style={{ height: '2.143em' }} />
// </Grid>
// <div style={{ width: '26.43em', left: '2.143em' }}>
// <Grid container align='center'>
// <Grid item xs={0.8} />
// <Grid item xs={7.5} align='right'>
// <Typography component='h5' variant='h5'>
// {t('Simple and Private LN P2P Exchange')}
// </Typography>
// </Grid>
// <Grid item xs={2.5} align='left'>
// <RoboSatsNoTextIcon color='primary' sx={{ height: '3.143em', width: '3.143em' }} />
// </Grid>
// </Grid>
// </div>
// </Grid> */}
// </Grid>
// );
// };
// export default RobotPage; */}
// class UserGenPage extends Component {
// handleClickNewRandomToken = () => {