From c0b8a6d3ac891dcfe99eae737d0ebef7ebf92f7e Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi <90936742+Reckless-Satoshi@users.noreply.github.com> Date: Thu, 2 Mar 2023 11:01:06 +0000 Subject: [PATCH] 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 --- frontend/src/basic/BookPage/index.tsx | 79 +--- frontend/src/basic/Main.tsx | 74 +-- frontend/src/basic/MakerPage/index.tsx | 21 +- frontend/src/basic/NavBar/NavBar.tsx | 9 +- frontend/src/basic/OrderPage/index.tsx | 14 +- frontend/src/basic/RobotPage/Onboarding.tsx | 30 +- frontend/src/basic/RobotPage/RobotProfile.tsx | 420 ++++++++++++------ frontend/src/basic/RobotPage/TokenInput.tsx | 101 ++--- frontend/src/basic/RobotPage/index.tsx | 25 +- .../src/components/Dialogs/StoreToken.tsx | 12 +- frontend/src/components/RobotAvatar/index.tsx | 9 +- .../EncryptedSocketChat/index.tsx | 17 +- .../EncryptedTurtleChat/index.tsx | 19 +- .../TradeBox/EncryptedChat/index.tsx | 8 + .../src/components/TradeBox/Prompts/Chat.tsx | 5 +- frontend/src/components/TradeBox/index.tsx | 5 +- frontend/src/contexts/AppContext.tsx | 86 ++-- frontend/src/models/Garage.model.ts | 51 +++ frontend/src/models/Robot.model.ts | 12 +- frontend/src/models/Settings.model.ts | 4 +- frontend/src/models/index.ts | 4 +- frontend/static/locales/ca.json | 11 +- frontend/static/locales/cs.json | 11 +- frontend/static/locales/de.json | 11 +- frontend/static/locales/en.json | 11 +- frontend/static/locales/es.json | 11 +- frontend/static/locales/eu.json | 11 +- frontend/static/locales/fr.json | 11 +- frontend/static/locales/it.json | 11 +- frontend/static/locales/pl.json | 11 +- frontend/static/locales/pt.json | 11 +- frontend/static/locales/ru.json | 11 +- frontend/static/locales/sv.json | 11 +- frontend/static/locales/th.json | 11 +- frontend/static/locales/zh-SI.json | 11 +- frontend/static/locales/zh-TR.json | 11 +- 36 files changed, 673 insertions(+), 497 deletions(-) create mode 100644 frontend/src/models/Garage.model.ts diff --git a/frontend/src/basic/BookPage/index.tsx b/frontend/src/basic/BookPage/index.tsx index 4a450a62..2fcc978e 100644 --- a/frontend/src/basic/BookPage/index.tsx +++ b/frontend/src/basic/BookPage/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Grid, ButtonGroup, Dialog, Box } from '@mui/material'; import { useHistory } from 'react-router-dom'; @@ -7,49 +7,24 @@ import DepthChart from '../../components/Charts/DepthChart'; import { NoRobotDialog } from '../../components/Dialogs'; import MakerForm from '../../components/MakerForm'; import BookTable from '../../components/BookTable'; -import { Page } from '../NavBar'; -import { Book, Favorites, LimitList, Maker } from '../../models'; // Icons import { BarChart, FormatListBulleted } from '@mui/icons-material'; +import { AppContext, AppContextProps } from '../../contexts/AppContext'; -interface BookPageProps { - book: Book; - limits: { list: LimitList; loading: boolean }; - 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, - fetchLimits, - clearOrder, - fav, - setFav, - onViewOrder, - maker, - setMaker, - windowSize, - hasRobot = false, - setPage = () => null, - setCurrentOrder = () => null, - baseUrl, -}: BookPageProps): JSX.Element => { +const BookPage = (): JSX.Element => { + const { + robot, + fetchBook, + clearOrder, + windowSize, + setPage, + setCurrentOrder, + baseUrl, + book, + setDelay, + setOrder, + } = useContext(AppContext); const { t } = useTranslation(); const history = useHistory(); const [view, setView] = useState<'list' | 'depth'>('list'); @@ -62,15 +37,16 @@ const BookPage = ({ const chartWidthEm = width - maxBookTableWidth; useEffect(() => { - if (book.orders.length < 1) { - fetchBook(true, false); - } else { - fetchBook(false, true); - } + fetchBook(); }, []); + const onViewOrder = function () { + setOrder(undefined); + setDelay(10000); + }; + const onOrderClicked = function (id: number) { - if (hasRobot) { + if (robot.avatarLoaded) { history.push('/order/' + id); setPage('order'); setCurrentOrder(id); @@ -108,6 +84,7 @@ const BookPage = ({ ); }; + return ( setOpenNoRobot(false)} setPage={setPage} /> @@ -115,21 +92,13 @@ const BookPage = ({ setOpenMaker(false)}> { clearOrder(); setCurrentOrder(id); setPage('order'); history.push('/order/' + id); }} - baseUrl={baseUrl} /> diff --git a/frontend/src/basic/Main.tsx b/frontend/src/basic/Main.tsx index a1ceaf69..97e27914 100644 --- a/frontend/src/basic/Main.tsx +++ b/frontend/src/basic/Main.tsx @@ -19,38 +19,18 @@ import { AppContextProps, AppContext } from '../contexts/AppContext'; const Main = (): JSX.Element => { const { t } = useTranslation(); const { - book, - fetchBook, - maker, - setMaker, - clearOrder, - torStatus, settings, - limits, - fetchLimits, robot, setRobot, - fetchRobot, - setOrder, - setDelay, - info, - fav, - setFav, baseUrl, order, page, setPage, slideDirection, - setSlideDirection, - currentOrder, - setCurrentOrder, closeAll, - open, setOpen, windowSize, - badOrder, navbarHeight, - setBadOrder, } = useContext(AppContext); const Router = window.NativeRobosats === undefined ? BrowserRouter : HashRouter; @@ -101,16 +81,7 @@ const Main = (): JSX.Element => { appear={slideDirection.in != undefined} >
- +
)} @@ -123,27 +94,7 @@ const Main = (): JSX.Element => { appear={slideDirection.in != undefined} >
- { - 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} - /> +
@@ -155,7 +106,7 @@ const Main = (): JSX.Element => { appear={slideDirection.in != undefined} >
- +
@@ -169,10 +120,7 @@ const Main = (): JSX.Element => { appear={slideDirection.in != undefined} >
- +
)} @@ -192,19 +140,9 @@ const Main = (): JSX.Element => {
- +
- + ); }; diff --git a/frontend/src/basic/MakerPage/index.tsx b/frontend/src/basic/MakerPage/index.tsx index 3a231a93..19567514 100644 --- a/frontend/src/basic/MakerPage/index.tsx +++ b/frontend/src/basic/MakerPage/index.tsx @@ -10,13 +10,18 @@ import BookTable from '../../components/BookTable'; import { AppContext, AppContextProps } from '../../contexts/AppContext'; -interface MakerPageProps { - hasRobot: boolean; -} - -const MakerPage = ({ hasRobot = false }: MakerPageProps): JSX.Element => { - const { book, fav, maker, clearOrder, windowSize, setCurrentOrder, navbarHeight, setPage } = - useContext(AppContext); +const MakerPage = (): JSX.Element => { + const { + robot, + book, + fav, + maker, + clearOrder, + windowSize, + setCurrentOrder, + navbarHeight, + setPage, + } = useContext(AppContext); const { t } = useTranslation(); const history = useHistory(); @@ -74,7 +79,7 @@ const MakerPage = ({ hasRobot = false }: MakerPageProps): JSX.Element => { setPage('order'); history.push('/order/' + id); }} - hasRobot={hasRobot} + hasRobot={robot.avatarLoaded} disableRequest={matches.length > 0 && !showMatches} collapseAll={showMatches} onSubmit={() => setShowMatches(matches.length > 0)} diff --git a/frontend/src/basic/NavBar/NavBar.tsx b/frontend/src/basic/NavBar/NavBar.tsx index 0fc9236a..bfe13f51 100644 --- a/frontend/src/basic/NavBar/NavBar.tsx +++ b/frontend/src/basic/NavBar/NavBar.tsx @@ -20,17 +20,16 @@ import { AppContext, AppContextProps } from '../../contexts/AppContext'; interface NavBarProps { width: number; height: number; - hasRobot: boolean; } -const NavBar = ({ width, height, hasRobot = false }: NavBarProps): JSX.Element => { +const NavBar = ({ width, height }: NavBarProps): JSX.Element => { const { + robot, page, settings, setPage, setSlideDirection, open, - robot, setOpen, closeAll, currentOrder, @@ -43,7 +42,7 @@ const NavBar = ({ width, height, hasRobot = false }: NavBarProps): JSX.Element = const smallBar = width < 50; 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' }; const pagesPosition = { robot: 1, @@ -137,7 +136,7 @@ const NavBar = ({ width, height, hasRobot = false }: NavBarProps): JSX.Element = sx={tabSx} label={smallBar ? undefined : t('Order')} value='order' - disabled={!hasRobot || currentOrder == undefined} + disabled={!robot.avatarLoaded || currentOrder == undefined} icon={} iconPosition='start' /> diff --git a/frontend/src/basic/OrderPage/index.tsx b/frontend/src/basic/OrderPage/index.tsx index c0aa632a..d9558e85 100644 --- a/frontend/src/basic/OrderPage/index.tsx +++ b/frontend/src/basic/OrderPage/index.tsx @@ -6,20 +6,18 @@ import { useHistory } from 'react-router-dom'; import TradeBox from '../../components/TradeBox'; import OrderDetails from '../../components/OrderDetails'; -import { Page } from '../NavBar'; -import { Order, Settings } from '../../models'; import { apiClient } from '../../services/api'; import { AppContext, AppContextProps } from '../../contexts/AppContext'; interface OrderPageProps { - hasRobot: boolean; locationOrderId: number; } -const OrderPage = ({ hasRobot = false, locationOrderId }: OrderPageProps): JSX.Element => { +const OrderPage = ({ locationOrderId }: OrderPageProps): JSX.Element => { const { windowSize, order, + robot, settings, setOrder, setCurrentOrder, @@ -106,7 +104,7 @@ const OrderPage = ({ hasRobot = false, locationOrderId }: OrderPageProps): JSX.E setOrder={setOrder} baseUrl={baseUrl} setPage={setPage} - hasRobot={hasRobot} + hasRobot={robot.avatarLoaded} />
@@ -121,6 +119,7 @@ const OrderPage = ({ hasRobot = false, locationOrderId }: OrderPageProps): JSX.E >
) diff --git a/frontend/src/basic/RobotPage/Onboarding.tsx b/frontend/src/basic/RobotPage/Onboarding.tsx index d6e2a9a3..626ee5f6 100644 --- a/frontend/src/basic/RobotPage/Onboarding.tsx +++ b/frontend/src/basic/RobotPage/Onboarding.tsx @@ -49,17 +49,16 @@ const Onboarding = ({ }: OnboardingProps): JSX.Element => { const { t } = useTranslation(); const history = useHistory(); - const theme = useTheme(); const [step, setStep] = useState<'1' | '2' | '3'>('1'); const [generatedToken, setGeneratedToken] = useState(false); - const [showMimickProgress, setShowMimickProgress] = useState(false); + const [loading, setLoading] = useState(false); const generateToken = () => { setGeneratedToken(true); setInputToken(genBase62Token(36)); - setShowMimickProgress(true); - setTimeout(() => setShowMimickProgress(false), 1000); + setLoading(true); + setTimeout(() => setLoading(false), 1000); }; const changePage = function (newPage: Page) { @@ -104,19 +103,16 @@ const Onboarding = ({ - {showMimickProgress ? ( - - ) : ( - null} - /> - )} + null} + /> diff --git a/frontend/src/basic/RobotPage/RobotProfile.tsx b/frontend/src/basic/RobotPage/RobotProfile.tsx index 60a6c8f4..83fb5c2f 100644 --- a/frontend/src/basic/RobotPage/RobotProfile.tsx +++ b/frontend/src/basic/RobotPage/RobotProfile.tsx @@ -1,22 +1,37 @@ -import React, { useState } from 'react'; +import React, { useState, useContext, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; -import { Button, Link, Grid, LinearProgress, Typography, Alert } from '@mui/material'; -import { Bolt, Logout, Refresh } from '@mui/icons-material'; +import { + 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 TokenInput from './TokenInput'; 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 { robot: Robot; setRobot: (state: Robot) => void; setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void; + getGenerateRobot: (token: string, slot?: number) => void; inputToken: string; setCurrentOrder: (state: number) => void; logoutRobot: () => void; + inputToken: string; setInputToken: (state: string) => void; - getGenerateRobot: (token: string) => void; setPage: (state: Page) => void; baseUrl: string; badRequest: string; @@ -27,9 +42,9 @@ const RobotProfile = ({ robot, setRobot, inputToken, + getGenerateRobot, setInputToken, setCurrentOrder, - getGenerateRobot, logoutRobot, setPage, setView, @@ -37,158 +52,287 @@ const RobotProfile = ({ baseUrl, width, }: RobotProfileProps): JSX.Element => { + const { currentSlot, garage, setCurrentSlot, windowSize } = + useContext(AppContext); const { t } = useTranslation(); + const theme = useTheme(); const history = useHistory(); + const [loading, setLoading] = useState(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 ( - - - {robot.avatarLoaded && robot.nickname ? ( - -
- {width < 19 ? null : ( - - )} - {robot.nickname} - {width < 19 ? null : ( - - )} -
-
- ) : ( - <> - {t('Building your robot!')} - - - )} -
- - - - - - {robot.found ? ( - - {t('Welcome back!')} - - ) : ( - <> - )} - - {robot.activeOrderId ? ( - - + + + + {robot.avatarLoaded && robot.nickname ? ( + +
+ {width < 19 ? null : ( + + )} + {robot.nickname} + {width < 19 ? null : ( + + )} +
+
+ ) : ( + <> + {t('Building your robot!')} + + + )}
- ) : null} - {robot.lastOrderId ? ( - + + + {robot.found && !robot.lastOrderId ? ( + + {t('Welcome back!')} + + ) : ( + <> + )} + + + {robot.activeOrderId && robot.avatarLoaded && robot.nickname ? ( - - - - - {t( - 'Reusing trading identity degrades your privacy against other users, coordinators and observers.', - )} + ) : null} + + {robot.lastOrderId && robot.avatarLoaded && robot.nickname ? ( + + + + + + + + + {t( + 'Reusing trading identity degrades your privacy against other users, coordinators and observers.', + )} + + + + - - - - - + + + + ) : null} + + + + + + + + + null} + /> - ) : null} - - - null} - /> - - - + + + {t('Robot Garage')} + + + + + + +
+ {t('Add Robot')} + + + + {window.NativeRobosats === undefined ? ( + + + + ) : null} + + + + + + + ); diff --git a/frontend/src/basic/RobotPage/TokenInput.tsx b/frontend/src/basic/RobotPage/TokenInput.tsx index 8969b369..1a2a0ec2 100644 --- a/frontend/src/basic/RobotPage/TokenInput.tsx +++ b/frontend/src/basic/RobotPage/TokenInput.tsx @@ -1,16 +1,16 @@ import React, { useEffect, useState } from 'react'; 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 { Download, ContentCopy } from '@mui/icons-material'; +import { ContentCopy } from '@mui/icons-material'; import { systemClient } from '../../services/System'; -import { saveAsJson } from '../../utils'; interface TokenInputProps { robot: Robot; editable?: boolean; showDownload?: boolean; fullWidth?: boolean; + loading?: boolean; setRobot: (state: Robot) => void; inputToken: string; autoFocusTarget?: 'textfield' | 'copyButton' | 'none'; @@ -27,12 +27,12 @@ const TokenInput = ({ showCopy = true, label, setRobot, - showDownload = false, fullWidth = true, onPressEnter, autoFocusTarget = 'textfield', inputToken, badRequest, + loading = false, setInputToken, }: TokenInputProps): JSX.Element => { const { t } = useTranslation(); @@ -52,56 +52,49 @@ const TokenInput = ({ }; }; - return ( - setInputToken(e.target.value)} - onKeyPress={(e) => { - if (e.key === 'Enter') { - onPressEnter(); - } - }} - InputProps={{ - startAdornment: showDownload ? ( - - saveAsJson(robot.nickname + '.json', createJsonFile())} - > - - - - ) : null, - endAdornment: showCopy ? ( - - { - systemClient.copyToClipboard(inputToken); - setShowCopied(true); - setTimeout(() => setShowCopied(false), 1000); - setRobot({ ...robot, copiedToken: true }); - }} - > - - - - ) : null, - }} - /> - ); + if (loading) { + return ; + } else { + return ( + setInputToken(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter') { + onPressEnter(); + } + }} + InputProps={{ + endAdornment: showCopy ? ( + + { + systemClient.copyToClipboard(inputToken); + setShowCopied(true); + setTimeout(() => setShowCopied(false), 1000); + setRobot({ ...robot, copiedToken: true }); + }} + > + + + + ) : null, + }} + /> + ); + } }; export default TokenInput; diff --git a/frontend/src/basic/RobotPage/index.tsx b/frontend/src/basic/RobotPage/index.tsx index 5ccfa660..1d63054e 100644 --- a/frontend/src/basic/RobotPage/index.tsx +++ b/frontend/src/basic/RobotPage/index.tsx @@ -12,9 +12,7 @@ import { } from '@mui/material'; import { useParams } from 'react-router-dom'; -import { Page } from '../NavBar'; import { Robot } from '../../models'; -import { systemClient } from '../../services/System'; import { apiClient } from '../../services/api'; import Onboarding from './Onboarding'; import Welcome from './Welcome'; @@ -25,7 +23,7 @@ import { genKey } from '../../pgp'; import { AppContext, AppContextProps } from '../../contexts/AppContext'; const RobotPage = (): JSX.Element => { - const { setPage, setCurrentOrder, fetchRobot, torStatus, windowSize, robot, setRobot, baseUrl } = + const { robot, setRobot, setPage, setCurrentOrder, fetchRobot, torStatus, windowSize, baseUrl } = useContext(AppContext); const { t } = useTranslation(); const params = useParams(); @@ -49,7 +47,7 @@ const RobotPage = (): JSX.Element => { } }, []); - const getGenerateRobot = (token: string) => { + const getGenerateRobot = (token: string, slot?: number) => { setInputToken(token); genKey(token).then(function (key) { fetchRobot({ @@ -59,24 +57,16 @@ const RobotPage = (): JSX.Element => { encPrivKey: key.encryptedPrivateKeyArmored, }, newToken: token, + slot, refCode, setBadRequest, }); }); }; - const deleteRobot = () => { - apiClient.delete(baseUrl, '/api/user'); - logoutRobot(); - }; - const logoutRobot = () => { setInputToken(''); - systemClient.deleteCookie('sessionid'); - systemClient.deleteItem('robot_token'); - systemClient.deleteItem('pub_key'); - systemClient.deleteItem('enc_priv_key'); - setTimeout(() => setRobot(new Robot()), 10); + setRobot(new Robot()); }; if (!(window.NativeRobosats === undefined) && !(torStatus == 'DONE' || torStatus == '"Done"')) { @@ -146,7 +136,7 @@ const RobotPage = (): JSX.Element => { null} badRequest={badRequest} inputToken={inputToken} setInputToken={setInputToken} @@ -160,9 +150,10 @@ const RobotPage = (): JSX.Element => { null} setCurrentOrder={setCurrentOrder} badRequest={badRequest} + getGenerateRobot={getGenerateRobot} logoutRobot={logoutRobot} width={width} inputToken={inputToken} @@ -177,7 +168,7 @@ const RobotPage = (): JSX.Element => { null} badRequest={badRequest} inputToken={inputToken} setInputToken={setInputToken} diff --git a/frontend/src/components/Dialogs/StoreToken.tsx b/frontend/src/components/Dialogs/StoreToken.tsx index 6ba6dbe5..ce6eb067 100644 --- a/frontend/src/components/Dialogs/StoreToken.tsx +++ b/frontend/src/components/Dialogs/StoreToken.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { useTranslation } from 'react-i18next'; import { Dialog, @@ -14,6 +14,7 @@ import { } from '@mui/material'; import { systemClient } from '../../services/System'; import ContentCopy from '@mui/icons-material/ContentCopy'; +import { AppContext, AppContextProps } from '../../contexts/AppContext'; interface Props { open: boolean; @@ -23,6 +24,7 @@ interface Props { } const StoreTokenDialog = ({ open, onClose, onClickBack, onClickDone }: Props): JSX.Element => { + const { robot } = useContext(AppContext); const { t } = useTranslation(); return ( @@ -41,17 +43,13 @@ const StoreTokenDialog = ({ open, onClose, onClickBack, onClickDone }: Props): J sx={{ width: '100%', maxWidth: '550px' }} disabled label={t('Back it up!')} - value={systemClient.getItem('robot_token')} + value={robot.token} variant='filled' size='small' InputProps={{ endAdornment: ( - - systemClient.copyToClipboard(systemClient.getItem('robot_token')) - } - > + systemClient.copyToClipboard(robot.token)}> diff --git a/frontend/src/components/RobotAvatar/index.tsx b/frontend/src/components/RobotAvatar/index.tsx index 79855855..7a4e9f0f 100644 --- a/frontend/src/components/RobotAvatar/index.tsx +++ b/frontend/src/components/RobotAvatar/index.tsx @@ -40,6 +40,7 @@ const RobotAvatar: React.FC = ({ const { t } = useTranslation(); const theme = useTheme(); const [avatarSrc, setAvatarSrc] = useState(); + const [nicknameReady, setNicknameReady] = useState(false); const backgroundData = placeholderType == 'generating' ? placeholder.generating : placeholder.loading; @@ -55,11 +56,15 @@ const RobotAvatar: React.FC = ({ if (nickname != undefined) { if (window.NativeRobosats === undefined) { setAvatarSrc(baseUrl + '/static/assets/avatars/' + nickname + '.png'); + setNicknameReady(true); } else { + setNicknameReady(true); apiClient .fileImageUrl(baseUrl, '/static/assets/avatars/' + nickname + '.png') .then(setAvatarSrc); } + } else { + setNicknameReady(false); } }, [nickname]); @@ -93,7 +98,7 @@ const RobotAvatar: React.FC = ({ >
= ({ className={avatarClass} style={style} alt={nickname} - src={avatarSrc} + src={nicknameReady ? avatarSrc : null} imgProps={{ sx: { transform: flipHorizontally ? 'scaleX(-1)' : '' }, style: { transform: flipHorizontally ? 'scaleX(-1)' : '' }, diff --git a/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx index 99347f70..b8f23be9 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx @@ -1,11 +1,10 @@ import React, { useEffect, useState } from 'react'; 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 { saveAsJson } from '../../../../utils'; import { AuditPGPDialog } from '../../../Dialogs'; -import { systemClient } from '../../../../services/System'; import { websocketClient, WebsocketConnection } from '../../../../services/Websocket'; +import { Robot } from '../../../../models'; // Icons import CircularProgress from '@mui/material/CircularProgress'; @@ -19,6 +18,7 @@ import ChatBottom from '../ChatBottom'; interface Props { orderId: number; status: number; + robot: Robot; userNick: string; takerNick: string; messages: EncryptedChatMessage[]; @@ -31,6 +31,7 @@ interface Props { const EncryptedSocketChat: React.FC = ({ orderId, status, + robot, userNick, takerNick, messages, @@ -45,14 +46,10 @@ const EncryptedSocketChat: React.FC = ({ const audio = new Audio(`/static/assets/sounds/chat-open.mp3`); const [connected, setConnected] = useState(false); const [peerConnected, setPeerConnected] = useState(false); - const [ownPubKey] = useState( - (systemClient.getItem('pub_key') ?? '').split('\\').join('\n'), - ); - const [ownEncPrivKey] = useState( - (systemClient.getItem('enc_priv_key') ?? '').split('\\').join('\n'), - ); + const [ownPubKey] = useState(robot.pubKey); + const [ownEncPrivKey] = useState(robot.encPrivKey); const [peerPubKey, setPeerPubKey] = useState(); - const [token] = useState(systemClient.getItem('robot_token') || ''); + const [token] = useState(robot.token); const [serverMessages, setServerMessages] = useState([]); const [value, setValue] = useState(''); const [connection, setConnection] = useState(); diff --git a/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx index 8edcc7b0..4badad61 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx @@ -1,9 +1,9 @@ import React, { useEffect, useState } from 'react'; 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 { AuditPGPDialog } from '../../../Dialogs'; -import { systemClient } from '../../../../services/System'; +import { Robot } from '../../../../models'; // Icons import CircularProgress from '@mui/material/CircularProgress'; @@ -17,6 +17,7 @@ import ChatBottom from '../ChatBottom'; interface Props { orderId: number; + robot: Robot; userNick: string; takerNick: string; chatOffset: number; @@ -29,6 +30,7 @@ interface Props { const EncryptedTurtleChat: React.FC = ({ orderId, + robot, userNick, takerNick, chatOffset, @@ -43,14 +45,10 @@ const EncryptedTurtleChat: React.FC = ({ const audio = new Audio(`/static/assets/sounds/chat-open.mp3`); const [peerConnected, setPeerConnected] = useState(false); - const [ownPubKey] = useState( - (systemClient.getItem('pub_key') ?? '').split('\\').join('\n'), - ); - const [ownEncPrivKey] = useState( - (systemClient.getItem('enc_priv_key') ?? '').split('\\').join('\n'), - ); + const [ownPubKey] = useState(robot.pubKey || ''); + const [ownEncPrivKey] = useState(robot.encPrivKey || ''); const [peerPubKey, setPeerPubKey] = useState(); - const [token] = useState(systemClient.getItem('robot_token') || ''); + const [token] = useState(robot.token || ''); const [value, setValue] = useState(''); const [audit, setAudit] = useState(false); const [waitingEcho, setWaitingEcho] = useState(false); @@ -171,8 +169,9 @@ const EncryptedTurtleChat: React.FC = ({ // If input string contains '#' send unencrypted and unlogged message else if (value.substring(0, 1) == '#') { apiClient - .post(baseUrl, `/api/chat`, { + .post(baseUrl, `/api/chat/`, { PGP_message: value, + order_id: orderId, offset: lastIndex, }) .then((response) => { diff --git a/frontend/src/components/TradeBox/EncryptedChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/index.tsx index 527fa827..dbdc1808 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/index.tsx @@ -1,9 +1,11 @@ import React, { useState } from 'react'; +import { Robot } from '../../../models'; import EncryptedSocketChat from './EncryptedSocketChat'; import EncryptedTurtleChat from './EncryptedTurtleChat'; interface Props { orderId: number; + status: number; takerNick: string; makerNick: string; userNick: string; @@ -15,6 +17,7 @@ interface Props { export interface EncryptedChatMessage { userNick: string; + robot: Robot; validSignature: boolean; plainTextMessage: string; encryptedMessage: string; @@ -32,17 +35,20 @@ export interface ServerMessage { const EncryptedChat: React.FC = ({ orderId, takerNick, + robot, userNick, chatOffset, baseUrl, setMessages, messages, + status, }: Props): JSX.Element => { const [turtleMode, setTurtleMode] = useState(window.ReactNativeWebView !== undefined); return turtleMode ? ( = ({ /> ) : ( void; loadingSent: boolean; onClickConfirmReceived: () => void; @@ -24,6 +25,7 @@ interface ChatPromptProps { export const ChatPrompt = ({ order, + robot, onClickConfirmSent, onClickConfirmReceived, loadingSent, @@ -116,6 +118,7 @@ export const ChatPrompt = ({ void; + robot: Robot; setBadOrder: (state: string | undefined) => void; onRenewOrder: () => void; onStartAgain: () => void; @@ -103,6 +104,7 @@ interface TradeBoxProps { const TradeBox = ({ order, setOrder, + robot, settings, baseUrl, setBadOrder, @@ -230,7 +232,7 @@ const TradeBox = ({ const submitStatement = function () { let statement = dispute.statement; 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); } setLoadingButtons({ ...noLoadingButtons, submitStatement: true }); @@ -461,6 +463,7 @@ const TradeBox = ({ return ( setOpen({ ...open, confirmFiatReceived: true })} loadingSent={loadingButtons.fiatSent} diff --git a/frontend/src/contexts/AppContext.tsx b/frontend/src/contexts/AppContext.tsx index 60454d21..b6cc886d 100644 --- a/frontend/src/contexts/AppContext.tsx +++ b/frontend/src/contexts/AppContext.tsx @@ -7,6 +7,7 @@ import { LimitList, Maker, Robot, + Garage, Info, Settings, Favorites, @@ -22,7 +23,6 @@ import { sha256 } from 'js-sha256'; import defaultCoordinators from '../../static/federation.json'; import { useTheme } from '@mui/material'; -import { systemClient } from '../services/System'; const getWindowSize = function (fontSize: number) { // returns window size in EM units @@ -61,10 +61,11 @@ export interface SlideDirection { } export interface fetchRobotProps { - action?: 'login' | 'generate'; + action?: 'login' | 'generate' | 'refresh'; newKeys?: { encPrivKey: string; pubKey: string } | null; newToken?: string | null; refCode?: string | null; + slot?: number | null; setBadRequest?: (state: string) => void; } @@ -78,6 +79,10 @@ export interface AppContextProps { setSettings: (state: Settings) => void; book: Book; info: Info; + garage: Garage; + setGarage: (state: Garage) => void; + currentSlot: number; + setCurrentSlot: (state: number) => void; setBook: (state: Book) => void; fetchBook: () => void; limits: { list: LimitList; loading: boolean }; @@ -200,7 +205,13 @@ export const AppContextProvider = ({ list: [], loading: true, }); - const [robot, setRobot] = useState(new Robot()); + const [garage, setGarage] = useState(() => { + const initialState = { setGarage }; + const newGarage = new Garage(initialState); + return newGarage; + }); + const [currentSlot, setCurrentSlot] = useState(garage.slots.length - 1); + const [robot, setRobot] = useState(new Robot(garage.slots[currentSlot].robot)); const [maker, setMaker] = useState(defaultMaker); const [info, setInfo] = useState(defaultInfo); const [coordinators, setCoordinators] = useState(defaultCoordinators); @@ -219,7 +230,6 @@ export const AppContextProvider = ({ in: undefined, out: undefined, }); - const [currentOrder, setCurrentOrder] = useState(undefined); const navbarHeight = 2.5; @@ -239,6 +249,13 @@ export const AppContextProvider = ({ 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(() => { if (typeof window !== undefined) { window.addEventListener('resize', onResize); @@ -257,15 +274,18 @@ export const AppContextProvider = ({ useEffect(() => { let host = ''; + let protocol = ''; if (window.NativeRobosats === undefined) { host = getHost(); + protocol = location.protocol; } else { + protocol = 'http:'; host = settings.network === 'mainnet' ? coordinators[0].mainnetOnion : coordinators[0].testnetOnion; } - setBaseUrl(`${location.protocol}//${host}`); + setBaseUrl(`${protocol}//${host}`); }, [settings.network]); useEffect(() => { @@ -371,14 +391,19 @@ export const AppContextProvider = ({ newKeys = null, newToken = null, refCode = null, + slot = null, setBadRequest = () => {}, }: fetchRobotProps) { - setRobot({ ...robot, loading: true, avatarLoaded: false }); + const oldRobot = robot; + const targetSlot = slot ?? currentSlot; + if (action != 'refresh') { + setRobot(new Robot()); + } setBadRequest(''); let requestBody = {}; - if (action == 'login') { - requestBody.token_sha256 = sha256(newToken ?? robot.token); + if (action == 'login' || action == 'refresh') { + requestBody.token_sha256 = sha256(newToken ?? oldRobot.token); } else if (action == 'generate' && newToken != null) { const strength = tokenStrength(newToken); requestBody.token_sha256 = sha256(newToken); @@ -386,11 +411,12 @@ export const AppContextProvider = ({ requestBody.counts = strength.counts; requestBody.length = newToken.length; requestBody.ref_code = refCode; - requestBody.public_key = newKeys.pubKey ?? robot.pubkey; - requestBody.encrypted_private_key = newKeys.encPrivKey ?? robot.encPrivKey; + requestBody.public_key = newKeys.pubKey ?? oldRobot.pubkey; + requestBody.encrypted_private_key = newKeys.encPrivKey ?? oldRobot.encPrivKey; } apiClient.post(baseUrl, '/api/user/', requestBody).then((data: any) => { + let newRobot = robot; setCurrentOrder( data.active_order_id ? data.active_order_id @@ -400,25 +426,25 @@ export const AppContextProvider = ({ ); if (data.bad_request) { setBadRequest(data.bad_request); - setRobot({ - ...robot, + newRobot = { + ...oldRobot, loading: false, - nickname: data.nickname ?? robot.nickname, + nickname: data.nickname ?? oldRobot.nickname, activeOrderId: data.active_order_id ?? null, - referralCode: data.referral_code ?? robot.referralCode, - earnedRewards: data.earned_rewards ?? robot.earnedRewards, - lastOrderId: data.last_order_id ?? robot.lastOrderId, + referralCode: data.referral_code ?? oldRobot.referralCode, + earnedRewards: data.earned_rewards ?? oldRobot.earnedRewards, + lastOrderId: data.last_order_id ?? oldRobot.lastOrderId, stealthInvoices: data.wants_stealth ?? robot.stealthInvoices, tgEnabled: data.tg_enabled, tgBotName: data.tg_bot_name, tgToken: data.tg_token, found: false, - }); + }; } else { - setRobot({ - ...robot, + newRobot = { + ...oldRobot, nickname: data.nickname, - token: newToken ?? robot.token, + token: newToken ?? oldRobot.token, loading: false, activeOrderId: data.active_order_id ?? null, lastOrderId: data.last_order_id ?? null, @@ -433,11 +459,11 @@ export const AppContextProvider = ({ shannonEntropy: data.token_shannon_entropy, pubKey: data.public_key, encPrivKey: data.encrypted_private_key, - copiedToken: data.found ? true : robot.copiedToken, - }); - systemClient.setItem('robot_token', newToken ?? robot.token); - systemClient.setItem('pub_key', data.public_key.split('\n').join('\\')); - systemClient.setItem('enc_priv_key', data.encrypted_private_key.split('\n').join('\\')); + copiedToken: data.found ? true : false, + }; + setRobot(newRobot); + garage.updateRobot(newRobot, targetSlot); + setCurrentSlot(targetSlot); } }); }; @@ -445,9 +471,9 @@ export const AppContextProvider = ({ useEffect(() => { if (baseUrl != '' && page != 'robot') { 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) { - 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]); @@ -460,8 +486,11 @@ export const AppContextProvider = ({ setSettings, book, setBook, + garage, + setGarage, + currentSlot, + setCurrentSlot, fetchBook, - fetchRobot, limits, info, setLimits, @@ -471,6 +500,7 @@ export const AppContextProvider = ({ clearOrder, robot, setRobot, + fetchRobot, baseUrl, setBaseUrl, fav, diff --git a/frontend/src/models/Garage.model.ts b/frontend/src/models/Garage.model.ts new file mode 100644 index 00000000..aa6796d3 --- /dev/null +++ b/frontend/src/models/Garage.model.ts @@ -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; diff --git a/frontend/src/models/Robot.model.ts b/frontend/src/models/Robot.model.ts index 532f4b0a..e79db8ae 100644 --- a/frontend/src/models/Robot.model.ts +++ b/frontend/src/models/Robot.model.ts @@ -1,10 +1,10 @@ -import { systemClient } from '../services/System'; - class Robot { - constructor() { - this.token = systemClient.getItem('robot_token') ?? undefined; - this.pubKey = systemClient.getItem('pub_key') ?? undefined; - this.encPrivKey = systemClient.getItem('enc_priv_key') ?? undefined; + constructor(garageRobot?: Robot) { + if (garageRobot) { + this.token = garageRobot?.token ?? undefined; + this.pubKey = garageRobot?.pubKey ?? undefined; + this.encPrivKey = garageRobot?.encPrivKey ?? undefined; + } } public nickname?: string; diff --git a/frontend/src/models/Settings.model.ts b/frontend/src/models/Settings.model.ts index cf7b3be9..dce68564 100644 --- a/frontend/src/models/Settings.model.ts +++ b/frontend/src/models/Settings.model.ts @@ -39,7 +39,7 @@ class BaseSettings { : i18n.resolvedLanguage.substring(0, 2); const networkCookie = systemClient.getItem('settings_network'); - this.network = networkCookie !== '' ? networkCookie : undefined; + this.network = networkCookie !== '' ? networkCookie : 'mainnet'; } public frontend: 'basic' | 'pro' = 'basic'; @@ -47,7 +47,7 @@ class BaseSettings { public fontSize: number = 14; public language?: Language; public freezeViewports: boolean = false; - public network: 'mainnet' | 'testnet' | undefined = undefined; + public network: 'mainnet' | 'testnet' = 'mainnet'; public coordinator: Coordinator | undefined = undefined; public host?: string; public unsafeClient: boolean = false; diff --git a/frontend/src/models/index.ts b/frontend/src/models/index.ts index fb5210a4..ccfdbc72 100644 --- a/frontend/src/models/index.ts +++ b/frontend/src/models/index.ts @@ -1,6 +1,7 @@ import Robot from './Robot.model'; +import Garage from './Garage.model'; import Settings from './Settings.default.basic'; -export { Robot, Settings }; +export { Robot, Garage, Settings }; export type { LimitList } 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 { Book } from './Book.model'; export type { Info } from './Info.model'; +export type { Slot } from './Garage.model'; export type { Language } from './Settings.model'; export type { Favorites } from './Favorites.model'; export type { Coordinator } from './Coordinator.model'; diff --git a/frontend/static/locales/ca.json b/frontend/static/locales/ca.json index a2021785..b80d0fea 100644 --- a/frontend/static/locales/ca.json +++ b/frontend/static/locales/ca.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Ordre activa #{{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.", - "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", - "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", - "Download token and PGP credentials": "Descarrega el token i les credencials PGP", "Copied!": "Copiat!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Genera un token", @@ -144,7 +147,7 @@ "Public sell orders": "Ordres de venta públiques", "Book liquidity": "Liquiditat en el llibre", "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", "Taker fee": "Comissió del prenedor", "Current onchain payout fee": "Cost actual de rebre onchain", diff --git a/frontend/static/locales/cs.json b/frontend/static/locales/cs.json index 1bef85f8..cc5c618d 100644 --- a/frontend/static/locales/cs.json +++ b/frontend/static/locales/cs.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "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ě", - "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "Zkopirováno!!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "Veřejné prodejní nabídky", "Book liquidity": "Dostupná likvidita", "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", "Taker fee": "Poplatek příjemce", "Current onchain payout fee": "Současný poplatek za vyplacení na onchain ", diff --git a/frontend/static/locales/de.json b/frontend/static/locales/de.json index b7eaca36..cf185052 100644 --- a/frontend/static/locales/de.json +++ b/frontend/static/locales/de.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "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", - "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "Kopiert!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "Öffentliche Verkaufsangebote", "Book liquidity": "Marktplatz-Liquidität", "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", "Taker fee": "Takergebühr", "Current onchain payout fee": "Current onchain payout fee", diff --git a/frontend/static/locales/en.json b/frontend/static/locales/en.json index f8ceedd3..bd8ff7eb 100644 --- a/frontend/static/locales/en.json +++ b/frontend/static/locales/en.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "Copied!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "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": "24h non-KYC bitcoin premium", + "Last 24h mean premium": "Last 24h mean premium", "Maker fee": "Maker fee", "Taker fee": "Taker fee", "Current onchain payout fee": "Current onchain payout fee", diff --git a/frontend/static/locales/es.json b/frontend/static/locales/es.json index 4d9e0217..356f4b1f 100644 --- a/frontend/static/locales/es.json +++ b/frontend/static/locales/es.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "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", - "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "¡Copiado!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "Órdenes de venta públicas", "Book liquidity": "Liquidez en el libro", "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", "Taker fee": "Comisión del tomador", "Current onchain payout fee": "Coste actual de recibir onchain", diff --git a/frontend/static/locales/eu.json b/frontend/static/locales/eu.json index 3295f206..77db47e2 100644 --- a/frontend/static/locales/eu.json +++ b/frontend/static/locales/eu.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "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", - "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "Kopiatuta!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "Salmenta eskaera publikoak", "Book liquidity": "Liburuaren likidezia", "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", "Taker fee": "Hartzaile kuota", "Current onchain payout fee": "Oraingo onchain jasotze-kuota", diff --git a/frontend/static/locales/fr.json b/frontend/static/locales/fr.json index 63624964..ef0e55cb 100644 --- a/frontend/static/locales/fr.json +++ b/frontend/static/locales/fr.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "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é", - "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "Copié!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "Ordres de vente publics", "Book liquidity": "Liquidité du livre", "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", "Taker fee": "Frais du preneur", "Current onchain payout fee": "Current onchain payout fee", diff --git a/frontend/static/locales/it.json b/frontend/static/locales/it.json index 518597ce..18210242 100644 --- a/frontend/static/locales/it.json +++ b/frontend/static/locales/it.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "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", - "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "Copiato!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "Ordini di vendita pubblici", "Book liquidity": "Registro della liquidità", "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", "Taker fee": "Commissione dell'acquirente", "Current onchain payout fee": "Current onchain payout fee", diff --git a/frontend/static/locales/pl.json b/frontend/static/locales/pl.json index be41bfce..f0881e34 100644 --- a/frontend/static/locales/pl.json +++ b/frontend/static/locales/pl.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "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", - "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "Skopiowane!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "Zlecenia sprzedaży publicznej", "Book liquidity": "Płynność księgowa", "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", "Taker fee": "Opłata takera", "Current onchain payout fee": "Current onchain payout fee", diff --git a/frontend/static/locales/pt.json b/frontend/static/locales/pt.json index a85e8cf6..357c457d 100644 --- a/frontend/static/locales/pt.json +++ b/frontend/static/locales/pt.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "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", - "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "Copiado!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "Ordens de venda públicss", "Book liquidity": "Liquidez do livro", "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", "Taker fee": "Taxa do tomador", "Current onchain payout fee": "Taxa de pagamento onchain atual", diff --git a/frontend/static/locales/ru.json b/frontend/static/locales/ru.json index b6823ebc..68ba1a51 100644 --- a/frontend/static/locales/ru.json +++ b/frontend/static/locales/ru.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "Generate a new Robot": "Generate a new Robot", + "Add a new Robot": "Add a new Robot", + "Logout": "Logout", "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "Скопировано!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "Ордера на продажу", "Book liquidity": "Ликвидность книги ордеров", "Today active robots": "Сегодня активных роботов", - "24h non-KYC bitcoin premium": "Наценка на Биткойн без ЗСК за 24 часа", + "Last 24h mean premium": "Last 24h mean premium", "Maker fee": "Комиссия мейкера", "Taker fee": "Комиссия тейкера", "Current onchain payout fee": "Текущая комиссия за выплату ончейн", diff --git a/frontend/static/locales/sv.json b/frontend/static/locales/sv.json index 43b40791..345f4eb4 100644 --- a/frontend/static/locales/sv.json +++ b/frontend/static/locales/sv.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "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", - "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "Kopierat!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "Publika säljordrar", "Book liquidity": "Orderbokslikviditet", "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", "Taker fee": "Takeravgift", "Current onchain payout fee": "Aktuell utbetalningsavgift (on-chain)", diff --git a/frontend/static/locales/th.json b/frontend/static/locales/th.json index d485f37a..80a4f267 100644 --- a/frontend/static/locales/th.json +++ b/frontend/static/locales/th.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "Generate a new Robot": "Generate a new Robot", + "Add a new Robot": "Add a new Robot", + "Logout": "Logout", "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "คัดลอกแล้ว!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "จำนวนรายการขาย", "Book liquidity": "สภาพคล่องทางบ้ญชี", "Today active robots": "จำนวนโรบอทที่ใช้งานในวันนี้", - "24h non-KYC bitcoin premium": "ค่าพรีเมี่ยม 24 ชม.ที่แล้วสำหรับ bitcoin non-KYC", + "Last 24h mean premium": "Last 24h mean premium", "Maker fee": "ค่าธรรมเนียม Maker", "Taker fee": "ค่าธรรมเนียม Taker", "Current onchain payout fee": "ค่าธรรมเนียมการจ่าย On-chain ตอนนี้", diff --git a/frontend/static/locales/zh-SI.json b/frontend/static/locales/zh-SI.json index 2424d2a2..31daf026 100644 --- a/frontend/static/locales/zh-SI.json +++ b/frontend/static/locales/zh-SI.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "Generate a new Robot": "Generate a new Robot", + "Add a new Robot": "Add a new Robot", + "Logout": "Logout", "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "已复制!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "公开出售订单", "Book liquidity": "账面流动性", "Today active robots": "今天活跃的机器人", - "24h non-KYC bitcoin premium": "24小时 non-KYC 比特币溢价", + "Last 24h mean premium": "Last 24h mean premium", "Maker fee": "挂单方费用", "Taker fee": "吃单方费用", "Current onchain payout fee": "当前链上支付费用", diff --git a/frontend/static/locales/zh-TR.json b/frontend/static/locales/zh-TR.json index 48d47a1c..7ee9dcd5 100644 --- a/frontend/static/locales/zh-TR.json +++ b/frontend/static/locales/zh-TR.json @@ -12,11 +12,14 @@ "Active order #{{orderID}}": "Active 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.", - "Generate a new Robot": "Generate a new Robot", + "Add a new Robot": "Add a new Robot", + "Logout": "Logout", "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", - "Download token and PGP credentials": "Download token and PGP credentials", "Copied!": "已複製!", "#5": "Phrases in basic/RobotPage/Onboarding.tsx", "1. Generate a token": "1. Generate a token", @@ -144,7 +147,7 @@ "Public sell orders": "公開出售訂單", "Book liquidity": "賬面流動性", "Today active robots": "今天活躍的機器人", - "24h non-KYC bitcoin premium": "24小時 non-KYC 比特幣溢價", + "Last 24h mean premium": "Last 24h mean premium", "Maker fee": "掛單方費用", "Taker fee": "吃單方費用", "Current onchain payout fee": "當前鏈上支付費用",