Add robot garage (#370)

* Add garage model

* Add robot select to profile

* Replace Robot for Garage init

* Add Garage inners, not re-rendering

* Revert

* Collect new phrases and small fixes

* Small fixes

* Fix unencrypted # hack on Turtle chat

* Small fixes and collect phrases
This commit is contained in:
Reckless_Satoshi 2023-03-02 11:01:06 +00:00 committed by GitHub
parent d88c2a5eff
commit c0b8a6d3ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 673 additions and 497 deletions

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Grid, ButtonGroup, Dialog, Box } from '@mui/material'; import { Button, Grid, ButtonGroup, Dialog, Box } from '@mui/material';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@ -7,49 +7,24 @@ import DepthChart from '../../components/Charts/DepthChart';
import { NoRobotDialog } from '../../components/Dialogs'; import { NoRobotDialog } from '../../components/Dialogs';
import MakerForm from '../../components/MakerForm'; import MakerForm from '../../components/MakerForm';
import BookTable from '../../components/BookTable'; import BookTable from '../../components/BookTable';
import { Page } from '../NavBar';
import { Book, Favorites, LimitList, Maker } from '../../models';
// Icons // Icons
import { BarChart, FormatListBulleted } from '@mui/icons-material'; import { BarChart, FormatListBulleted } from '@mui/icons-material';
import { AppContext, AppContextProps } from '../../contexts/AppContext';
interface BookPageProps { const BookPage = (): JSX.Element => {
book: Book; const {
limits: { list: LimitList; loading: boolean }; robot,
fetchLimits: () => void;
fav: Favorites;
setFav: (state: Favorites) => void;
onViewOrder: () => void;
fetchBook: () => void;
clearOrder: () => void;
windowSize: { width: number; height: number };
lastDayPremium: number;
maker: Maker;
setMaker: (state: Maker) => void;
hasRobot: boolean;
setPage: (state: Page) => void;
setCurrentOrder: (state: number) => void;
baseUrl: string;
}
const BookPage = ({
lastDayPremium = 0,
limits,
book = { orders: [], loading: true },
fetchBook, fetchBook,
fetchLimits,
clearOrder, clearOrder,
fav,
setFav,
onViewOrder,
maker,
setMaker,
windowSize, windowSize,
hasRobot = false, setPage,
setPage = () => null, setCurrentOrder,
setCurrentOrder = () => null,
baseUrl, baseUrl,
}: BookPageProps): JSX.Element => { book,
setDelay,
setOrder,
} = useContext<AppContextProps>(AppContext);
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory(); const history = useHistory();
const [view, setView] = useState<'list' | 'depth'>('list'); const [view, setView] = useState<'list' | 'depth'>('list');
@ -62,15 +37,16 @@ const BookPage = ({
const chartWidthEm = width - maxBookTableWidth; const chartWidthEm = width - maxBookTableWidth;
useEffect(() => { useEffect(() => {
if (book.orders.length < 1) { fetchBook();
fetchBook(true, false);
} else {
fetchBook(false, true);
}
}, []); }, []);
const onViewOrder = function () {
setOrder(undefined);
setDelay(10000);
};
const onOrderClicked = function (id: number) { const onOrderClicked = function (id: number) {
if (hasRobot) { if (robot.avatarLoaded) {
history.push('/order/' + id); history.push('/order/' + id);
setPage('order'); setPage('order');
setCurrentOrder(id); setCurrentOrder(id);
@ -108,6 +84,7 @@ const BookPage = ({
</ButtonGroup> </ButtonGroup>
); );
}; };
return ( return (
<Grid container direction='column' alignItems='center' spacing={1} sx={{ minWidth: 400 }}> <Grid container direction='column' alignItems='center' spacing={1} sx={{ minWidth: 400 }}>
<NoRobotDialog open={openNoRobot} onClose={() => setOpenNoRobot(false)} setPage={setPage} /> <NoRobotDialog open={openNoRobot} onClose={() => setOpenNoRobot(false)} setPage={setPage} />
@ -115,21 +92,13 @@ const BookPage = ({
<Dialog open={openMaker} onClose={() => setOpenMaker(false)}> <Dialog open={openMaker} onClose={() => setOpenMaker(false)}>
<Box sx={{ maxWidth: '18em', padding: '0.5em' }}> <Box sx={{ maxWidth: '18em', padding: '0.5em' }}>
<MakerForm <MakerForm
limits={limits} hasRobot={robot.AvatarLoaded}
fetchLimits={fetchLimits}
maker={maker}
setMaker={setMaker}
fav={fav}
setFav={setFav}
setPage={setPage}
hasRobot={hasRobot}
onOrderCreated={(id) => { onOrderCreated={(id) => {
clearOrder(); clearOrder();
setCurrentOrder(id); setCurrentOrder(id);
setPage('order'); setPage('order');
history.push('/order/' + id); history.push('/order/' + id);
}} }}
baseUrl={baseUrl}
/> />
</Box> </Box>
</Dialog> </Dialog>

View File

@ -19,38 +19,18 @@ import { AppContextProps, AppContext } from '../contexts/AppContext';
const Main = (): JSX.Element => { const Main = (): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
book,
fetchBook,
maker,
setMaker,
clearOrder,
torStatus,
settings, settings,
limits,
fetchLimits,
robot, robot,
setRobot, setRobot,
fetchRobot,
setOrder,
setDelay,
info,
fav,
setFav,
baseUrl, baseUrl,
order, order,
page, page,
setPage, setPage,
slideDirection, slideDirection,
setSlideDirection,
currentOrder,
setCurrentOrder,
closeAll, closeAll,
open,
setOpen, setOpen,
windowSize, windowSize,
badOrder,
navbarHeight, navbarHeight,
setBadOrder,
} = useContext<AppContextProps>(AppContext); } = useContext<AppContextProps>(AppContext);
const Router = window.NativeRobosats === undefined ? BrowserRouter : HashRouter; const Router = window.NativeRobosats === undefined ? BrowserRouter : HashRouter;
@ -101,16 +81,7 @@ const Main = (): JSX.Element => {
appear={slideDirection.in != undefined} appear={slideDirection.in != undefined}
> >
<div> <div>
<RobotPage <RobotPage />
setPage={setPage}
torStatus={torStatus}
fetchRobot={fetchRobot}
setCurrentOrder={setCurrentOrder}
windowSize={windowSize}
robot={robot}
setRobot={setRobot}
baseUrl={baseUrl}
/>
</div> </div>
</Slide> </Slide>
)} )}
@ -123,27 +94,7 @@ const Main = (): JSX.Element => {
appear={slideDirection.in != undefined} appear={slideDirection.in != undefined}
> >
<div> <div>
<BookPage <BookPage />
book={book}
fetchBook={fetchBook}
onViewOrder={() => {
setOrder(undefined);
setDelay(10000);
}}
limits={limits}
fetchLimits={fetchLimits}
fav={fav}
setFav={setFav}
maker={maker}
setMaker={setMaker}
clearOrder={clearOrder}
lastDayPremium={info.last_day_nonkyc_btc_premium}
windowSize={windowSize}
hasRobot={robot.avatarLoaded}
setPage={setPage}
setCurrentOrder={setCurrentOrder}
baseUrl={baseUrl}
/>
</div> </div>
</Slide> </Slide>
</Route> </Route>
@ -155,7 +106,7 @@ const Main = (): JSX.Element => {
appear={slideDirection.in != undefined} appear={slideDirection.in != undefined}
> >
<div> <div>
<MakerPage hasRobot={robot.avatarLoaded} /> <MakerPage />
</div> </div>
</Slide> </Slide>
</Route> </Route>
@ -169,10 +120,7 @@ const Main = (): JSX.Element => {
appear={slideDirection.in != undefined} appear={slideDirection.in != undefined}
> >
<div> <div>
<OrderPage <OrderPage locationOrderId={props.match.params.orderId} />
locationOrderId={props.match.params.orderId}
hasRobot={robot.avatarLoaded}
/>
</div> </div>
</Slide> </Slide>
)} )}
@ -192,19 +140,9 @@ const Main = (): JSX.Element => {
</Switch> </Switch>
</Box> </Box>
<div style={{ alignContent: 'center', display: 'flex' }}> <div style={{ alignContent: 'center', display: 'flex' }}>
<NavBar width={windowSize.width} height={navbarHeight} hasRobot={robot.avatarLoaded} /> <NavBar width={windowSize.width} height={navbarHeight} />
</div> </div>
<MainDialogs <MainDialogs />
open={open}
setOpen={setOpen}
setRobot={setRobot}
setPage={setPage}
setCurrentOrder={setCurrentOrder}
info={info}
robot={robot}
closeAll={closeAll}
baseUrl={baseUrl}
/>
</Router> </Router>
); );
}; };

View File

@ -10,13 +10,18 @@ import BookTable from '../../components/BookTable';
import { AppContext, AppContextProps } from '../../contexts/AppContext'; import { AppContext, AppContextProps } from '../../contexts/AppContext';
interface MakerPageProps { const MakerPage = (): JSX.Element => {
hasRobot: boolean; const {
} robot,
book,
const MakerPage = ({ hasRobot = false }: MakerPageProps): JSX.Element => { fav,
const { book, fav, maker, clearOrder, windowSize, setCurrentOrder, navbarHeight, setPage } = maker,
useContext<AppContextProps>(AppContext); clearOrder,
windowSize,
setCurrentOrder,
navbarHeight,
setPage,
} = useContext<AppContextProps>(AppContext);
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory(); const history = useHistory();
@ -74,7 +79,7 @@ const MakerPage = ({ hasRobot = false }: MakerPageProps): JSX.Element => {
setPage('order'); setPage('order');
history.push('/order/' + id); history.push('/order/' + id);
}} }}
hasRobot={hasRobot} hasRobot={robot.avatarLoaded}
disableRequest={matches.length > 0 && !showMatches} disableRequest={matches.length > 0 && !showMatches}
collapseAll={showMatches} collapseAll={showMatches}
onSubmit={() => setShowMatches(matches.length > 0)} onSubmit={() => setShowMatches(matches.length > 0)}

View File

@ -20,17 +20,16 @@ import { AppContext, AppContextProps } from '../../contexts/AppContext';
interface NavBarProps { interface NavBarProps {
width: number; width: number;
height: number; height: number;
hasRobot: boolean;
} }
const NavBar = ({ width, height, hasRobot = false }: NavBarProps): JSX.Element => { const NavBar = ({ width, height }: NavBarProps): JSX.Element => {
const { const {
robot,
page, page,
settings, settings,
setPage, setPage,
setSlideDirection, setSlideDirection,
open, open,
robot,
setOpen, setOpen,
closeAll, closeAll,
currentOrder, currentOrder,
@ -43,7 +42,7 @@ const NavBar = ({ width, height, hasRobot = false }: NavBarProps): JSX.Element =
const smallBar = width < 50; const smallBar = width < 50;
const tabSx = smallBar const tabSx = smallBar
? { position: 'relative', bottom: robot.nickname ? '1em' : '0em', minWidth: '1em' } ? { position: 'relative', bottom: robot.avatarLoaded ? '0.9em' : '0.13em', minWidth: '1em' }
: { position: 'relative', bottom: '1em', minWidth: '2em' }; : { position: 'relative', bottom: '1em', minWidth: '2em' };
const pagesPosition = { const pagesPosition = {
robot: 1, robot: 1,
@ -137,7 +136,7 @@ const NavBar = ({ width, height, hasRobot = false }: NavBarProps): JSX.Element =
sx={tabSx} sx={tabSx}
label={smallBar ? undefined : t('Order')} label={smallBar ? undefined : t('Order')}
value='order' value='order'
disabled={!hasRobot || currentOrder == undefined} disabled={!robot.avatarLoaded || currentOrder == undefined}
icon={<Assignment />} icon={<Assignment />}
iconPosition='start' iconPosition='start'
/> />

View File

@ -6,20 +6,18 @@ import { useHistory } from 'react-router-dom';
import TradeBox from '../../components/TradeBox'; import TradeBox from '../../components/TradeBox';
import OrderDetails from '../../components/OrderDetails'; import OrderDetails from '../../components/OrderDetails';
import { Page } from '../NavBar';
import { Order, Settings } from '../../models';
import { apiClient } from '../../services/api'; import { apiClient } from '../../services/api';
import { AppContext, AppContextProps } from '../../contexts/AppContext'; import { AppContext, AppContextProps } from '../../contexts/AppContext';
interface OrderPageProps { interface OrderPageProps {
hasRobot: boolean;
locationOrderId: number; locationOrderId: number;
} }
const OrderPage = ({ hasRobot = false, locationOrderId }: OrderPageProps): JSX.Element => { const OrderPage = ({ locationOrderId }: OrderPageProps): JSX.Element => {
const { const {
windowSize, windowSize,
order, order,
robot,
settings, settings,
setOrder, setOrder,
setCurrentOrder, setCurrentOrder,
@ -106,7 +104,7 @@ const OrderPage = ({ hasRobot = false, locationOrderId }: OrderPageProps): JSX.E
setOrder={setOrder} setOrder={setOrder}
baseUrl={baseUrl} baseUrl={baseUrl}
setPage={setPage} setPage={setPage}
hasRobot={hasRobot} hasRobot={robot.avatarLoaded}
/> />
</Paper> </Paper>
</Grid> </Grid>
@ -121,6 +119,7 @@ const OrderPage = ({ hasRobot = false, locationOrderId }: OrderPageProps): JSX.E
> >
<TradeBox <TradeBox
order={order} order={order}
robot={robot}
settings={settings} settings={settings}
setOrder={setOrder} setOrder={setOrder}
setBadOrder={setBadOrder} setBadOrder={setBadOrder}
@ -158,12 +157,13 @@ const OrderPage = ({ hasRobot = false, locationOrderId }: OrderPageProps): JSX.E
setOrder={setOrder} setOrder={setOrder}
baseUrl={baseUrl} baseUrl={baseUrl}
setPage={setPage} setPage={setPage}
hasRobot={hasRobot} hasRobot={robot.avatarLoaded}
/> />
</div> </div>
<div style={{ display: tab == 'contract' ? '' : 'none' }}> <div style={{ display: tab == 'contract' ? '' : 'none' }}>
<TradeBox <TradeBox
order={order} order={order}
robot={robot}
settings={settings} settings={settings}
setOrder={setOrder} setOrder={setOrder}
setBadOrder={setBadOrder} setBadOrder={setBadOrder}
@ -189,7 +189,7 @@ const OrderPage = ({ hasRobot = false, locationOrderId }: OrderPageProps): JSX.E
setOrder={setOrder} setOrder={setOrder}
baseUrl={baseUrl} baseUrl={baseUrl}
setPage={setPage} setPage={setPage}
hasRobot={hasRobot} hasRobot={robot.avatarLoaded}
/> />
</Paper> </Paper>
) )

View File

@ -49,17 +49,16 @@ const Onboarding = ({
}: OnboardingProps): JSX.Element => { }: OnboardingProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory(); const history = useHistory();
const theme = useTheme();
const [step, setStep] = useState<'1' | '2' | '3'>('1'); const [step, setStep] = useState<'1' | '2' | '3'>('1');
const [generatedToken, setGeneratedToken] = useState<boolean>(false); const [generatedToken, setGeneratedToken] = useState<boolean>(false);
const [showMimickProgress, setShowMimickProgress] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const generateToken = () => { const generateToken = () => {
setGeneratedToken(true); setGeneratedToken(true);
setInputToken(genBase62Token(36)); setInputToken(genBase62Token(36));
setShowMimickProgress(true); setLoading(true);
setTimeout(() => setShowMimickProgress(false), 1000); setTimeout(() => setLoading(false), 1000);
}; };
const changePage = function (newPage: Page) { const changePage = function (newPage: Page) {
@ -104,10 +103,8 @@ const Onboarding = ({
</Alert> </Alert>
</Grid> </Grid>
<Grid item sx={{ width: '100%' }}> <Grid item sx={{ width: '100%' }}>
{showMimickProgress ? (
<LinearProgress sx={{ height: '0.7em' }} />
) : (
<TokenInput <TokenInput
loading={loading}
autoFocusTarget='copyButton' autoFocusTarget='copyButton'
inputToken={inputToken} inputToken={inputToken}
setInputToken={setInputToken} setInputToken={setInputToken}
@ -116,7 +113,6 @@ const Onboarding = ({
robot={robot} robot={robot}
onPressEnter={() => null} onPressEnter={() => null}
/> />
)}
</Grid> </Grid>
<Grid item> <Grid item>
<Typography> <Typography>

View File

@ -1,22 +1,37 @@
import React, { useState } from 'react'; import React, { useState, useContext, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { Button, Link, Grid, LinearProgress, Typography, Alert } from '@mui/material'; import {
import { Bolt, Logout, Refresh } from '@mui/icons-material'; Button,
Grid,
LinearProgress,
Typography,
Alert,
Select,
MenuItem,
Box,
useTheme,
Tooltip,
} from '@mui/material';
import { Bolt, Add, DeleteSweep, Logout, Download } 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 { Slot, Robot } from '../../models';
import { AppContext, AppContextProps } from '../../contexts/AppContext';
import { genBase62Token } from '../../utils';
import { LoadingButton } from '@mui/lab';
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;
getGenerateRobot: (token: string, slot?: number) => void;
inputToken: string; inputToken: string;
setCurrentOrder: (state: number) => void; setCurrentOrder: (state: number) => void;
logoutRobot: () => void; logoutRobot: () => void;
inputToken: string;
setInputToken: (state: string) => void; setInputToken: (state: string) => void;
getGenerateRobot: (token: string) => void;
setPage: (state: Page) => void; setPage: (state: Page) => void;
baseUrl: string; baseUrl: string;
badRequest: string; badRequest: string;
@ -27,9 +42,9 @@ const RobotProfile = ({
robot, robot,
setRobot, setRobot,
inputToken, inputToken,
getGenerateRobot,
setInputToken, setInputToken,
setCurrentOrder, setCurrentOrder,
getGenerateRobot,
logoutRobot, logoutRobot,
setPage, setPage,
setView, setView,
@ -37,11 +52,41 @@ const RobotProfile = ({
baseUrl, baseUrl,
width, width,
}: RobotProfileProps): JSX.Element => { }: RobotProfileProps): JSX.Element => {
const { currentSlot, garage, setCurrentSlot, windowSize } =
useContext<AppContextProps>(AppContext);
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme();
const history = useHistory(); const history = useHistory();
const [loading, setLoading] = useState<boolean>(true);
useEffect(() => {
if (robot.nickname && robot.avatarLoaded) {
setLoading(false);
}
}, [robot]);
const handleAddRobot = () => {
getGenerateRobot(genBase62Token(36), garage.slots.length);
setLoading(true);
};
const handleChangeSlot = (e) => {
const slot = e.target.value;
getGenerateRobot(garage.slots[slot].robot.token, slot);
setLoading(true);
};
return ( return (
<Grid container direction='column' alignItems='center' spacing={2} padding={2}> <Grid container direction='column' alignItems='center' spacing={1} padding={1} paddingTop={2}>
<Grid
item
container
direction='column'
alignItems='center'
spacing={1}
sx={{ width: '100%' }}
>
<Grid item sx={{ height: '2.3em', position: 'relative' }}> <Grid item sx={{ height: '2.3em', position: 'relative' }}>
{robot.avatarLoaded && robot.nickname ? ( {robot.avatarLoaded && robot.nickname ? (
<Typography align='center' component='h5' variant='h5'> <Typography align='center' component='h5' variant='h5'>
@ -99,17 +144,16 @@ const RobotProfile = ({
tooltipPosition='top' tooltipPosition='top'
baseUrl={baseUrl} baseUrl={baseUrl}
/> />
</Grid> {robot.found && !robot.lastOrderId ? (
{robot.found ? (
<Typography align='center' variant='h6'> <Typography align='center' variant='h6'>
{t('Welcome back!')} {t('Welcome back!')}
</Typography> </Typography>
) : ( ) : (
<></> <></>
)} )}
</Grid>
{robot.activeOrderId ? ( {robot.activeOrderId && robot.avatarLoaded && robot.nickname ? (
<Grid item> <Grid item>
<Button <Button
onClick={() => { onClick={() => {
@ -123,7 +167,7 @@ const RobotProfile = ({
</Grid> </Grid>
) : null} ) : null}
{robot.lastOrderId ? ( {robot.lastOrderId && robot.avatarLoaded && robot.nickname ? (
<Grid item container direction='column' alignItems='center'> <Grid item container direction='column' alignItems='center'>
<Grid item> <Grid item>
<Button <Button
@ -145,16 +189,9 @@ const RobotProfile = ({
)} )}
</Grid> </Grid>
<Grid item sx={{ position: 'relative', right: '1em' }}> <Grid item sx={{ position: 'relative', right: '1em' }}>
<Button <Button color='success' size='small' onClick={handleAddRobot}>
color='inherit' <Add />
size='small' {t('Add a new Robot')}
onClick={() => {
logoutRobot();
setView('welcome');
}}
>
<Refresh />
{t('Generate a new Robot')}
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>
@ -163,11 +200,37 @@ const RobotProfile = ({
</Grid> </Grid>
) : null} ) : null}
<Grid item sx={{ width: '100%' }}> <Grid
item
container
direction='row'
justifyContent='stretch'
alignItems='stretch'
sx={{ width: '100%' }}
>
<Grid
item
xs={2}
sx={{ display: 'flex', justifyContent: 'stretch', alignItems: 'stretch' }}
>
<Tooltip enterTouchDelay={0} enterDelay={300} enterNextDelay={1000} title={t('Logout')}>
<Button
sx={{ minWidth: '2em', width: '100%' }}
color='primary'
variant='outlined'
onClick={() => {
logoutRobot();
setView('welcome');
}}
>
<Logout />
</Button>
</Tooltip>
</Grid>
<Grid item xs={10}>
<TokenInput <TokenInput
inputToken={inputToken} inputToken={inputToken}
editable={false} editable={false}
showDownload={true}
label={t('Store your token safely')} label={t('Store your token safely')}
setInputToken={setInputToken} setInputToken={setInputToken}
setRobot={setRobot} setRobot={setRobot}
@ -176,21 +239,102 @@ const RobotProfile = ({
onPressEnter={() => null} onPressEnter={() => null}
/> />
</Grid> </Grid>
</Grid>
</Grid>
<Grid item sx={{ width: '100%' }}>
<Box
sx={{
backgroundColor: 'background.paper',
border: '1px solid',
borderRadius: '4px',
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
}}
>
<Grid container direction='column' alignItems='center' spacing={2} padding={2}>
<Grid item sx={{ width: '100%' }}>
<Typography variant='caption'>{t('Robot Garage')}</Typography>
<Select
fullWidth
required={true}
inputProps={{
style: { textAlign: 'center' },
}}
value={loading ? 'loading' : currentSlot}
onChange={handleChangeSlot}
>
{loading ? (
<MenuItem key={'loading'} value={'loading'}>
<Typography>{t('Building...')}</Typography>
</MenuItem>
) : (
garage.slots.map((slot: Slot, index: number) => {
return (
<MenuItem key={index} value={index}>
<Grid
container
direction='row'
justifyContent='flex-start'
alignItems='center'
style={{ height: '2.8em' }}
spacing={1}
>
<Grid item>
<RobotAvatar
nickname={slot.robot.nickname}
smooth={true}
style={{ width: '2.6em', height: '2.6em' }}
placeholderType='loading'
baseUrl={baseUrl}
/>
</Grid>
<Grid item>
<Typography variant={windowSize.width < 26 ? 'caption' : undefined}>
{slot.robot.nickname}
</Typography>
</Grid>
</Grid>
</MenuItem>
);
})
)}
</Select>
</Grid>
<Grid item container direction='row' alignItems='center' justifyContent='space-evenly'>
<Grid item>
<LoadingButton loading={loading} color='primary' onClick={handleAddRobot}>
<Add /> <div style={{ width: '0.5em' }} />
{t('Add Robot')}
</LoadingButton>
</Grid>
{window.NativeRobosats === undefined ? (
<Grid item>
<Button color='primary' onClick={() => garage.download()}>
<Download />
</Button>
</Grid>
) : null}
<Grid item> <Grid item>
<Button <Button
size='small'
color='primary' color='primary'
onClick={() => { onClick={() => {
garage.delete();
setCurrentSlot(0);
logoutRobot(); logoutRobot();
setView('welcome'); setView('welcome');
}} }}
> >
<Logout /> <div style={{ width: '0.5em' }} /> <DeleteSweep /> <div style={{ width: '0.5em' }} />
{t('Logout Robot')} {t('Delete Garage')}
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>
</Grid>
</Box>
</Grid>
</Grid>
); );
}; };

View File

@ -1,16 +1,16 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { IconButton, TextField, Tooltip, useTheme } from '@mui/material'; import { IconButton, LinearProgress, TextField, Tooltip } from '@mui/material';
import { Robot } from '../../models'; import { Robot } from '../../models';
import { Download, ContentCopy } from '@mui/icons-material'; import { ContentCopy } from '@mui/icons-material';
import { systemClient } from '../../services/System'; import { systemClient } from '../../services/System';
import { saveAsJson } from '../../utils';
interface TokenInputProps { interface TokenInputProps {
robot: Robot; robot: Robot;
editable?: boolean; editable?: boolean;
showDownload?: boolean; showDownload?: boolean;
fullWidth?: boolean; fullWidth?: boolean;
loading?: boolean;
setRobot: (state: Robot) => void; setRobot: (state: Robot) => void;
inputToken: string; inputToken: string;
autoFocusTarget?: 'textfield' | 'copyButton' | 'none'; autoFocusTarget?: 'textfield' | 'copyButton' | 'none';
@ -27,12 +27,12 @@ const TokenInput = ({
showCopy = true, showCopy = true,
label, label,
setRobot, setRobot,
showDownload = false,
fullWidth = true, fullWidth = true,
onPressEnter, onPressEnter,
autoFocusTarget = 'textfield', autoFocusTarget = 'textfield',
inputToken, inputToken,
badRequest, badRequest,
loading = false,
setInputToken, setInputToken,
}: TokenInputProps): JSX.Element => { }: TokenInputProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -52,6 +52,9 @@ const TokenInput = ({
}; };
}; };
if (loading) {
return <LinearProgress sx={{ height: '0.8em' }} />;
} else {
return ( return (
<TextField <TextField
error={!!badRequest} error={!!badRequest}
@ -72,17 +75,6 @@ const TokenInput = ({
} }
}} }}
InputProps={{ InputProps={{
startAdornment: showDownload ? (
<Tooltip enterTouchDelay={250} title={t('Download token and PGP credentials')}>
<IconButton
color='primary'
sx={{ position: 'relative', top: label ? '0.4em' : '0em' }}
onClick={() => saveAsJson(robot.nickname + '.json', createJsonFile())}
>
<Download sx={{ width: '1em', height: '1em' }} />
</IconButton>
</Tooltip>
) : null,
endAdornment: showCopy ? ( endAdornment: showCopy ? (
<Tooltip open={showCopied} title={t('Copied!')}> <Tooltip open={showCopied} title={t('Copied!')}>
<IconButton <IconButton
@ -102,6 +94,7 @@ const TokenInput = ({
}} }}
/> />
); );
}
}; };
export default TokenInput; export default TokenInput;

View File

@ -12,9 +12,7 @@ import {
} from '@mui/material'; } from '@mui/material';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { Page } from '../NavBar';
import { Robot } from '../../models'; import { Robot } from '../../models';
import { systemClient } from '../../services/System';
import { apiClient } from '../../services/api'; import { apiClient } from '../../services/api';
import Onboarding from './Onboarding'; import Onboarding from './Onboarding';
import Welcome from './Welcome'; import Welcome from './Welcome';
@ -25,7 +23,7 @@ import { genKey } from '../../pgp';
import { AppContext, AppContextProps } from '../../contexts/AppContext'; import { AppContext, AppContextProps } from '../../contexts/AppContext';
const RobotPage = (): JSX.Element => { const RobotPage = (): JSX.Element => {
const { setPage, setCurrentOrder, fetchRobot, torStatus, windowSize, robot, setRobot, baseUrl } = const { robot, setRobot, setPage, setCurrentOrder, fetchRobot, torStatus, windowSize, baseUrl } =
useContext<AppContextProps>(AppContext); useContext<AppContextProps>(AppContext);
const { t } = useTranslation(); const { t } = useTranslation();
const params = useParams(); const params = useParams();
@ -49,7 +47,7 @@ const RobotPage = (): JSX.Element => {
} }
}, []); }, []);
const getGenerateRobot = (token: string) => { const getGenerateRobot = (token: string, slot?: number) => {
setInputToken(token); setInputToken(token);
genKey(token).then(function (key) { genKey(token).then(function (key) {
fetchRobot({ fetchRobot({
@ -59,24 +57,16 @@ const RobotPage = (): JSX.Element => {
encPrivKey: key.encryptedPrivateKeyArmored, encPrivKey: key.encryptedPrivateKeyArmored,
}, },
newToken: token, newToken: token,
slot,
refCode, refCode,
setBadRequest, setBadRequest,
}); });
}); });
}; };
const deleteRobot = () => {
apiClient.delete(baseUrl, '/api/user');
logoutRobot();
};
const logoutRobot = () => { const logoutRobot = () => {
setInputToken(''); setInputToken('');
systemClient.deleteCookie('sessionid'); setRobot(new Robot());
systemClient.deleteItem('robot_token');
systemClient.deleteItem('pub_key');
systemClient.deleteItem('enc_priv_key');
setTimeout(() => setRobot(new Robot()), 10);
}; };
if (!(window.NativeRobosats === undefined) && !(torStatus == 'DONE' || torStatus == '"Done"')) { if (!(window.NativeRobosats === undefined) && !(torStatus == 'DONE' || torStatus == '"Done"')) {
@ -146,7 +136,7 @@ const RobotPage = (): JSX.Element => {
<Onboarding <Onboarding
setView={setView} setView={setView}
robot={robot} robot={robot}
setRobot={setRobot} setRobot={() => null}
badRequest={badRequest} badRequest={badRequest}
inputToken={inputToken} inputToken={inputToken}
setInputToken={setInputToken} setInputToken={setInputToken}
@ -160,9 +150,10 @@ const RobotPage = (): JSX.Element => {
<RobotProfile <RobotProfile
setView={setView} setView={setView}
robot={robot} robot={robot}
setRobot={setRobot} setRobot={() => null}
setCurrentOrder={setCurrentOrder} setCurrentOrder={setCurrentOrder}
badRequest={badRequest} badRequest={badRequest}
getGenerateRobot={getGenerateRobot}
logoutRobot={logoutRobot} logoutRobot={logoutRobot}
width={width} width={width}
inputToken={inputToken} inputToken={inputToken}
@ -177,7 +168,7 @@ const RobotPage = (): JSX.Element => {
<Recovery <Recovery
setView={setView} setView={setView}
robot={robot} robot={robot}
setRobot={setRobot} setRobot={() => null}
badRequest={badRequest} badRequest={badRequest}
inputToken={inputToken} inputToken={inputToken}
setInputToken={setInputToken} setInputToken={setInputToken}

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Dialog, Dialog,
@ -14,6 +14,7 @@ import {
} from '@mui/material'; } from '@mui/material';
import { systemClient } from '../../services/System'; import { systemClient } from '../../services/System';
import ContentCopy from '@mui/icons-material/ContentCopy'; import ContentCopy from '@mui/icons-material/ContentCopy';
import { AppContext, AppContextProps } from '../../contexts/AppContext';
interface Props { interface Props {
open: boolean; open: boolean;
@ -23,6 +24,7 @@ interface Props {
} }
const StoreTokenDialog = ({ open, onClose, onClickBack, onClickDone }: Props): JSX.Element => { const StoreTokenDialog = ({ open, onClose, onClickBack, onClickDone }: Props): JSX.Element => {
const { robot } = useContext<AppContextProps>(AppContext);
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -41,17 +43,13 @@ const StoreTokenDialog = ({ open, onClose, onClickBack, onClickDone }: Props): J
sx={{ width: '100%', maxWidth: '550px' }} sx={{ width: '100%', maxWidth: '550px' }}
disabled disabled
label={t('Back it up!')} label={t('Back it up!')}
value={systemClient.getItem('robot_token')} value={robot.token}
variant='filled' variant='filled'
size='small' size='small'
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}> <Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
<IconButton <IconButton onClick={() => systemClient.copyToClipboard(robot.token)}>
onClick={() =>
systemClient.copyToClipboard(systemClient.getItem('robot_token'))
}
>
<ContentCopy color='primary' /> <ContentCopy color='primary' />
</IconButton> </IconButton>
</Tooltip> </Tooltip>

View File

@ -40,6 +40,7 @@ const RobotAvatar: React.FC<Props> = ({
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const theme = useTheme();
const [avatarSrc, setAvatarSrc] = useState<string>(); const [avatarSrc, setAvatarSrc] = useState<string>();
const [nicknameReady, setNicknameReady] = useState<boolean>(false);
const backgroundData = const backgroundData =
placeholderType == 'generating' ? placeholder.generating : placeholder.loading; placeholderType == 'generating' ? placeholder.generating : placeholder.loading;
@ -55,11 +56,15 @@ const RobotAvatar: React.FC<Props> = ({
if (nickname != undefined) { if (nickname != undefined) {
if (window.NativeRobosats === undefined) { if (window.NativeRobosats === undefined) {
setAvatarSrc(baseUrl + '/static/assets/avatars/' + nickname + '.png'); setAvatarSrc(baseUrl + '/static/assets/avatars/' + nickname + '.png');
setNicknameReady(true);
} else { } else {
setNicknameReady(true);
apiClient apiClient
.fileImageUrl(baseUrl, '/static/assets/avatars/' + nickname + '.png') .fileImageUrl(baseUrl, '/static/assets/avatars/' + nickname + '.png')
.then(setAvatarSrc); .then(setAvatarSrc);
} }
} else {
setNicknameReady(false);
} }
}, [nickname]); }, [nickname]);
@ -93,7 +98,7 @@ const RobotAvatar: React.FC<Props> = ({
> >
<div className={className}> <div className={className}>
<SmoothImage <SmoothImage
src={avatarSrc} src={nicknameReady ? avatarSrc : null}
imageStyles={{ imageStyles={{
borderRadius: '50%', borderRadius: '50%',
border: '0.3px solid #55555', border: '0.3px solid #55555',
@ -110,7 +115,7 @@ const RobotAvatar: React.FC<Props> = ({
className={avatarClass} className={avatarClass}
style={style} style={style}
alt={nickname} alt={nickname}
src={avatarSrc} src={nicknameReady ? avatarSrc : null}
imgProps={{ imgProps={{
sx: { transform: flipHorizontally ? 'scaleX(-1)' : '' }, sx: { transform: flipHorizontally ? 'scaleX(-1)' : '' },
style: { transform: flipHorizontally ? 'scaleX(-1)' : '' }, style: { transform: flipHorizontally ? 'scaleX(-1)' : '' },

View File

@ -1,11 +1,10 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Tooltip, TextField, Grid, Container, Paper, Typography } from '@mui/material'; import { Button, Tooltip, TextField, Grid, Paper } from '@mui/material';
import { encryptMessage, decryptMessage } from '../../../../pgp'; import { encryptMessage, decryptMessage } from '../../../../pgp';
import { saveAsJson } from '../../../../utils';
import { AuditPGPDialog } from '../../../Dialogs'; import { AuditPGPDialog } from '../../../Dialogs';
import { systemClient } from '../../../../services/System';
import { websocketClient, WebsocketConnection } from '../../../../services/Websocket'; import { websocketClient, WebsocketConnection } from '../../../../services/Websocket';
import { Robot } from '../../../../models';
// Icons // Icons
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
@ -19,6 +18,7 @@ import ChatBottom from '../ChatBottom';
interface Props { interface Props {
orderId: number; orderId: number;
status: number; status: number;
robot: Robot;
userNick: string; userNick: string;
takerNick: string; takerNick: string;
messages: EncryptedChatMessage[]; messages: EncryptedChatMessage[];
@ -31,6 +31,7 @@ interface Props {
const EncryptedSocketChat: React.FC<Props> = ({ const EncryptedSocketChat: React.FC<Props> = ({
orderId, orderId,
status, status,
robot,
userNick, userNick,
takerNick, takerNick,
messages, messages,
@ -45,14 +46,10 @@ const EncryptedSocketChat: React.FC<Props> = ({
const audio = new Audio(`/static/assets/sounds/chat-open.mp3`); const audio = new Audio(`/static/assets/sounds/chat-open.mp3`);
const [connected, setConnected] = useState<boolean>(false); const [connected, setConnected] = useState<boolean>(false);
const [peerConnected, setPeerConnected] = useState<boolean>(false); const [peerConnected, setPeerConnected] = useState<boolean>(false);
const [ownPubKey] = useState<string>( const [ownPubKey] = useState<string>(robot.pubKey);
(systemClient.getItem('pub_key') ?? '').split('\\').join('\n'), const [ownEncPrivKey] = useState<string>(robot.encPrivKey);
);
const [ownEncPrivKey] = useState<string>(
(systemClient.getItem('enc_priv_key') ?? '').split('\\').join('\n'),
);
const [peerPubKey, setPeerPubKey] = useState<string>(); const [peerPubKey, setPeerPubKey] = useState<string>();
const [token] = useState<string>(systemClient.getItem('robot_token') || ''); const [token] = useState<string>(robot.token);
const [serverMessages, setServerMessages] = useState<ServerMessage[]>([]); const [serverMessages, setServerMessages] = useState<ServerMessage[]>([]);
const [value, setValue] = useState<string>(''); const [value, setValue] = useState<string>('');
const [connection, setConnection] = useState<WebsocketConnection>(); const [connection, setConnection] = useState<WebsocketConnection>();

View File

@ -1,9 +1,9 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, TextField, Grid, Container, Paper } from '@mui/material'; import { Button, TextField, Grid, Paper } from '@mui/material';
import { encryptMessage, decryptMessage } from '../../../../pgp'; import { encryptMessage, decryptMessage } from '../../../../pgp';
import { AuditPGPDialog } from '../../../Dialogs'; import { AuditPGPDialog } from '../../../Dialogs';
import { systemClient } from '../../../../services/System'; import { Robot } from '../../../../models';
// Icons // Icons
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
@ -17,6 +17,7 @@ import ChatBottom from '../ChatBottom';
interface Props { interface Props {
orderId: number; orderId: number;
robot: Robot;
userNick: string; userNick: string;
takerNick: string; takerNick: string;
chatOffset: number; chatOffset: number;
@ -29,6 +30,7 @@ interface Props {
const EncryptedTurtleChat: React.FC<Props> = ({ const EncryptedTurtleChat: React.FC<Props> = ({
orderId, orderId,
robot,
userNick, userNick,
takerNick, takerNick,
chatOffset, chatOffset,
@ -43,14 +45,10 @@ const EncryptedTurtleChat: React.FC<Props> = ({
const audio = new Audio(`/static/assets/sounds/chat-open.mp3`); const audio = new Audio(`/static/assets/sounds/chat-open.mp3`);
const [peerConnected, setPeerConnected] = useState<boolean>(false); const [peerConnected, setPeerConnected] = useState<boolean>(false);
const [ownPubKey] = useState<string>( const [ownPubKey] = useState<string>(robot.pubKey || '');
(systemClient.getItem('pub_key') ?? '').split('\\').join('\n'), const [ownEncPrivKey] = useState<string>(robot.encPrivKey || '');
);
const [ownEncPrivKey] = useState<string>(
(systemClient.getItem('enc_priv_key') ?? '').split('\\').join('\n'),
);
const [peerPubKey, setPeerPubKey] = useState<string>(); const [peerPubKey, setPeerPubKey] = useState<string>();
const [token] = useState<string>(systemClient.getItem('robot_token') || ''); const [token] = useState<string>(robot.token || '');
const [value, setValue] = useState<string>(''); const [value, setValue] = useState<string>('');
const [audit, setAudit] = useState<boolean>(false); const [audit, setAudit] = useState<boolean>(false);
const [waitingEcho, setWaitingEcho] = useState<boolean>(false); const [waitingEcho, setWaitingEcho] = useState<boolean>(false);
@ -171,8 +169,9 @@ const EncryptedTurtleChat: React.FC<Props> = ({
// If input string contains '#' send unencrypted and unlogged message // If input string contains '#' send unencrypted and unlogged message
else if (value.substring(0, 1) == '#') { else if (value.substring(0, 1) == '#') {
apiClient apiClient
.post(baseUrl, `/api/chat`, { .post(baseUrl, `/api/chat/`, {
PGP_message: value, PGP_message: value,
order_id: orderId,
offset: lastIndex, offset: lastIndex,
}) })
.then((response) => { .then((response) => {

View File

@ -1,9 +1,11 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Robot } from '../../../models';
import EncryptedSocketChat from './EncryptedSocketChat'; import EncryptedSocketChat from './EncryptedSocketChat';
import EncryptedTurtleChat from './EncryptedTurtleChat'; import EncryptedTurtleChat from './EncryptedTurtleChat';
interface Props { interface Props {
orderId: number; orderId: number;
status: number;
takerNick: string; takerNick: string;
makerNick: string; makerNick: string;
userNick: string; userNick: string;
@ -15,6 +17,7 @@ interface Props {
export interface EncryptedChatMessage { export interface EncryptedChatMessage {
userNick: string; userNick: string;
robot: Robot;
validSignature: boolean; validSignature: boolean;
plainTextMessage: string; plainTextMessage: string;
encryptedMessage: string; encryptedMessage: string;
@ -32,17 +35,20 @@ export interface ServerMessage {
const EncryptedChat: React.FC<Props> = ({ const EncryptedChat: React.FC<Props> = ({
orderId, orderId,
takerNick, takerNick,
robot,
userNick, userNick,
chatOffset, chatOffset,
baseUrl, baseUrl,
setMessages, setMessages,
messages, messages,
status,
}: Props): JSX.Element => { }: Props): JSX.Element => {
const [turtleMode, setTurtleMode] = useState<boolean>(window.ReactNativeWebView !== undefined); const [turtleMode, setTurtleMode] = useState<boolean>(window.ReactNativeWebView !== undefined);
return turtleMode ? ( return turtleMode ? (
<EncryptedTurtleChat <EncryptedTurtleChat
messages={messages} messages={messages}
robot={robot}
setMessages={setMessages} setMessages={setMessages}
orderId={orderId} orderId={orderId}
takerNick={takerNick} takerNick={takerNick}
@ -54,7 +60,9 @@ const EncryptedChat: React.FC<Props> = ({
/> />
) : ( ) : (
<EncryptedSocketChat <EncryptedSocketChat
status={status}
messages={messages} messages={messages}
robot={robot}
setMessages={setMessages} setMessages={setMessages}
orderId={orderId} orderId={orderId}
takerNick={takerNick} takerNick={takerNick}

View File

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Grid, Typography, Tooltip, Collapse, IconButton } from '@mui/material'; import { Grid, Typography, Tooltip, Collapse, IconButton } from '@mui/material';
import currencies from '../../../../static/assets/currencies.json'; import currencies from '../../../../static/assets/currencies.json';
import { Order } from '../../../models'; import { Order, Robot } from '../../../models';
import { pn } from '../../../utils'; import { pn } from '../../../utils';
import EncryptedChat, { EncryptedChatMessage } from '../EncryptedChat'; import EncryptedChat, { EncryptedChatMessage } from '../EncryptedChat';
import Countdown, { zeroPad } from 'react-countdown'; import Countdown, { zeroPad } from 'react-countdown';
@ -11,6 +11,7 @@ import { LoadingButton } from '@mui/lab';
interface ChatPromptProps { interface ChatPromptProps {
order: Order; order: Order;
robot: Robot;
onClickConfirmSent: () => void; onClickConfirmSent: () => void;
loadingSent: boolean; loadingSent: boolean;
onClickConfirmReceived: () => void; onClickConfirmReceived: () => void;
@ -24,6 +25,7 @@ interface ChatPromptProps {
export const ChatPrompt = ({ export const ChatPrompt = ({
order, order,
robot,
onClickConfirmSent, onClickConfirmSent,
onClickConfirmReceived, onClickConfirmReceived,
loadingSent, loadingSent,
@ -116,6 +118,7 @@ export const ChatPrompt = ({
<Grid item> <Grid item>
<EncryptedChat <EncryptedChat
status={order.status} status={order.status}
robot={robot}
chatOffset={order.chat_last_index} chatOffset={order.chat_last_index}
orderId={order.id} orderId={order.id}
takerNick={order.taker_nick} takerNick={order.taker_nick}

View File

@ -93,6 +93,7 @@ const closeAll: OpenDialogProps = {
interface TradeBoxProps { interface TradeBoxProps {
order: Order; order: Order;
setOrder: (state: Order) => void; setOrder: (state: Order) => void;
robot: Robot;
setBadOrder: (state: string | undefined) => void; setBadOrder: (state: string | undefined) => void;
onRenewOrder: () => void; onRenewOrder: () => void;
onStartAgain: () => void; onStartAgain: () => void;
@ -103,6 +104,7 @@ interface TradeBoxProps {
const TradeBox = ({ const TradeBox = ({
order, order,
setOrder, setOrder,
robot,
settings, settings,
baseUrl, baseUrl,
setBadOrder, setBadOrder,
@ -230,7 +232,7 @@ const TradeBox = ({
const submitStatement = function () { const submitStatement = function () {
let statement = dispute.statement; let statement = dispute.statement;
if (dispute.attachLogs) { if (dispute.attachLogs) {
const payload = { statement, messages, token: systemClient.getItem('robot_token') }; const payload = { statement, messages, token: robot.token };
statement = JSON.stringify(payload, null, 2); statement = JSON.stringify(payload, null, 2);
} }
setLoadingButtons({ ...noLoadingButtons, submitStatement: true }); setLoadingButtons({ ...noLoadingButtons, submitStatement: true });
@ -461,6 +463,7 @@ const TradeBox = ({
return ( return (
<ChatPrompt <ChatPrompt
order={order} order={order}
robot={robot}
onClickConfirmSent={confirmFiatSent} onClickConfirmSent={confirmFiatSent}
onClickConfirmReceived={() => setOpen({ ...open, confirmFiatReceived: true })} onClickConfirmReceived={() => setOpen({ ...open, confirmFiatReceived: true })}
loadingSent={loadingButtons.fiatSent} loadingSent={loadingButtons.fiatSent}

View File

@ -7,6 +7,7 @@ import {
LimitList, LimitList,
Maker, Maker,
Robot, Robot,
Garage,
Info, Info,
Settings, Settings,
Favorites, Favorites,
@ -22,7 +23,6 @@ import { sha256 } from 'js-sha256';
import defaultCoordinators from '../../static/federation.json'; import defaultCoordinators from '../../static/federation.json';
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { systemClient } from '../services/System';
const getWindowSize = function (fontSize: number) { const getWindowSize = function (fontSize: number) {
// returns window size in EM units // returns window size in EM units
@ -61,10 +61,11 @@ export interface SlideDirection {
} }
export interface fetchRobotProps { export interface fetchRobotProps {
action?: 'login' | 'generate'; action?: 'login' | 'generate' | 'refresh';
newKeys?: { encPrivKey: string; pubKey: string } | null; newKeys?: { encPrivKey: string; pubKey: string } | null;
newToken?: string | null; newToken?: string | null;
refCode?: string | null; refCode?: string | null;
slot?: number | null;
setBadRequest?: (state: string) => void; setBadRequest?: (state: string) => void;
} }
@ -78,6 +79,10 @@ export interface AppContextProps {
setSettings: (state: Settings) => void; setSettings: (state: Settings) => void;
book: Book; book: Book;
info: Info; info: Info;
garage: Garage;
setGarage: (state: Garage) => void;
currentSlot: number;
setCurrentSlot: (state: number) => void;
setBook: (state: Book) => void; setBook: (state: Book) => void;
fetchBook: () => void; fetchBook: () => void;
limits: { list: LimitList; loading: boolean }; limits: { list: LimitList; loading: boolean };
@ -200,7 +205,13 @@ export const AppContextProvider = ({
list: [], list: [],
loading: true, loading: true,
}); });
const [robot, setRobot] = useState<Robot>(new Robot()); const [garage, setGarage] = useState<Garage>(() => {
const initialState = { setGarage };
const newGarage = new Garage(initialState);
return newGarage;
});
const [currentSlot, setCurrentSlot] = useState<number>(garage.slots.length - 1);
const [robot, setRobot] = useState<Robot>(new Robot(garage.slots[currentSlot].robot));
const [maker, setMaker] = useState<Maker>(defaultMaker); const [maker, setMaker] = useState<Maker>(defaultMaker);
const [info, setInfo] = useState<Info>(defaultInfo); const [info, setInfo] = useState<Info>(defaultInfo);
const [coordinators, setCoordinators] = useState<Coordinator[]>(defaultCoordinators); const [coordinators, setCoordinators] = useState<Coordinator[]>(defaultCoordinators);
@ -219,7 +230,6 @@ export const AppContextProvider = ({
in: undefined, in: undefined,
out: undefined, out: undefined,
}); });
const [currentOrder, setCurrentOrder] = useState<number | undefined>(undefined); const [currentOrder, setCurrentOrder] = useState<number | undefined>(undefined);
const navbarHeight = 2.5; const navbarHeight = 2.5;
@ -239,6 +249,13 @@ export const AppContextProvider = ({
getWindowSize(theme.typography.fontSize), getWindowSize(theme.typography.fontSize),
); );
useEffect(() => {
window.addEventListener('torStatus', (event) => {
// UX improv: delay the "Conencted" status by 10 secs to avoid long waits for first requests
setTimeout(() => setTorStatus(event?.detail), event?.detail === '"Done"' ? 10000 : 0);
});
}, []);
useEffect(() => { useEffect(() => {
if (typeof window !== undefined) { if (typeof window !== undefined) {
window.addEventListener('resize', onResize); window.addEventListener('resize', onResize);
@ -257,15 +274,18 @@ export const AppContextProvider = ({
useEffect(() => { useEffect(() => {
let host = ''; let host = '';
let protocol = '';
if (window.NativeRobosats === undefined) { if (window.NativeRobosats === undefined) {
host = getHost(); host = getHost();
protocol = location.protocol;
} else { } else {
protocol = 'http:';
host = host =
settings.network === 'mainnet' settings.network === 'mainnet'
? coordinators[0].mainnetOnion ? coordinators[0].mainnetOnion
: coordinators[0].testnetOnion; : coordinators[0].testnetOnion;
} }
setBaseUrl(`${location.protocol}//${host}`); setBaseUrl(`${protocol}//${host}`);
}, [settings.network]); }, [settings.network]);
useEffect(() => { useEffect(() => {
@ -371,14 +391,19 @@ export const AppContextProvider = ({
newKeys = null, newKeys = null,
newToken = null, newToken = null,
refCode = null, refCode = null,
slot = null,
setBadRequest = () => {}, setBadRequest = () => {},
}: fetchRobotProps) { }: fetchRobotProps) {
setRobot({ ...robot, loading: true, avatarLoaded: false }); const oldRobot = robot;
const targetSlot = slot ?? currentSlot;
if (action != 'refresh') {
setRobot(new Robot());
}
setBadRequest(''); setBadRequest('');
let requestBody = {}; let requestBody = {};
if (action == 'login') { if (action == 'login' || action == 'refresh') {
requestBody.token_sha256 = sha256(newToken ?? robot.token); requestBody.token_sha256 = sha256(newToken ?? oldRobot.token);
} else if (action == 'generate' && newToken != null) { } else if (action == 'generate' && newToken != null) {
const strength = tokenStrength(newToken); const strength = tokenStrength(newToken);
requestBody.token_sha256 = sha256(newToken); requestBody.token_sha256 = sha256(newToken);
@ -386,11 +411,12 @@ export const AppContextProvider = ({
requestBody.counts = strength.counts; requestBody.counts = strength.counts;
requestBody.length = newToken.length; requestBody.length = newToken.length;
requestBody.ref_code = refCode; requestBody.ref_code = refCode;
requestBody.public_key = newKeys.pubKey ?? robot.pubkey; requestBody.public_key = newKeys.pubKey ?? oldRobot.pubkey;
requestBody.encrypted_private_key = newKeys.encPrivKey ?? robot.encPrivKey; requestBody.encrypted_private_key = newKeys.encPrivKey ?? oldRobot.encPrivKey;
} }
apiClient.post(baseUrl, '/api/user/', requestBody).then((data: any) => { apiClient.post(baseUrl, '/api/user/', requestBody).then((data: any) => {
let newRobot = robot;
setCurrentOrder( setCurrentOrder(
data.active_order_id data.active_order_id
? data.active_order_id ? data.active_order_id
@ -400,25 +426,25 @@ export const AppContextProvider = ({
); );
if (data.bad_request) { if (data.bad_request) {
setBadRequest(data.bad_request); setBadRequest(data.bad_request);
setRobot({ newRobot = {
...robot, ...oldRobot,
loading: false, loading: false,
nickname: data.nickname ?? robot.nickname, nickname: data.nickname ?? oldRobot.nickname,
activeOrderId: data.active_order_id ?? null, activeOrderId: data.active_order_id ?? null,
referralCode: data.referral_code ?? robot.referralCode, referralCode: data.referral_code ?? oldRobot.referralCode,
earnedRewards: data.earned_rewards ?? robot.earnedRewards, earnedRewards: data.earned_rewards ?? oldRobot.earnedRewards,
lastOrderId: data.last_order_id ?? robot.lastOrderId, lastOrderId: data.last_order_id ?? oldRobot.lastOrderId,
stealthInvoices: data.wants_stealth ?? robot.stealthInvoices, stealthInvoices: data.wants_stealth ?? robot.stealthInvoices,
tgEnabled: data.tg_enabled, tgEnabled: data.tg_enabled,
tgBotName: data.tg_bot_name, tgBotName: data.tg_bot_name,
tgToken: data.tg_token, tgToken: data.tg_token,
found: false, found: false,
}); };
} else { } else {
setRobot({ newRobot = {
...robot, ...oldRobot,
nickname: data.nickname, nickname: data.nickname,
token: newToken ?? robot.token, token: newToken ?? oldRobot.token,
loading: false, loading: false,
activeOrderId: data.active_order_id ?? null, activeOrderId: data.active_order_id ?? null,
lastOrderId: data.last_order_id ?? null, lastOrderId: data.last_order_id ?? null,
@ -433,11 +459,11 @@ export const AppContextProvider = ({
shannonEntropy: data.token_shannon_entropy, shannonEntropy: data.token_shannon_entropy,
pubKey: data.public_key, pubKey: data.public_key,
encPrivKey: data.encrypted_private_key, encPrivKey: data.encrypted_private_key,
copiedToken: data.found ? true : robot.copiedToken, copiedToken: data.found ? true : false,
}); };
systemClient.setItem('robot_token', newToken ?? robot.token); setRobot(newRobot);
systemClient.setItem('pub_key', data.public_key.split('\n').join('\\')); garage.updateRobot(newRobot, targetSlot);
systemClient.setItem('enc_priv_key', data.encrypted_private_key.split('\n').join('\\')); setCurrentSlot(targetSlot);
} }
}); });
}; };
@ -445,9 +471,9 @@ export const AppContextProvider = ({
useEffect(() => { useEffect(() => {
if (baseUrl != '' && page != 'robot') { if (baseUrl != '' && page != 'robot') {
if (open.profile || (robot.token && robot.nickname === null)) { if (open.profile || (robot.token && robot.nickname === null)) {
fetchRobot({ action: 'login' }); // fetch existing robot fetchRobot({ action: 'refresh' }); // refresh/update existing robot
} else if (robot.token && robot.encPrivKey && robot.pubKey) { } else if (robot.token && robot.encPrivKey && robot.pubKey) {
fetchRobot({ action: 'login' }); // create new robot with existing token and keys (on network and coordinator change) fetchRobot({ action: 'refresh' }); // create new robot with existing token and keys (on network and coordinator change)
} }
} }
}, [open.profile, baseUrl]); }, [open.profile, baseUrl]);
@ -460,8 +486,11 @@ export const AppContextProvider = ({
setSettings, setSettings,
book, book,
setBook, setBook,
garage,
setGarage,
currentSlot,
setCurrentSlot,
fetchBook, fetchBook,
fetchRobot,
limits, limits,
info, info,
setLimits, setLimits,
@ -471,6 +500,7 @@ export const AppContextProvider = ({
clearOrder, clearOrder,
robot, robot,
setRobot, setRobot,
fetchRobot,
baseUrl, baseUrl,
setBaseUrl, setBaseUrl,
fav, fav,

View File

@ -0,0 +1,51 @@
import { Robot, Order } from '.';
import { systemClient } from '../services/System';
import { saveAsJson } from '../utils';
export interface Slot {
robot: Robot;
order: Order | null;
}
const emptySlot: Slot = { robot: new Robot(), order: null };
class Garage {
constructor(initialState?: Garage) {
if (initialState?.slots === undefined && systemClient.getItem('garage') != '') {
this.slots = JSON.parse(systemClient.getItem('garage'));
console.log('Robot Garage was loaded from local storage');
} else {
this.slots = [emptySlot];
}
this.setGarage = initialState?.setGarage || (() => {});
}
slots: Slot[] = [emptySlot];
setGarage: (state: Garage) => void = () => {};
save = () => {
systemClient.setItem('garage', JSON.stringify(this.slots));
this.setGarage(new Garage(this));
};
delete = () => {
this.slots = [emptySlot];
systemClient.deleteItem('garage');
this.save();
};
updateRobot: (robot: Robot, index: number) => void = (robot, index) => {
this.slots[index] = { robot, order: null };
this.save();
};
download = () => {
saveAsJson(`robotGarage_${new Date().toISOString()}.json`, this.slots);
};
deleteSlot: (index?: number) => void = (index) => {
const targetSlot = index ?? this.slots.length - 1;
this.slots.splice(targetSlot, 1);
this.save();
};
}
export default Garage;

View File

@ -1,10 +1,10 @@
import { systemClient } from '../services/System';
class Robot { class Robot {
constructor() { constructor(garageRobot?: Robot) {
this.token = systemClient.getItem('robot_token') ?? undefined; if (garageRobot) {
this.pubKey = systemClient.getItem('pub_key') ?? undefined; this.token = garageRobot?.token ?? undefined;
this.encPrivKey = systemClient.getItem('enc_priv_key') ?? undefined; this.pubKey = garageRobot?.pubKey ?? undefined;
this.encPrivKey = garageRobot?.encPrivKey ?? undefined;
}
} }
public nickname?: string; public nickname?: string;

View File

@ -39,7 +39,7 @@ class BaseSettings {
: i18n.resolvedLanguage.substring(0, 2); : i18n.resolvedLanguage.substring(0, 2);
const networkCookie = systemClient.getItem('settings_network'); const networkCookie = systemClient.getItem('settings_network');
this.network = networkCookie !== '' ? networkCookie : undefined; this.network = networkCookie !== '' ? networkCookie : 'mainnet';
} }
public frontend: 'basic' | 'pro' = 'basic'; public frontend: 'basic' | 'pro' = 'basic';
@ -47,7 +47,7 @@ class BaseSettings {
public fontSize: number = 14; public fontSize: number = 14;
public language?: Language; public language?: Language;
public freezeViewports: boolean = false; public freezeViewports: boolean = false;
public network: 'mainnet' | 'testnet' | undefined = undefined; public network: 'mainnet' | 'testnet' = 'mainnet';
public coordinator: Coordinator | undefined = undefined; public coordinator: Coordinator | undefined = undefined;
public host?: string; public host?: string;
public unsafeClient: boolean = false; public unsafeClient: boolean = false;

View File

@ -1,6 +1,7 @@
import Robot from './Robot.model'; import Robot from './Robot.model';
import Garage from './Garage.model';
import Settings from './Settings.default.basic'; import Settings from './Settings.default.basic';
export { Robot, Settings }; export { Robot, Garage, Settings };
export type { LimitList } from './Limit.model'; export type { LimitList } from './Limit.model';
export type { Limit } from './Limit.model'; export type { Limit } from './Limit.model';
@ -9,6 +10,7 @@ export type { Order } from './Order.model';
export type { PublicOrder } from './Book.model'; export type { PublicOrder } from './Book.model';
export type { Book } from './Book.model'; export type { Book } from './Book.model';
export type { Info } from './Info.model'; export type { Info } from './Info.model';
export type { Slot } from './Garage.model';
export type { Language } from './Settings.model'; export type { Language } from './Settings.model';
export type { Favorites } from './Favorites.model'; export type { Favorites } from './Favorites.model';
export type { Coordinator } from './Coordinator.model'; export type { Coordinator } from './Coordinator.model';

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Ordre activa #{{orderID}}", "Active order #{{orderID}}": "Ordre activa #{{orderID}}",
"Last order #{{orderID}}": "Última ordre #{{orderID}}", "Last order #{{orderID}}": "Última ordre #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "La reutilització de la identitat de trading degrada la teva privadesa davant d'altres usuaris, coordinadors i observadors.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "La reutilització de la identitat de trading degrada la teva privadesa davant d'altres usuaris, coordinadors i observadors.",
"Generate a new Robot": "Generar un nou Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Guarda el teu token de manera segura", "Store your token safely": "Guarda el teu token de manera segura",
"Logout Robot": "Tanca sessió Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Descarrega el token i les credencials PGP",
"Copied!": "Copiat!", "Copied!": "Copiat!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Genera un token", "1. Generate a token": "1. Genera un token",
@ -144,7 +147,7 @@
"Public sell orders": "Ordres de venta públiques", "Public sell orders": "Ordres de venta públiques",
"Book liquidity": "Liquiditat en el llibre", "Book liquidity": "Liquiditat en el llibre",
"Today active robots": "Robots actius avui", "Today active robots": "Robots actius avui",
"24h non-KYC bitcoin premium": "Prima de bitcoin sense KYC en 24h", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Comissió del creador", "Maker fee": "Comissió del creador",
"Taker fee": "Comissió del prenedor", "Taker fee": "Comissió del prenedor",
"Current onchain payout fee": "Cost actual de rebre onchain", "Current onchain payout fee": "Cost actual de rebre onchain",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Ulož si svůj token bezpečně", "Store your token safely": "Ulož si svůj token bezpečně",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "Zkopirováno!!", "Copied!": "Zkopirováno!!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Veřejné prodejní nabídky", "Public sell orders": "Veřejné prodejní nabídky",
"Book liquidity": "Dostupná likvidita", "Book liquidity": "Dostupná likvidita",
"Today active robots": "Dnešní aktivní roboti", "Today active robots": "Dnešní aktivní roboti",
"24h non-KYC bitcoin premium": "24h no-KYC bitcoin přirážka", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Poplatek tvůrce", "Maker fee": "Poplatek tvůrce",
"Taker fee": "Poplatek příjemce", "Taker fee": "Poplatek příjemce",
"Current onchain payout fee": "Současný poplatek za vyplacení na onchain ", "Current onchain payout fee": "Současný poplatek za vyplacení na onchain ",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Verwahre deinen Token sicher", "Store your token safely": "Verwahre deinen Token sicher",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "Kopiert!", "Copied!": "Kopiert!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Öffentliche Verkaufsangebote", "Public sell orders": "Öffentliche Verkaufsangebote",
"Book liquidity": "Marktplatz-Liquidität", "Book liquidity": "Marktplatz-Liquidität",
"Today active robots": "Heute aktive Roboter", "Today active robots": "Heute aktive Roboter",
"24h non-KYC bitcoin premium": "24h non-KYC Bitcoin-Aufschlag", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Makergebühr", "Maker fee": "Makergebühr",
"Taker fee": "Takergebühr", "Taker fee": "Takergebühr",
"Current onchain payout fee": "Current onchain payout fee", "Current onchain payout fee": "Current onchain payout fee",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Store your token safely", "Store your token safely": "Store your token safely",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "Copied!", "Copied!": "Copied!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Public sell orders", "Public sell orders": "Public sell orders",
"Book liquidity": "Book liquidity", "Book liquidity": "Book liquidity",
"Today active robots": "Today active robots", "Today active robots": "Today active robots",
"24h non-KYC bitcoin premium": "24h non-KYC bitcoin premium", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Maker fee", "Maker fee": "Maker fee",
"Taker fee": "Taker fee", "Taker fee": "Taker fee",
"Current onchain payout fee": "Current onchain payout fee", "Current onchain payout fee": "Current onchain payout fee",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Guarda tu token de forma segura", "Store your token safely": "Guarda tu token de forma segura",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "¡Copiado!", "Copied!": "¡Copiado!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Órdenes de venta públicas", "Public sell orders": "Órdenes de venta públicas",
"Book liquidity": "Liquidez en el libro", "Book liquidity": "Liquidez en el libro",
"Today active robots": "Robots activos hoy", "Today active robots": "Robots activos hoy",
"24h non-KYC bitcoin premium": "Prima de bitcoin sin KYC en 24h", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Comisión del creador", "Maker fee": "Comisión del creador",
"Taker fee": "Comisión del tomador", "Taker fee": "Comisión del tomador",
"Current onchain payout fee": "Coste actual de recibir onchain", "Current onchain payout fee": "Coste actual de recibir onchain",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Gorde zure tokena era seguru batean", "Store your token safely": "Gorde zure tokena era seguru batean",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "Kopiatuta!", "Copied!": "Kopiatuta!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Salmenta eskaera publikoak", "Public sell orders": "Salmenta eskaera publikoak",
"Book liquidity": "Liburuaren likidezia", "Book liquidity": "Liburuaren likidezia",
"Today active robots": "Robot aktiboak gaur", "Today active robots": "Robot aktiboak gaur",
"24h non-KYC bitcoin premium": "24 orduko ez-KYC prima", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Egile kuota", "Maker fee": "Egile kuota",
"Taker fee": "Hartzaile kuota", "Taker fee": "Hartzaile kuota",
"Current onchain payout fee": "Oraingo onchain jasotze-kuota", "Current onchain payout fee": "Oraingo onchain jasotze-kuota",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Stockez votre jeton en sécurité", "Store your token safely": "Stockez votre jeton en sécurité",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "Copié!", "Copied!": "Copié!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Ordres de vente publics", "Public sell orders": "Ordres de vente publics",
"Book liquidity": "Liquidité du livre", "Book liquidity": "Liquidité du livre",
"Today active robots": "Robots actifs aujourd'hui", "Today active robots": "Robots actifs aujourd'hui",
"24h non-KYC bitcoin premium": "Prime bitcoin non-KYC 24h", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Frais du createur", "Maker fee": "Frais du createur",
"Taker fee": "Frais du preneur", "Taker fee": "Frais du preneur",
"Current onchain payout fee": "Current onchain payout fee", "Current onchain payout fee": "Current onchain payout fee",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Custodisci il tuo gettone in modo sicuro", "Store your token safely": "Custodisci il tuo gettone in modo sicuro",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "Copiato!", "Copied!": "Copiato!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Ordini di vendita pubblici", "Public sell orders": "Ordini di vendita pubblici",
"Book liquidity": "Registro della liquidità", "Book liquidity": "Registro della liquidità",
"Today active robots": "I Robottini attivi oggi", "Today active robots": "I Robottini attivi oggi",
"24h non-KYC bitcoin premium": "Premio bitcoin non-KYC 24h", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Commissione dell'offerente", "Maker fee": "Commissione dell'offerente",
"Taker fee": "Commissione dell'acquirente", "Taker fee": "Commissione dell'acquirente",
"Current onchain payout fee": "Current onchain payout fee", "Current onchain payout fee": "Current onchain payout fee",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Przechowuj swój token bezpiecznie", "Store your token safely": "Przechowuj swój token bezpiecznie",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "Skopiowane!", "Copied!": "Skopiowane!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Zlecenia sprzedaży publicznej", "Public sell orders": "Zlecenia sprzedaży publicznej",
"Book liquidity": "Płynność księgowa", "Book liquidity": "Płynność księgowa",
"Today active robots": "Dziś aktywne roboty", "Today active robots": "Dziś aktywne roboty",
"24h non-KYC bitcoin premium": "24h premia bitcoin non-KYC", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Opłata producenta", "Maker fee": "Opłata producenta",
"Taker fee": "Opłata takera", "Taker fee": "Opłata takera",
"Current onchain payout fee": "Current onchain payout fee", "Current onchain payout fee": "Current onchain payout fee",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Guarde seu token de forma segura", "Store your token safely": "Guarde seu token de forma segura",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "Copiado!", "Copied!": "Copiado!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Ordens de venda públicss", "Public sell orders": "Ordens de venda públicss",
"Book liquidity": "Liquidez do livro", "Book liquidity": "Liquidez do livro",
"Today active robots": "Robôs ativos hoje", "Today active robots": "Robôs ativos hoje",
"24h non-KYC bitcoin premium": "Prêmio de bitcoin não-KYC nas últimas 24 horas", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Taxa do criador", "Maker fee": "Taxa do criador",
"Taker fee": "Taxa do tomador", "Taker fee": "Taxa do tomador",
"Current onchain payout fee": "Taxa de pagamento onchain atual", "Current onchain payout fee": "Taxa de pagamento onchain atual",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Храните Ваш токен в безопасности", "Store your token safely": "Храните Ваш токен в безопасности",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "Скопировано!", "Copied!": "Скопировано!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Ордера на продажу", "Public sell orders": "Ордера на продажу",
"Book liquidity": "Ликвидность книги ордеров", "Book liquidity": "Ликвидность книги ордеров",
"Today active robots": "Сегодня активных роботов", "Today active robots": "Сегодня активных роботов",
"24h non-KYC bitcoin premium": "Наценка на Биткойн без ЗСК за 24 часа", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Комиссия мейкера", "Maker fee": "Комиссия мейкера",
"Taker fee": "Комиссия тейкера", "Taker fee": "Комиссия тейкера",
"Current onchain payout fee": "Текущая комиссия за выплату ончейн", "Current onchain payout fee": "Текущая комиссия за выплату ончейн",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "Spara din token säkert", "Store your token safely": "Spara din token säkert",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "Kopierat!", "Copied!": "Kopierat!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "Publika säljordrar", "Public sell orders": "Publika säljordrar",
"Book liquidity": "Orderbokslikviditet", "Book liquidity": "Orderbokslikviditet",
"Today active robots": "Aktiva robotar idag", "Today active robots": "Aktiva robotar idag",
"24h non-KYC bitcoin premium": "24h non-KYC bitcoin premium", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "Makeravgift", "Maker fee": "Makeravgift",
"Taker fee": "Takeravgift", "Taker fee": "Takeravgift",
"Current onchain payout fee": "Aktuell utbetalningsavgift (on-chain)", "Current onchain payout fee": "Aktuell utbetalningsavgift (on-chain)",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "รักษา token ของคุณไว้ให้ดี", "Store your token safely": "รักษา token ของคุณไว้ให้ดี",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "คัดลอกแล้ว!", "Copied!": "คัดลอกแล้ว!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "จำนวนรายการขาย", "Public sell orders": "จำนวนรายการขาย",
"Book liquidity": "สภาพคล่องทางบ้ญชี", "Book liquidity": "สภาพคล่องทางบ้ญชี",
"Today active robots": "จำนวนโรบอทที่ใช้งานในวันนี้", "Today active robots": "จำนวนโรบอทที่ใช้งานในวันนี้",
"24h non-KYC bitcoin premium": "ค่าพรีเมี่ยม 24 ชม.ที่แล้วสำหรับ bitcoin non-KYC", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "ค่าธรรมเนียม Maker", "Maker fee": "ค่าธรรมเนียม Maker",
"Taker fee": "ค่าธรรมเนียม Taker", "Taker fee": "ค่าธรรมเนียม Taker",
"Current onchain payout fee": "ค่าธรรมเนียมการจ่าย On-chain ตอนนี้", "Current onchain payout fee": "ค่าธรรมเนียมการจ่าย On-chain ตอนนี้",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "请安全地存储你的令牌", "Store your token safely": "请安全地存储你的令牌",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "已复制!", "Copied!": "已复制!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "公开出售订单", "Public sell orders": "公开出售订单",
"Book liquidity": "账面流动性", "Book liquidity": "账面流动性",
"Today active robots": "今天活跃的机器人", "Today active robots": "今天活跃的机器人",
"24h non-KYC bitcoin premium": "24小时 non-KYC 比特币溢价", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "挂单方费用", "Maker fee": "挂单方费用",
"Taker fee": "吃单方费用", "Taker fee": "吃单方费用",
"Current onchain payout fee": "当前链上支付费用", "Current onchain payout fee": "当前链上支付费用",

View File

@ -12,11 +12,14 @@
"Active order #{{orderID}}": "Active order #{{orderID}}", "Active order #{{orderID}}": "Active order #{{orderID}}",
"Last order #{{orderID}}": "Last order #{{orderID}}", "Last order #{{orderID}}": "Last order #{{orderID}}",
"Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.", "Reusing trading identity degrades your privacy against other users, coordinators and observers.": "Reusing trading identity degrades your privacy against other users, coordinators and observers.",
"Generate a new Robot": "Generate a new Robot", "Add a new Robot": "Add a new Robot",
"Logout": "Logout",
"Store your token safely": "請安全地存儲你的令牌", "Store your token safely": "請安全地存儲你的令牌",
"Logout Robot": "Logout Robot", "Robot Garage": "Robot Garage",
"Building...": "Building...",
"Add Robot": "Add Robot",
"Delete Garage": "Delete Garage",
"#4": "Phrases in basic/RobotPage/TokenInput.tsx", "#4": "Phrases in basic/RobotPage/TokenInput.tsx",
"Download token and PGP credentials": "Download token and PGP credentials",
"Copied!": "已複製!", "Copied!": "已複製!",
"#5": "Phrases in basic/RobotPage/Onboarding.tsx", "#5": "Phrases in basic/RobotPage/Onboarding.tsx",
"1. Generate a token": "1. Generate a token", "1. Generate a token": "1. Generate a token",
@ -144,7 +147,7 @@
"Public sell orders": "公開出售訂單", "Public sell orders": "公開出售訂單",
"Book liquidity": "賬面流動性", "Book liquidity": "賬面流動性",
"Today active robots": "今天活躍的機器人", "Today active robots": "今天活躍的機器人",
"24h non-KYC bitcoin premium": "24小時 non-KYC 比特幣溢價", "Last 24h mean premium": "Last 24h mean premium",
"Maker fee": "掛單方費用", "Maker fee": "掛單方費用",
"Taker fee": "吃單方費用", "Taker fee": "吃單方費用",
"Current onchain payout fee": "當前鏈上支付費用", "Current onchain payout fee": "當前鏈上支付費用",