Small fixes

This commit is contained in:
Reckless_Satoshi 2023-02-20 12:36:38 -08:00
parent b98c7a1e9f
commit 766666f8ef
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
4 changed files with 299 additions and 649 deletions

View File

@ -12,6 +12,9 @@ import {
Link, Link,
Typography, Typography,
useTheme, useTheme,
Accordion,
AccordionSummary,
AccordionDetails,
} from '@mui/material'; } from '@mui/material';
import { Page } from '../NavBar'; import { Page } from '../NavBar';
import { Robot } from '../../models'; import { Robot } from '../../models';
@ -65,21 +68,15 @@ const Onboarding = ({
}; };
return ( return (
<Grid container direction='column' alignItems='center' spacing={2} padding={2}> <Box>
<Grid item> <Accordion expanded={step === '1'} disableGutters={true}>
<AccordionSummary>
<Typography variant='h5' color={step == '1' ? 'text.primary' : 'text.disabled'}> <Typography variant='h5' color={step == '1' ? 'text.primary' : 'text.disabled'}>
{t('1. Generate a token')} {t('1. Generate a token')}
</Typography> </Typography>
<Box </AccordionSummary>
sx={{ <AccordionDetails>
backgroundColor: 'background.paper', <Grid container direction='column' alignItems='center' spacing={1} padding={1}>
border: '1px solid',
borderRadius: '4px',
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
}}
>
<Collapse in={step == '1'}>
<Grid container direction='column' alignItems='center' spacing={1} padding={1.5}>
<Grid item> <Grid item>
<Typography> <Typography>
{t( {t(
@ -149,24 +146,17 @@ const Onboarding = ({
</Grid> </Grid>
)} )}
</Grid> </Grid>
</Collapse> </AccordionDetails>
</Box> </Accordion>
</Grid>
<Grid item sx={{ width: '100%' }}> <Accordion expanded={step === '2'} disableGutters={true}>
<AccordionSummary>
<Typography variant='h5' color={step == '2' ? 'text.primary' : 'text.disabled'}> <Typography variant='h5' color={step == '2' ? 'text.primary' : 'text.disabled'}>
{t('2. Meet your robot identity')} {t('2. Meet your robot identity')}
</Typography> </Typography>
<Box </AccordionSummary>
sx={{ <AccordionDetails>
backgroundColor: 'background.paper', <Grid container direction='column' alignItems='center' spacing={1}>
border: '1px solid',
borderRadius: '4px',
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
}}
>
<Collapse in={step == '2'}>
<Grid container direction='column' alignItems='center' spacing={1} padding={1.5}>
<Grid item> <Grid item>
<Typography> <Typography>
{robot.avatarLoaded && robot.nickname ? ( {robot.avatarLoaded && robot.nickname ? (
@ -238,23 +228,16 @@ const Onboarding = ({
</Collapse> </Collapse>
</Grid> </Grid>
</Grid> </Grid>
</Collapse> </AccordionDetails>
</Box> </Accordion>
</Grid>
<Grid item> <Accordion expanded={step === '3'} disableGutters={true}>
<AccordionSummary>
<Typography variant='h5' color={step == '3' ? 'text.primary' : 'text.disabled'}> <Typography variant='h5' color={step == '3' ? 'text.primary' : 'text.disabled'}>
{t('3. Browse or create an order')} {t('3. Browse or create an order')}
</Typography> </Typography>
<Box </AccordionSummary>
sx={{ <AccordionDetails>
backgroundColor: 'background.paper',
border: '1px solid',
borderRadius: '4px',
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
}}
>
<Collapse in={step == '3'}>
<Grid container direction='column' alignItems='center' spacing={1} padding={1.5}> <Grid container direction='column' alignItems='center' spacing={1} padding={1.5}>
<Grid item> <Grid item>
<Typography> <Typography>
@ -301,16 +284,15 @@ const Onboarding = ({
<NewTabIcon sx={{ width: '0.8em' }} /> <NewTabIcon sx={{ width: '0.8em' }} />
</Button> </Button>
</Grid> </Grid>
</Grid> <Grid item sx={{ position: 'relative', top: '0.6em' }}>
</Collapse>
</Box>
</Grid>
<Grid item>
<Button color='inherit' onClick={() => setView('profile')}> <Button color='inherit' onClick={() => setView('profile')}>
{t('See profile')} {t('See profile')}
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>
</AccordionDetails>
</Accordion>
</Box>
); );
}; };

View File

@ -1,17 +1,20 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Grid, LinearProgress, Typography, useTheme } from '@mui/material'; import { useHistory } from 'react-router-dom';
import { Bolt, Logout } from '@mui/icons-material'; import { Button, Link, Grid, LinearProgress, Typography, Alert } from '@mui/material';
import { Bolt, Logout, Refresh } from '@mui/icons-material';
import RobotAvatar from '../../components/RobotAvatar'; import RobotAvatar from '../../components/RobotAvatar';
import TokenInput from './TokenInput'; import TokenInput from './TokenInput';
import { Page } from '../NavBar'; import { Page } from '../NavBar';
import { Robot } from '../../models'; import { Robot } from '../../models';
import { genBase62Token } from '../../utils';
interface RobotProfileProps { interface RobotProfileProps {
robot: Robot; robot: Robot;
setRobot: (state: Robot) => void; setRobot: (state: Robot) => void;
setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void; setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void;
inputToken: string; inputToken: string;
setCurrentOrder: (state: number) => void;
logoutRobot: () => void; logoutRobot: () => void;
setInputToken: (state: string) => void; setInputToken: (state: string) => void;
getGenerateRobot: (token: string) => void; getGenerateRobot: (token: string) => void;
@ -27,7 +30,10 @@ const RobotProfile = ({
setRobot, setRobot,
inputToken, inputToken,
setInputToken, setInputToken,
setCurrentOrder,
getGenerateRobot,
logoutRobot, logoutRobot,
setPage,
setView, setView,
badRequest, badRequest,
baseUrl, baseUrl,
@ -35,7 +41,12 @@ const RobotProfile = ({
width, width,
}: RobotProfileProps): JSX.Element => { }: RobotProfileProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const history = useHistory();
const getNewRobot = () => {
logoutRobot();
setTimeout(() => getGenerateRobot(genBase62Token(36)), 10);
};
return ( return (
<Grid container direction='column' alignItems='center' spacing={2} padding={2}> <Grid container direction='column' alignItems='center' spacing={2} padding={2}>
@ -73,7 +84,7 @@ const RobotProfile = ({
</Typography> </Typography>
) : ( ) : (
<> <>
<b>{t('Rebuilding your robot!')}</b> <b>{t('Building your robot!')}</b>
<LinearProgress /> <LinearProgress />
</> </>
)} )}
@ -102,18 +113,60 @@ const RobotProfile = ({
<Grid item> <Grid item>
<Typography variant='subtitle2' color='primary'> <Typography variant='subtitle2' color='primary'>
{t('Welcome back!')} {t('Welcome back!')}
<br />
</Typography> </Typography>
</Grid> </Grid>
) : ( ) : (
<></> <></>
)} )}
{robot.activeOrderId ? (
<Grid item> <Grid item>
{/* This robot has an active order <Typography>
{t('One active order')}
This robot has a past order. Reusing Robot degrades your privacy: Get a new robot! */} <Link
onClick={() => {
history.push('/order/' + robot.activeOrderId);
setPage('order');
setCurrentOrder(robot.activeOrderId);
}}
>
{` #${robot.activeOrderId}`}
</Link>
</Typography>
</Grid> </Grid>
) : null}
{robot.lastOrderId ? (
<Grid item>
<Typography align='center'>
{t('Your past order')}
<Link
onClick={() => {
history.push('/order/' + robot.lastOrderId);
setPage('order');
setCurrentOrder(robot.lastOrderId);
}}
>
{` #${robot.lastOrderId}`}
</Link>
</Typography>
<Alert severity='warning'>
<Grid container direction='column' alignItems='center'>
<Grid item>
{t(
'Reusing trading identity degrades your privacy against other users, coordinators and observers.',
)}
</Grid>
<Grid item sx={{ position: 'relative', right: '1em' }}>
<Button color='inherit' size='small' onClick={getNewRobot}>
<Refresh />
{t('Generate a new Robot')}
</Button>
</Grid>
</Grid>
</Alert>
</Grid>
) : null}
<Grid item sx={{ width: '100%' }}> <Grid item sx={{ width: '100%' }}>
<TokenInput <TokenInput

View File

@ -1,6 +1,15 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Paper, Grid, CircularProgress, Box, Alert, Typography, useTheme } from '@mui/material'; import {
Paper,
Grid,
CircularProgress,
Box,
Alert,
Typography,
useTheme,
AlertTitle,
} from '@mui/material';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { Page } from '../NavBar'; import { Page } from '../NavBar';
@ -134,13 +143,13 @@ const RobotPage = ({
}; };
const logoutRobot = () => { const logoutRobot = () => {
setRobot({ ...robot, nickname: undefined, token: undefined, avatarLoaded: false });
setInputToken(''); setInputToken('');
setRobotFound(false); setRobotFound(false);
systemClient.deleteCookie('sessionid'); systemClient.deleteCookie('sessionid');
systemClient.deleteItem('robot_token'); systemClient.deleteItem('robot_token');
systemClient.deleteItem('pub_key'); systemClient.deleteItem('pub_key');
systemClient.deleteItem('enc_priv_key'); systemClient.deleteItem('enc_priv_key');
setTimeout(() => setRobot(new Robot()), 10);
}; };
if (window?.NativeRobosats & (torStatus != 'DONE')) { if (window?.NativeRobosats & (torStatus != 'DONE')) {
@ -154,7 +163,7 @@ const RobotPage = ({
> >
<Grid container direction='column' alignItems='center' spacing={1} padding={2}> <Grid container direction='column' alignItems='center' spacing={1} padding={2}>
<Grid item> <Grid item>
<Typography align='center' variant='h5'> <Typography align='center' variant='h6'>
{t('Connecting to TOR')} {t('Connecting to TOR')}
</Typography> </Typography>
</Grid> </Grid>
@ -182,7 +191,7 @@ const RobotPage = ({
</Grid> </Grid>
<Grid item> <Grid item>
<Alert> <Alert>
<b>{t('Your traffic is encrypted and annonimized using TOR. ')}</b> <AlertTitle>{t('Connection encrypted and anonymized using TOR.')}</AlertTitle>
{t( {t(
'This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.', 'This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.',
)} )}
@ -226,6 +235,7 @@ const RobotPage = ({
robot={robot} robot={robot}
robotFound={robotFound} robotFound={robotFound}
setRobot={setRobot} setRobot={setRobot}
setCurrentOrder={setCurrentOrder}
badRequest={badRequest} badRequest={badRequest}
logoutRobot={logoutRobot} logoutRobot={logoutRobot}
width={width} width={width}

View File

@ -1,395 +0,0 @@
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import {
Button,
Tooltip,
Grid,
Typography,
TextField,
ButtonGroup,
CircularProgress,
IconButton,
} from '@mui/material';
import SmartToyIcon from '@mui/icons-material/SmartToy';
import CasinoIcon from '@mui/icons-material/Casino';
import ContentCopy from '@mui/icons-material/ContentCopy';
import BoltIcon from '@mui/icons-material/Bolt';
import DownloadIcon from '@mui/icons-material/Download';
import { RoboSatsNoTextIcon } from '../components/Icons';
import { sha256 } from 'js-sha256';
import { genBase62Token, tokenStrength, saveAsJson } from '../utils';
import { genKey } from '../pgp';
import { systemClient } from '../services/System';
import { apiClient } from '../services/api/index';
import RobotAvatar from '../components/RobotAvatar';
class UserGenPage extends Component {
constructor(props) {
super(props);
this.state = {
tokenHasChanged: false,
inputToken: '',
found: false,
};
this.refCode = this.props.match.params.refCode;
}
componentDidMount() {
// Checks in parent HomePage if there is already a nick and token
// Displays the existing one
if (this.props.robot.nickname != null) {
this.setState({ inputToken: this.props.robot.token });
} else if (this.props.robot.token) {
this.setState({ inputToken: this.props.robot.token });
this.getGeneratedUser(this.props.robot.token);
} else {
const newToken = genBase62Token(36);
this.setState({
inputToken: newToken,
});
this.getGeneratedUser(newToken);
}
}
getGeneratedUser = (token) => {
const strength = tokenStrength(token);
const refCode = this.refCode;
this.props.setRobot({ ...this.props.robot, loading: true, avatarLoaded: false });
const requestBody = genKey(token).then(function (key) {
return {
token_sha256: sha256(token),
public_key: key.publicKeyArmored,
encrypted_private_key: key.encryptedPrivateKeyArmored,
unique_values: strength.uniqueValues,
counts: strength.counts,
length: token.length,
ref_code: refCode,
};
});
requestBody.then((body) =>
apiClient.post(this.props.baseUrl, '/api/user/', body).then((data) => {
this.setState({ found: data.found, bad_request: data.bad_request });
this.props.setCurrentOrder(
data.active_order_id
? data.active_order_id
: data.last_order_id
? data.last_order_id
: null,
);
// Add nick and token to App state (token only if not a bad request)
data.bad_request
? this.props.setRobot({
...this.props.robot,
avatarLoaded: true,
loading: false,
nickname: data.nickname ?? this.props.robot.nickname,
activeOrderId: data.active_order_id ?? null,
referralCode: data.referral_code ?? this.props.referralCode,
earnedRewards: data.earned_rewards ?? this.props.earnedRewards,
lastOrderId: data.last_order_id ?? this.props.lastOrderId,
stealthInvoices: data.wants_stealth ?? this.props.stealthInvoices,
tgEnabled: data.tg_enabled,
tgBotName: data.tg_bot_name,
tgToken: data.tg_token,
})
: this.props.setRobot({
...this.props.robot,
nickname: data.nickname,
token,
loading: false,
activeOrderId: data.active_order_id ? data.active_order_id : null,
lastOrderId: data.last_order_id ? data.last_order_id : null,
referralCode: data.referral_code,
earnedRewards: data.earned_rewards ?? 0,
stealthInvoices: data.wants_stealth,
tgEnabled: data.tg_enabled,
tgBotName: data.tg_bot_name,
tgToken: data.tg_token,
bitsEntropy: data.token_bits_entropy,
shannonEntropy: data.token_shannon_entropy,
pubKey: data.public_key,
encPrivKey: data.encrypted_private_key,
copiedToken: data.found ? true : this.props.robot.copiedToken,
}) &
systemClient.setItem('robot_token', token) &
systemClient.setItem('pub_key', data.public_key.split('\n').join('\\')) &
systemClient.setItem('enc_priv_key', data.encrypted_private_key.split('\n').join('\\'));
}),
);
};
delGeneratedUser() {
apiClient.delete(this.props.baseUrl, '/api/user');
systemClient.deleteCookie('sessionid');
systemClient.deleteItem('robot_token');
systemClient.deleteItem('pub_key');
systemClient.deleteItem('enc_priv_key');
}
handleClickNewRandomToken = () => {
const inputToken = genBase62Token(36);
this.setState({
inputToken,
tokenHasChanged: true,
});
this.props.setRobot({ ...this.props.robot, copiedToken: true });
};
handleChangeToken = (e) => {
this.setState({
inputToken: e.target.value.split(' ').join(''),
tokenHasChanged: true,
});
};
handleClickSubmitToken = () => {
this.delGeneratedUser();
this.getGeneratedUser(this.state.inputToken);
this.setState({ tokenHasChanged: false });
this.props.setRobot({
...this.props.robot,
avatarLoaded: false,
nickname: null,
token: null,
copiedToken: false,
lastOrderId: null,
activeOrderId: null,
});
};
createJsonFile = () => {
return {
token: this.props.robot.token,
token_shannon_entropy: this.props.robot.shannonEntropy,
token_bit_entropy: this.props.robot.bitsEntropy,
public_key: this.props.robot.pub_key,
encrypted_private_key: this.props.robot.enc_priv_key,
};
};
render() {
const { t, i18n } = this.props;
const fontSize = this.props.theme.typography.fontSize;
const fontSizeFactor = fontSize / 14; // to scale sizes, default fontSize is 14
return (
<Grid container spacing={1}>
<Grid item>
<div className='clickTrough' />
</Grid>
<Grid
item
xs={12}
align='center'
sx={{ width: 370 * fontSizeFactor, height: 260 * fontSizeFactor }}
>
{this.props.robot.avatarLoaded && this.props.robot.nickname ? (
<div>
<Grid item xs={12} align='center'>
<Typography component='h5' variant='h5'>
<b>
{this.props.robot.nickname && systemClient.getCookie('sessionid') ? (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexWrap: 'wrap',
height: 45 * fontSizeFactor,
}}
>
<BoltIcon
sx={{
color: '#fcba03',
height: 33 * fontSizeFactor,
width: 33 * fontSizeFactor,
}}
/>
<a>{this.props.robot.nickname}</a>
<BoltIcon
sx={{
color: '#fcba03',
height: 33 * fontSizeFactor,
width: 33 * fontSizeFactor,
}}
/>
</div>
) : (
''
)}
</b>
</Typography>
</Grid>
<Grid item xs={12} align='center'>
<RobotAvatar
nickname={this.props.robot.nickname}
smooth={true}
style={{ maxWidth: 203 * fontSizeFactor, maxHeight: 203 * fontSizeFactor }}
imageStyle={{
transform: '',
border: '2px solid #555',
filter: 'drop-shadow(1px 1px 1px #000000)',
height: `${201 * fontSizeFactor}px`,
width: `${201 * fontSizeFactor}px`,
}}
tooltip={t('This is your trading avatar')}
tooltipPosition='top'
baseUrl={this.props.baseUrl}
/>
<br />
</Grid>
</div>
) : (
<CircularProgress sx={{ position: 'relative', top: 100 }} />
)}
</Grid>
{this.state.found ? (
<Grid item xs={12} align='center'>
<Typography variant='subtitle2' color='primary'>
{this.state.found ? t('A robot avatar was found, welcome back!') : null}
<br />
</Typography>
</Grid>
) : (
''
)}
<Grid container align='center'>
<Grid item xs={12} align='center'>
<TextField
sx={{ maxWidth: 280 * fontSizeFactor }}
error={!!this.state.bad_request}
label={t('Store your token safely')}
required={true}
value={this.state.inputToken}
variant='standard'
helperText={this.state.bad_request}
size='small'
onChange={this.handleChangeToken}
onKeyPress={(e) => {
if (e.key === 'Enter') {
this.handleClickSubmitToken();
}
}}
InputProps={{
startAdornment: (
<div
style={{
width: 50 * fontSizeFactor,
minWidth: 50 * fontSizeFactor,
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={
!this.props.robot.avatarLoaded ||
!(systemClient.getItem('robot_token') == this.state.inputToken)
}
onClick={() =>
saveAsJson(
this.props.robot.nickname + '.json',
this.createJsonFile(),
)
}
>
<DownloadIcon
sx={{ width: 22 * fontSizeFactor, height: 22 * fontSizeFactor }}
/>
</IconButton>
</span>
</Tooltip>
</Grid>
<Grid item xs={6}>
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
<IconButton
color={this.props.robot.copiedToken ? 'inherit' : 'primary'}
disabled={
!this.props.robot.avatarLoaded ||
!(systemClient.getItem('robot_token') === this.state.inputToken)
}
onClick={() =>
systemClient.copyToClipboard(systemClient.getItem('robot_token')) &
this.props.setRobot({ ...this.props.robot, copiedToken: true })
}
>
<ContentCopy
sx={{ width: 18 * fontSizeFactor, height: 18 * fontSizeFactor }}
/>
</IconButton>
</Tooltip>
</Grid>
</Grid>
</div>
),
endAdornment: (
<Tooltip enterTouchDelay={250} title={t('Generate a new token')}>
<IconButton onClick={this.handleClickNewRandomToken}>
<CasinoIcon
sx={{ width: 18 * fontSizeFactor, height: 18 * fontSizeFactor }}
/>
</IconButton>
</Tooltip>
),
}}
/>
</Grid>
</Grid>
<Grid item xs={12} align='center'>
{this.state.tokenHasChanged ? (
<Button type='submit' size='small' onClick={this.handleClickSubmitToken}>
<SmartToyIcon sx={{ width: 18 * fontSizeFactor, height: 18 * fontSizeFactor }} />
<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'>
<SmartToyIcon sx={{ width: 18 * fontSizeFactor, height: 18 * fontSizeFactor }} />
<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 withTranslation()(UserGenPage);