mirror of
https://github.com/RoboSats/robosats.git
synced 2024-09-11 14:05:51 +03:00
Add App context (#369)
* Refactor fetchRobot * Add appcontext * Robot recovery fixes * Add usecontext on maker and settings forms * Add usecontext to booktable * Add useContext to order page and main dialogs * Small fixes
This commit is contained in:
parent
d815fb8a8e
commit
d88c2a5eff
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
@ -1,5 +1,8 @@
|
||||
## What does this PR do?
|
||||
Fixes #<PR_NUMBER/>
|
||||
Fixes #<ISSUE_NUMBER>
|
||||
|
||||
This PR introduces/refactors/...
|
||||
|
||||
## Checklist before merging
|
||||
- [ ] Make sure you have installed and initialized [pre-commit](https://pre-commit.com). `pip install pre-commit` and `pre-commit install`
|
||||
- [ ] If it's a frontend feature, I have ran prettier `cd frontend; npm run format`. If it's a mobile app feature I ran `cd mobile; npm run format`.
|
||||
- [ ] If I added new phrases to the user interface, I have ran prettier `cd frontend/static/locales; python collect_phrases.py` to collect them for translation.
|
@ -21,17 +21,15 @@ repos:
|
||||
- id: check-docstring-first
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: collect-phrases
|
||||
name: collect-phrases
|
||||
stages:
|
||||
- commit
|
||||
- merge-commit
|
||||
language: system
|
||||
files: ^frontend/src/
|
||||
types_or: [javascript, jsx, ts, tsx] # uses https://github.com/pre-commit/identify
|
||||
entry: bash -c 'cd frontend/static/locales && python3 collect_phrases.py'
|
||||
- repo: local
|
||||
hooks:
|
||||
# - id: collect-phrases
|
||||
# name: Collect i18n phrases
|
||||
# stages:
|
||||
# - commit
|
||||
# - merge-commit
|
||||
# language: system
|
||||
# files: ^frontend/
|
||||
# types_or: [javascript, jsx, ts, tsx] # uses https://github.com/pre-commit/identify
|
||||
# entry: bash -c 'cd frontend/static/locales && python3 collect_phrases.py'
|
||||
- id: prettier-frontend
|
||||
name: prettier-frontend
|
||||
stages:
|
||||
|
@ -759,9 +759,8 @@ class UserView(APIView):
|
||||
if last_order:
|
||||
context["last_order_id"] = last_order.id
|
||||
|
||||
# Sends the welcome back message, only if created +3 mins ago
|
||||
if request.user.date_joined < (timezone.now() - timedelta(minutes=3)):
|
||||
context["found"] = "We found your Robot avatar. Welcome back!"
|
||||
# Sends the welcome back message.
|
||||
context["found"] = "We found your Robot avatar. Welcome back!"
|
||||
return Response(context, status=status.HTTP_202_ACCEPTED)
|
||||
else:
|
||||
# It is unlikely, but maybe the nickname is taken (1 in 20 Billion chance)
|
||||
|
@ -2,6 +2,7 @@ import React, { Suspense, useState, useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import Main from './basic/Main';
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import { AppContextProvider } from './contexts/AppContext';
|
||||
import { ThemeProvider, createTheme, Theme } from '@mui/material/styles';
|
||||
import UnsafeAlert from './components/UnsafeAlert';
|
||||
import TorConnectionBadge from './components/TorConnection';
|
||||
@ -29,14 +30,6 @@ const makeTheme = function (settings: Settings) {
|
||||
const App = (): JSX.Element => {
|
||||
const [theme, setTheme] = useState<Theme>(makeTheme(new Settings()));
|
||||
const [settings, setSettings] = useState<Settings>(new Settings());
|
||||
const [torStatus, setTorStatus] = useState<string>('NOTINIT');
|
||||
|
||||
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(() => {
|
||||
setTheme(makeTheme(settings));
|
||||
@ -50,13 +43,15 @@ const App = (): JSX.Element => {
|
||||
<Suspense fallback='loading language'>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
{window.NativeRobosats === undefined ? (
|
||||
<UnsafeAlert settings={settings} setSettings={setSettings} />
|
||||
) : (
|
||||
<TorConnectionBadge torStatus={torStatus} />
|
||||
)}
|
||||
<Main settings={settings} setSettings={setSettings} torStatus={torStatus} />
|
||||
<AppContextProvider settings={settings} setSettings={setSettings}>
|
||||
<CssBaseline />
|
||||
{window.NativeRobosats === undefined ? (
|
||||
<UnsafeAlert settings={settings} setSettings={setSettings} />
|
||||
) : (
|
||||
<TorConnectionBadge />
|
||||
)}
|
||||
<Main />
|
||||
</AppContextProvider>
|
||||
</ThemeProvider>
|
||||
</I18nextProvider>
|
||||
</Suspense>
|
||||
|
@ -147,56 +147,36 @@ const BookPage = ({
|
||||
>
|
||||
<Grid item>
|
||||
<BookTable
|
||||
clickRefresh={() => fetchBook()}
|
||||
book={book}
|
||||
fav={fav}
|
||||
setFav={setFav}
|
||||
maxWidth={maxBookTableWidth} // EM units
|
||||
maxHeight={windowSize.height * 0.825 - 5} // EM units
|
||||
fullWidth={windowSize.width} // EM units
|
||||
fullHeight={windowSize.height} // EM units
|
||||
defaultFullscreen={false}
|
||||
onOrderClicked={onOrderClicked}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<DepthChart
|
||||
orders={book.orders}
|
||||
lastDayPremium={lastDayPremium}
|
||||
currency={fav.currency}
|
||||
limits={limits.list}
|
||||
maxWidth={chartWidthEm} // EM units
|
||||
maxHeight={windowSize.height * 0.825 - 5} // EM units
|
||||
onOrderClicked={onOrderClicked}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : view === 'depth' ? (
|
||||
<DepthChart
|
||||
orders={book.orders}
|
||||
lastDayPremium={lastDayPremium}
|
||||
currency={fav.currency}
|
||||
limits={limits.list}
|
||||
maxWidth={windowSize.width * 0.8} // EM units
|
||||
maxHeight={windowSize.height * 0.825 - 5} // EM units
|
||||
onOrderClicked={onOrderClicked}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
) : (
|
||||
<BookTable
|
||||
book={book}
|
||||
clickRefresh={() => fetchBook()}
|
||||
fav={fav}
|
||||
setFav={setFav}
|
||||
maxWidth={windowSize.width * 0.97} // EM units
|
||||
maxHeight={windowSize.height * 0.825 - 5} // EM units
|
||||
fullWidth={windowSize.width} // EM units
|
||||
fullHeight={windowSize.height} // EM units
|
||||
defaultFullscreen={false}
|
||||
onOrderClicked={onOrderClicked}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
|
@ -1,312 +1,63 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { HashRouter, BrowserRouter, Switch, Route } from 'react-router-dom';
|
||||
import { useTheme, Box, Slide, Typography } from '@mui/material';
|
||||
import { Box, Slide, Typography } from '@mui/material';
|
||||
|
||||
import RobotPage from './RobotPage';
|
||||
import MakerPage from './MakerPage';
|
||||
import BookPage from './BookPage';
|
||||
import OrderPage from './OrderPage';
|
||||
import SettingsPage from './SettingsPage';
|
||||
import NavBar, { Page } from './NavBar';
|
||||
import MainDialogs, { OpenDialogs } from './MainDialogs';
|
||||
import NavBar from './NavBar';
|
||||
import MainDialogs from './MainDialogs';
|
||||
|
||||
import RobotAvatar from '../components/RobotAvatar';
|
||||
import {
|
||||
Book,
|
||||
LimitList,
|
||||
Maker,
|
||||
Robot,
|
||||
Info,
|
||||
Settings,
|
||||
Favorites,
|
||||
defaultMaker,
|
||||
defaultInfo,
|
||||
Coordinator,
|
||||
Order,
|
||||
} from '../models';
|
||||
|
||||
import { apiClient } from '../services/api';
|
||||
import { checkVer, getHost } from '../utils';
|
||||
import { sha256 } from 'js-sha256';
|
||||
|
||||
import defaultCoordinators from '../../static/federation.json';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Notifications from '../components/Notifications';
|
||||
import { AppContextProps, AppContext } from '../contexts/AppContext';
|
||||
|
||||
const getWindowSize = function (fontSize: number) {
|
||||
// returns window size in EM units
|
||||
return {
|
||||
width: window.innerWidth / fontSize,
|
||||
height: window.innerHeight / fontSize,
|
||||
};
|
||||
};
|
||||
|
||||
// Refresh delays (ms) according to Order status
|
||||
const statusToDelay = [
|
||||
3000, // 'Waiting for maker bond'
|
||||
35000, // 'Public'
|
||||
180000, // 'Paused'
|
||||
3000, // 'Waiting for taker bond'
|
||||
999999, // 'Cancelled'
|
||||
999999, // 'Expired'
|
||||
8000, // 'Waiting for trade collateral and buyer invoice'
|
||||
8000, // 'Waiting only for seller trade collateral'
|
||||
8000, // 'Waiting only for buyer invoice'
|
||||
10000, // 'Sending fiat - In chatroom'
|
||||
10000, // 'Fiat sent - In chatroom'
|
||||
100000, // 'In dispute'
|
||||
999999, // 'Collaboratively cancelled'
|
||||
10000, // 'Sending satoshis to buyer'
|
||||
999999, // 'Sucessful trade'
|
||||
30000, // 'Failed lightning network routing'
|
||||
300000, // 'Wait for dispute resolution'
|
||||
300000, // 'Maker lost dispute'
|
||||
300000, // 'Taker lost dispute'
|
||||
];
|
||||
|
||||
interface SlideDirection {
|
||||
in: 'left' | 'right' | undefined;
|
||||
out: 'left' | 'right' | undefined;
|
||||
}
|
||||
|
||||
interface MainProps {
|
||||
settings: Settings;
|
||||
torStatus: 'NOTINIT' | 'STARTING' | '"Done"' | 'DONE';
|
||||
setSettings: (state: Settings) => void;
|
||||
}
|
||||
|
||||
const Main = ({ torStatus, settings, setSettings }: MainProps): JSX.Element => {
|
||||
const Main = (): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
// All app data structured
|
||||
const [book, setBook] = useState<Book>({ orders: [], loading: true });
|
||||
const [limits, setLimits] = useState<{ list: LimitList; loading: boolean }>({
|
||||
list: [],
|
||||
loading: true,
|
||||
});
|
||||
const [robot, setRobot] = useState<Robot>(new Robot());
|
||||
const [maker, setMaker] = useState<Maker>(defaultMaker);
|
||||
const [info, setInfo] = useState<Info>(defaultInfo);
|
||||
const [coordinators, setCoordinators] = useState<Coordinator[]>(defaultCoordinators);
|
||||
const [baseUrl, setBaseUrl] = useState<string>('');
|
||||
const [fav, setFav] = useState<Favorites>({ type: null, mode: 'fiat', currency: 0 });
|
||||
|
||||
const [delay, setDelay] = useState<number>(60000);
|
||||
const [timer, setTimer] = useState<NodeJS.Timer | undefined>(setInterval(() => null, delay));
|
||||
const [order, setOrder] = useState<Order | undefined>(undefined);
|
||||
const [badOrder, setBadOrder] = useState<string | undefined>(undefined);
|
||||
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<AppContextProps>(AppContext);
|
||||
|
||||
const Router = window.NativeRobosats === undefined ? BrowserRouter : HashRouter;
|
||||
const basename = window.NativeRobosats === undefined ? '' : window.location.pathname;
|
||||
const entryPage: Page | '' =
|
||||
window.NativeRobosats === undefined ? window.location.pathname.split('/')[1] : '';
|
||||
const [page, setPage] = useState<Page>(entryPage == '' ? 'robot' : entryPage);
|
||||
const [slideDirection, setSlideDirection] = useState<SlideDirection>({
|
||||
in: undefined,
|
||||
out: undefined,
|
||||
});
|
||||
|
||||
const [currentOrder, setCurrentOrder] = useState<number | undefined>(undefined);
|
||||
|
||||
const navbarHeight = 2.5;
|
||||
const closeAll = {
|
||||
more: false,
|
||||
learn: false,
|
||||
community: false,
|
||||
info: false,
|
||||
coordinator: false,
|
||||
stats: false,
|
||||
update: false,
|
||||
profile: false,
|
||||
};
|
||||
const [open, setOpen] = useState<OpenDialogs>(closeAll);
|
||||
|
||||
const [windowSize, setWindowSize] = useState<{ width: number; height: number }>(
|
||||
getWindowSize(theme.typography.fontSize),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== undefined) {
|
||||
window.addEventListener('resize', onResize);
|
||||
}
|
||||
|
||||
if (baseUrl != '') {
|
||||
fetchBook();
|
||||
fetchLimits();
|
||||
}
|
||||
return () => {
|
||||
if (typeof window !== undefined) {
|
||||
window.removeEventListener('resize', onResize);
|
||||
}
|
||||
};
|
||||
}, [baseUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
let host = '';
|
||||
if (window.NativeRobosats === undefined) {
|
||||
host = getHost();
|
||||
} else {
|
||||
host =
|
||||
settings.network === 'mainnet'
|
||||
? coordinators[0].mainnetOnion
|
||||
: coordinators[0].testnetOnion;
|
||||
}
|
||||
setBaseUrl(`${location.protocol}//${host}`);
|
||||
}, [settings.network]);
|
||||
|
||||
useEffect(() => {
|
||||
setWindowSize(getWindowSize(theme.typography.fontSize));
|
||||
}, [theme.typography.fontSize]);
|
||||
|
||||
const onResize = function () {
|
||||
setWindowSize(getWindowSize(theme.typography.fontSize));
|
||||
};
|
||||
|
||||
const fetchBook = function () {
|
||||
setBook({ ...book, loading: true });
|
||||
apiClient.get(baseUrl, '/api/book/').then((data: any) =>
|
||||
setBook({
|
||||
loading: false,
|
||||
orders: data.not_found ? [] : data,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const fetchLimits = async () => {
|
||||
setLimits({ ...limits, loading: true });
|
||||
const data = apiClient.get(baseUrl, '/api/limits/').then((data) => {
|
||||
setLimits({ list: data ?? [], loading: false });
|
||||
return data;
|
||||
});
|
||||
return await data;
|
||||
};
|
||||
|
||||
const fetchInfo = function () {
|
||||
setInfo({ ...info, loading: true });
|
||||
apiClient.get(baseUrl, '/api/info/').then((data: Info) => {
|
||||
const versionInfo: any = checkVer(data.version.major, data.version.minor, data.version.patch);
|
||||
setInfo({
|
||||
...data,
|
||||
openUpdateClient: versionInfo.updateAvailable,
|
||||
coordinatorVersion: versionInfo.coordinatorVersion,
|
||||
clientVersion: versionInfo.clientVersion,
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open.stats || open.coordinator || info.coordinatorVersion == 'v?.?.?') {
|
||||
fetchInfo();
|
||||
}
|
||||
}, [open.stats, open.coordinator]);
|
||||
|
||||
useEffect(() => {
|
||||
// Sets Setting network from coordinator API param if accessing via web
|
||||
if (settings.network == undefined && info.network) {
|
||||
setSettings((settings: Settings) => {
|
||||
return { ...settings, network: info.network };
|
||||
});
|
||||
}
|
||||
}, [info]);
|
||||
|
||||
const fetchRobot = function ({ keys = false }) {
|
||||
const requestBody = {
|
||||
token_sha256: sha256(robot.token),
|
||||
};
|
||||
if (keys) {
|
||||
requestBody.pub_key = robot.pubKey;
|
||||
requestBody.enc_priv_key = robot.encPrivKey;
|
||||
}
|
||||
|
||||
setRobot({ ...robot, loading: true });
|
||||
apiClient.post(baseUrl, '/api/user/', requestBody).then((data: any) => {
|
||||
setCurrentOrder(
|
||||
data.active_order_id
|
||||
? data.active_order_id
|
||||
: data.last_order_id
|
||||
? data.last_order_id
|
||||
: null,
|
||||
);
|
||||
setRobot({
|
||||
...robot,
|
||||
nickname: data.nickname,
|
||||
token: robot.token,
|
||||
loading: false,
|
||||
avatarLoaded: robot.nickname === data.nickname,
|
||||
activeOrderId: data.active_order_id ? data.active_order_id : null,
|
||||
lastOrderId: data.last_order_id ? data.last_order_id : null,
|
||||
referralCode: data.referral_code,
|
||||
earnedRewards: data.earned_rewards ?? 0,
|
||||
stealthInvoices: data.wants_stealth,
|
||||
tgEnabled: data.tg_enabled,
|
||||
tgBotName: data.tg_bot_name,
|
||||
tgToken: data.tg_token,
|
||||
bitsEntropy: data.token_bits_entropy,
|
||||
shannonEntropy: data.token_shannon_entropy,
|
||||
pubKey: data.public_key,
|
||||
encPrivKey: data.encrypted_private_key,
|
||||
copiedToken: data.found ? true : robot.copiedToken,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (baseUrl != '' && page != 'robot') {
|
||||
if (open.profile || (robot.token && robot.nickname === null)) {
|
||||
fetchRobot({ keys: false }); // fetch existing robot
|
||||
} else if (robot.token && robot.encPrivKey && robot.pubKey) {
|
||||
fetchRobot({ keys: true }); // create new robot with existing token and keys (on network and coordinator change)
|
||||
}
|
||||
}
|
||||
}, [open.profile, baseUrl]);
|
||||
|
||||
// Fetch current order at load and in a loop
|
||||
useEffect(() => {
|
||||
if (currentOrder != undefined && (page == 'order' || (order == badOrder) == undefined)) {
|
||||
fetchOrder();
|
||||
}
|
||||
}, [currentOrder, page]);
|
||||
|
||||
useEffect(() => {
|
||||
clearInterval(timer);
|
||||
setTimer(setInterval(fetchOrder, delay));
|
||||
return () => clearInterval(timer);
|
||||
}, [delay, currentOrder, page, badOrder]);
|
||||
|
||||
const orderReceived = function (data: any) {
|
||||
if (data.bad_request != undefined) {
|
||||
setBadOrder(data.bad_request);
|
||||
setDelay(99999999);
|
||||
setOrder(undefined);
|
||||
} else {
|
||||
setDelay(
|
||||
data.status >= 0 && data.status <= 18
|
||||
? page == 'order'
|
||||
? statusToDelay[data.status]
|
||||
: statusToDelay[data.status] * 5
|
||||
: 99999999,
|
||||
);
|
||||
setOrder(data);
|
||||
setBadOrder(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchOrder = function () {
|
||||
if (currentOrder != undefined) {
|
||||
apiClient.get(baseUrl, '/api/order/?order_id=' + currentOrder).then(orderReceived);
|
||||
}
|
||||
};
|
||||
|
||||
const clearOrder = function () {
|
||||
setOrder(undefined);
|
||||
setBadOrder(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<Router basename={basename}>
|
||||
{/* load robot avatar image, set avatarLoaded: true */}
|
||||
<RobotAvatar
|
||||
style={{ display: 'none' }}
|
||||
nickname={robot.nickname}
|
||||
@ -353,6 +104,7 @@ const Main = ({ torStatus, settings, setSettings }: MainProps): JSX.Element => {
|
||||
<RobotPage
|
||||
setPage={setPage}
|
||||
torStatus={torStatus}
|
||||
fetchRobot={fetchRobot}
|
||||
setCurrentOrder={setCurrentOrder}
|
||||
windowSize={windowSize}
|
||||
robot={robot}
|
||||
@ -403,21 +155,7 @@ const Main = ({ torStatus, settings, setSettings }: MainProps): JSX.Element => {
|
||||
appear={slideDirection.in != undefined}
|
||||
>
|
||||
<div>
|
||||
<MakerPage
|
||||
book={book}
|
||||
limits={limits}
|
||||
fetchLimits={fetchLimits}
|
||||
maker={maker}
|
||||
setMaker={setMaker}
|
||||
clearOrder={clearOrder}
|
||||
setPage={setPage}
|
||||
setCurrentOrder={setCurrentOrder}
|
||||
fav={fav}
|
||||
setFav={setFav}
|
||||
windowSize={{ ...windowSize, height: windowSize.height - navbarHeight }}
|
||||
hasRobot={robot.avatarLoaded}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
<MakerPage hasRobot={robot.avatarLoaded} />
|
||||
</div>
|
||||
</Slide>
|
||||
</Route>
|
||||
@ -432,17 +170,8 @@ const Main = ({ torStatus, settings, setSettings }: MainProps): JSX.Element => {
|
||||
>
|
||||
<div>
|
||||
<OrderPage
|
||||
baseUrl={baseUrl}
|
||||
order={order}
|
||||
settings={settings}
|
||||
setOrder={setOrder}
|
||||
setCurrentOrder={setCurrentOrder}
|
||||
badOrder={badOrder}
|
||||
locationOrderId={props.match.params.orderId}
|
||||
setBadOrder={setBadOrder}
|
||||
hasRobot={robot.avatarLoaded}
|
||||
windowSize={{ ...windowSize, height: windowSize.height - navbarHeight }}
|
||||
setPage={setPage}
|
||||
/>
|
||||
</div>
|
||||
</Slide>
|
||||
@ -456,34 +185,14 @@ const Main = ({ torStatus, settings, setSettings }: MainProps): JSX.Element => {
|
||||
appear={slideDirection.in != undefined}
|
||||
>
|
||||
<div>
|
||||
<SettingsPage
|
||||
fav={fav}
|
||||
setFav={setFav}
|
||||
settings={settings}
|
||||
setSettings={setSettings}
|
||||
windowSize={{ ...windowSize, height: windowSize.height - navbarHeight }}
|
||||
/>
|
||||
<SettingsPage />
|
||||
</div>
|
||||
</Slide>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Box>
|
||||
<div style={{ alignContent: 'center', display: 'flex' }}>
|
||||
<NavBar
|
||||
nickname={robot.avatarLoaded ? robot.nickname : null}
|
||||
color={settings.network === 'mainnet' ? 'primary' : 'secondary'}
|
||||
width={windowSize.width}
|
||||
height={navbarHeight}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
closeAll={closeAll}
|
||||
setSlideDirection={setSlideDirection}
|
||||
currentOrder={currentOrder}
|
||||
hasRobot={robot.avatarLoaded}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
<NavBar width={windowSize.width} height={navbarHeight} hasRobot={robot.avatarLoaded} />
|
||||
</div>
|
||||
<MainDialogs
|
||||
open={open}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Info, Robot } from '../../models';
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import {
|
||||
CommunityDialog,
|
||||
CoordinatorSummaryDialog,
|
||||
@ -9,7 +8,8 @@ import {
|
||||
StatsDialog,
|
||||
UpdateClientDialog,
|
||||
} from '../../components/Dialogs';
|
||||
import { Page } from '../NavBar';
|
||||
import { pn } from '../../utils';
|
||||
import { AppContext, AppContextProps } from '../../contexts/AppContext';
|
||||
|
||||
export interface OpenDialogs {
|
||||
more: boolean;
|
||||
@ -19,32 +19,31 @@ export interface OpenDialogs {
|
||||
coordinator: boolean;
|
||||
stats: boolean;
|
||||
update: boolean;
|
||||
profile: boolean; // temporary until new Robot Page is ready
|
||||
profile: boolean;
|
||||
}
|
||||
|
||||
interface MainDialogsProps {
|
||||
open: OpenDialogs;
|
||||
setOpen: (state: OpenDialogs) => void;
|
||||
info: Info;
|
||||
robot: Robot;
|
||||
setRobot: (state: Robot) => void;
|
||||
setPage: (state: Page) => void;
|
||||
setCurrentOrder: (state: number) => void;
|
||||
closeAll: OpenDialogs;
|
||||
baseUrl: string;
|
||||
}
|
||||
const MainDialogs = (): JSX.Element => {
|
||||
const {
|
||||
open,
|
||||
setOpen,
|
||||
info,
|
||||
limits,
|
||||
closeAll,
|
||||
robot,
|
||||
setRobot,
|
||||
setPage,
|
||||
setCurrentOrder,
|
||||
baseUrl,
|
||||
} = useContext<AppContextProps>(AppContext);
|
||||
|
||||
const [maxAmount, setMaxAmount] = useState<string>('...loading...');
|
||||
|
||||
useEffect(() => {
|
||||
if (limits.list[1000]) {
|
||||
setMaxAmount(pn(limits.list[1000].max_amount * 100000000));
|
||||
}
|
||||
}, [limits.list]);
|
||||
|
||||
const MainDialogs = ({
|
||||
open,
|
||||
setOpen,
|
||||
info,
|
||||
closeAll,
|
||||
robot,
|
||||
setRobot,
|
||||
setPage,
|
||||
setCurrentOrder,
|
||||
baseUrl,
|
||||
}: MainDialogsProps): JSX.Element => {
|
||||
useEffect(() => {
|
||||
if (info.openUpdateClient) {
|
||||
setOpen({ ...closeAll, update: true });
|
||||
@ -61,7 +60,7 @@ const MainDialogs = ({
|
||||
/>
|
||||
<InfoDialog
|
||||
open={open.info}
|
||||
maxAmount='4,000,000'
|
||||
maxAmount={maxAmount}
|
||||
onClose={() => setOpen({ ...open, info: false })}
|
||||
/>
|
||||
<LearnDialog open={open.learn} onClose={() => setOpen({ ...open, learn: false })} />
|
||||
|
@ -1,52 +1,26 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Grid, Paper, Collapse, Typography } from '@mui/material';
|
||||
|
||||
import { LimitList, Maker, Book, Favorites, Order } from '../../models';
|
||||
|
||||
import { filterOrders } from '../../utils';
|
||||
|
||||
import MakerForm from '../../components/MakerForm';
|
||||
import BookTable from '../../components/BookTable';
|
||||
|
||||
import { Page } from '../NavBar';
|
||||
import { AppContext, AppContextProps } from '../../contexts/AppContext';
|
||||
|
||||
interface MakerPageProps {
|
||||
limits: { list: LimitList; loading: boolean };
|
||||
fetchLimits: () => void;
|
||||
book: Book;
|
||||
fav: Favorites;
|
||||
maker: Maker;
|
||||
setFav: (state: Favorites) => void;
|
||||
setMaker: (state: Maker) => void;
|
||||
clearOrder: () => void;
|
||||
windowSize: { width: number; height: number };
|
||||
hasRobot: boolean;
|
||||
setCurrentOrder: (state: number) => void;
|
||||
setPage: (state: Page) => void;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const MakerPage = ({
|
||||
limits,
|
||||
fetchLimits,
|
||||
book,
|
||||
fav,
|
||||
maker,
|
||||
setFav,
|
||||
setMaker,
|
||||
clearOrder,
|
||||
windowSize,
|
||||
setCurrentOrder,
|
||||
setPage,
|
||||
hasRobot = false,
|
||||
baseUrl,
|
||||
}: MakerPageProps): JSX.Element => {
|
||||
const MakerPage = ({ hasRobot = false }: MakerPageProps): JSX.Element => {
|
||||
const { book, fav, maker, clearOrder, windowSize, setCurrentOrder, navbarHeight, setPage } =
|
||||
useContext<AppContextProps>(AppContext);
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
|
||||
const maxHeight = windowSize.height * 0.85 - 3;
|
||||
const maxHeight = (windowSize.height - navbarHeight) * 0.85 - 3;
|
||||
const [showMatches, setShowMatches] = useState<boolean>(false);
|
||||
|
||||
const matches = filterOrders({
|
||||
@ -71,14 +45,13 @@ const MakerPage = ({
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<BookTable
|
||||
book={{ orders: matches, loading: book.loading }}
|
||||
orderList={matches}
|
||||
maxWidth={Math.min(windowSize.width, 60)} // EM units
|
||||
maxHeight={Math.min(matches.length * 3.25 + 3, 16)} // EM units
|
||||
defaultFullscreen={false}
|
||||
showControls={false}
|
||||
showFooter={false}
|
||||
showNoResults={false}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@ -95,12 +68,6 @@ const MakerPage = ({
|
||||
}}
|
||||
>
|
||||
<MakerForm
|
||||
limits={limits}
|
||||
fetchLimits={fetchLimits}
|
||||
fav={fav}
|
||||
setFav={setFav}
|
||||
maker={maker}
|
||||
setMaker={setMaker}
|
||||
onOrderCreated={(id) => {
|
||||
clearOrder();
|
||||
setCurrentOrder(id);
|
||||
@ -113,8 +80,6 @@ const MakerPage = ({
|
||||
onSubmit={() => setShowMatches(matches.length > 0)}
|
||||
onReset={() => setShowMatches(false)}
|
||||
submitButtonLabel={matches.length > 0 && !showMatches ? 'Submit' : 'Create order'}
|
||||
setPage={setPage}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
@ -21,19 +21,12 @@ const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
|
||||
interface MoreTooltipProps {
|
||||
open: OpenDialogs;
|
||||
nickname: string | null;
|
||||
setOpen: (state: OpenDialogs) => void;
|
||||
closeAll: OpenDialogs;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
const MoreTooltip = ({
|
||||
open,
|
||||
setOpen,
|
||||
closeAll,
|
||||
nickname,
|
||||
children,
|
||||
}: MoreTooltipProps): JSX.Element => {
|
||||
const MoreTooltip = ({ open, setOpen, closeAll, children }: MoreTooltipProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
return (
|
||||
|
@ -1,11 +1,9 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Tabs, Tab, Paper, useTheme } from '@mui/material';
|
||||
import MoreTooltip from './MoreTooltip';
|
||||
|
||||
import { OpenDialogs } from '../MainDialogs';
|
||||
|
||||
import { Page } from '.';
|
||||
|
||||
import {
|
||||
@ -17,47 +15,35 @@ import {
|
||||
MoreHoriz,
|
||||
} from '@mui/icons-material';
|
||||
import RobotAvatar from '../../components/RobotAvatar';
|
||||
|
||||
type Direction = 'left' | 'right' | undefined;
|
||||
import { AppContext, AppContextProps } from '../../contexts/AppContext';
|
||||
|
||||
interface NavBarProps {
|
||||
page: Page;
|
||||
nickname?: string | null;
|
||||
setPage: (state: Page) => void;
|
||||
setSlideDirection: (state: { in: Direction; out: Direction }) => void;
|
||||
width: number;
|
||||
height: number;
|
||||
open: OpenDialogs;
|
||||
setOpen: (state: OpenDialogs) => void;
|
||||
closeAll: OpenDialogs;
|
||||
currentOrder: number | undefined;
|
||||
hasRobot: boolean;
|
||||
baseUrl: string;
|
||||
color: 'primary' | 'secondary';
|
||||
}
|
||||
|
||||
const NavBar = ({
|
||||
page,
|
||||
setPage,
|
||||
setSlideDirection,
|
||||
open,
|
||||
nickname = null,
|
||||
setOpen,
|
||||
closeAll,
|
||||
width,
|
||||
height,
|
||||
currentOrder,
|
||||
hasRobot = false,
|
||||
baseUrl,
|
||||
color,
|
||||
}: NavBarProps): JSX.Element => {
|
||||
const NavBar = ({ width, height, hasRobot = false }: NavBarProps): JSX.Element => {
|
||||
const {
|
||||
page,
|
||||
settings,
|
||||
setPage,
|
||||
setSlideDirection,
|
||||
open,
|
||||
robot,
|
||||
setOpen,
|
||||
closeAll,
|
||||
currentOrder,
|
||||
baseUrl,
|
||||
} = useContext<AppContextProps>(AppContext);
|
||||
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const smallBar = width < 50;
|
||||
|
||||
const tabSx = smallBar
|
||||
? { position: 'relative', bottom: nickname ? '1em' : '0em', minWidth: '1em' }
|
||||
? { position: 'relative', bottom: robot.nickname ? '1em' : '0em', minWidth: '1em' }
|
||||
: { position: 'relative', bottom: '1em', minWidth: '2em' };
|
||||
const pagesPosition = {
|
||||
robot: 1,
|
||||
@ -102,21 +88,21 @@ const NavBar = ({
|
||||
TabIndicatorProps={{ sx: { height: '0.3em', position: 'absolute', top: 0 } }}
|
||||
variant='fullWidth'
|
||||
value={page}
|
||||
indicatorColor={color}
|
||||
textColor={color}
|
||||
indicatorColor={settings.network === 'mainnet' ? 'primary' : 'secondary'}
|
||||
textColor={settings.network === 'mainnet' ? 'primary' : 'secondary'}
|
||||
onChange={changePage}
|
||||
>
|
||||
<Tab
|
||||
sx={{ ...tabSx, minWidth: '2.5em', width: '2.5em', maxWidth: '4em' }}
|
||||
value='none'
|
||||
disabled={nickname === null}
|
||||
disabled={robot.nickname === null}
|
||||
onClick={() => setOpen({ ...closeAll, profile: !open.profile })}
|
||||
icon={
|
||||
nickname ? (
|
||||
robot.nickname && robot.avatarLoaded ? (
|
||||
<RobotAvatar
|
||||
style={{ width: '2.3em', height: '2.3em', position: 'relative', top: '0.2em' }}
|
||||
avatarClass={theme.palette.mode === 'dark' ? 'navBarAvatarDark' : 'navBarAvatar'}
|
||||
nickname={nickname}
|
||||
nickname={robot.nickname}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
) : (
|
||||
@ -171,7 +157,7 @@ const NavBar = ({
|
||||
open.more ? null : setOpen({ ...open, more: true });
|
||||
}}
|
||||
icon={
|
||||
<MoreTooltip open={open} nickname={nickname} setOpen={setOpen} closeAll={closeAll}>
|
||||
<MoreTooltip open={open} setOpen={setOpen} closeAll={closeAll}>
|
||||
<MoreHoriz />
|
||||
</MoreTooltip>
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tab, Tabs, Paper, CircularProgress, Grid, Typography, Box } from '@mui/material';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
@ -9,40 +9,31 @@ 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 {
|
||||
windowSize: { width: number; height: number };
|
||||
order: Order;
|
||||
settings: Settings;
|
||||
setOrder: (state: Order) => void;
|
||||
setCurrentOrder: (state: number) => void;
|
||||
fetchOrder: () => void;
|
||||
badOrder: string | undefined;
|
||||
setBadOrder: (state: string | undefined) => void;
|
||||
hasRobot: boolean;
|
||||
setPage: (state: Page) => void;
|
||||
baseUrl: string;
|
||||
locationOrderId: number;
|
||||
}
|
||||
|
||||
const OrderPage = ({
|
||||
windowSize,
|
||||
order,
|
||||
settings,
|
||||
setOrder,
|
||||
setCurrentOrder,
|
||||
badOrder,
|
||||
setBadOrder,
|
||||
setPage,
|
||||
hasRobot = false,
|
||||
baseUrl,
|
||||
locationOrderId,
|
||||
}: OrderPageProps): JSX.Element => {
|
||||
const OrderPage = ({ hasRobot = false, locationOrderId }: OrderPageProps): JSX.Element => {
|
||||
const {
|
||||
windowSize,
|
||||
order,
|
||||
settings,
|
||||
setOrder,
|
||||
setCurrentOrder,
|
||||
badOrder,
|
||||
setBadOrder,
|
||||
setPage,
|
||||
baseUrl,
|
||||
navbarHeight,
|
||||
} = useContext<AppContextProps>(AppContext);
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
|
||||
const doublePageWidth: number = 50;
|
||||
const maxHeight: number = windowSize.height * 0.85 - 3;
|
||||
const maxHeight: number = (windowSize.height - navbarHeight) * 0.85 - 3;
|
||||
|
||||
const [tab, setTab] = useState<'order' | 'contract'>('contract');
|
||||
|
||||
|
@ -1,12 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Collapse, Grid, Typography, useTheme } from '@mui/material';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { Page } from '../NavBar';
|
||||
import { Button, Grid, Typography, useTheme } from '@mui/material';
|
||||
import { Robot } from '../../models';
|
||||
import { Casino, Download, ContentCopy, SmartToy, Bolt } from '@mui/icons-material';
|
||||
import RobotAvatar from '../../components/RobotAvatar';
|
||||
import TokenInput from './TokenInput';
|
||||
import Key from '@mui/icons-material/Key';
|
||||
|
||||
@ -17,8 +12,6 @@ interface RecoveryProps {
|
||||
inputToken: string;
|
||||
setInputToken: (state: string) => void;
|
||||
getGenerateRobot: (token: string) => void;
|
||||
setPage: (state: Page) => void;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const Recovery = ({
|
||||
@ -28,11 +21,8 @@ const Recovery = ({
|
||||
setView,
|
||||
setInputToken,
|
||||
getGenerateRobot,
|
||||
setPage,
|
||||
baseUrl,
|
||||
}: RecoveryProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const recoveryDisabled = () => {
|
||||
return !(inputToken.length > 20);
|
||||
@ -47,11 +37,14 @@ const Recovery = ({
|
||||
|
||||
return (
|
||||
<Grid container direction='column' alignItems='center' spacing={1} padding={2}>
|
||||
<Grid item>
|
||||
<Typography variant='h5' align='center'>
|
||||
{t('Robot recovery')}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography align='center'>
|
||||
{t(
|
||||
'Please, introduce your robot token to re-build your robot and gain access to its trades.',
|
||||
)}
|
||||
{t('Enter your robot token to re-build your robot and gain access to its trades.')}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
|
@ -7,7 +7,6 @@ import RobotAvatar from '../../components/RobotAvatar';
|
||||
import TokenInput from './TokenInput';
|
||||
import { Page } from '../NavBar';
|
||||
import { Robot } from '../../models';
|
||||
import { genBase62Token } from '../../utils';
|
||||
|
||||
interface RobotProfileProps {
|
||||
robot: Robot;
|
||||
@ -21,7 +20,6 @@ interface RobotProfileProps {
|
||||
setPage: (state: Page) => void;
|
||||
baseUrl: string;
|
||||
badRequest: string;
|
||||
robotFound: boolean;
|
||||
width: number;
|
||||
}
|
||||
|
||||
@ -37,7 +35,6 @@ const RobotProfile = ({
|
||||
setView,
|
||||
badRequest,
|
||||
baseUrl,
|
||||
robotFound,
|
||||
width,
|
||||
}: RobotProfileProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
@ -104,15 +101,13 @@ const RobotProfile = ({
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* {robotFound ? (
|
||||
<Grid item>
|
||||
<Typography variant='h6'>
|
||||
{t('Welcome back!')}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{robot.found ? (
|
||||
<Typography align='center' variant='h6'>
|
||||
{t('Welcome back!')}
|
||||
</Typography>
|
||||
) : (
|
||||
<></>
|
||||
)} */}
|
||||
)}
|
||||
|
||||
{robot.activeOrderId ? (
|
||||
<Grid item>
|
||||
@ -184,7 +179,6 @@ const RobotProfile = ({
|
||||
|
||||
<Grid item>
|
||||
<Button
|
||||
disabled={!(robot.avatarLoaded && robot.nickname)}
|
||||
size='small'
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
|
@ -59,7 +59,7 @@ const TokenInput = ({
|
||||
required={true}
|
||||
label={label || undefined}
|
||||
value={inputToken}
|
||||
autoFocus={autoFocusTarget == 'texfield'}
|
||||
autoFocus={autoFocusTarget == 'textfield'}
|
||||
fullWidth={fullWidth}
|
||||
sx={{ borderColor: 'primary' }}
|
||||
variant={editable ? 'outlined' : 'filled'}
|
||||
|
@ -24,7 +24,7 @@ const Welcome = ({ setView, width, getGenerateRobot }: WelcomeProps): JSX.Elemen
|
||||
paddingTop={2.2}
|
||||
padding={0.5}
|
||||
>
|
||||
<Grid item>
|
||||
<Grid item style={{ paddingTop: '2em', paddingBottom: '1.5em' }}>
|
||||
<svg width={0} height={0}>
|
||||
<linearGradient id='linearColors' x1={1} y1={0} x2={1} y2={1}>
|
||||
<stop offset={0} stopColor={theme.palette.primary.main} />
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Paper,
|
||||
@ -14,36 +14,19 @@ import { useParams } from 'react-router-dom';
|
||||
|
||||
import { Page } from '../NavBar';
|
||||
import { Robot } from '../../models';
|
||||
import { tokenStrength } from '../../utils';
|
||||
import { systemClient } from '../../services/System';
|
||||
import { apiClient } from '../../services/api';
|
||||
import { genKey } from '../../pgp';
|
||||
import { sha256 } from 'js-sha256';
|
||||
import Onboarding from './Onboarding';
|
||||
import Welcome from './Welcome';
|
||||
import RobotProfile from './RobotProfile';
|
||||
import Recovery from './Recovery';
|
||||
import { TorIcon } from '../../components/Icons';
|
||||
import { genKey } from '../../pgp';
|
||||
import { AppContext, AppContextProps } from '../../contexts/AppContext';
|
||||
|
||||
interface RobotPageProps {
|
||||
setPage: (state: Page) => void;
|
||||
setCurrentOrder: (state: number) => void;
|
||||
torStatus: 'NOTINIT' | 'STARTING' | '"Done"' | 'DONE';
|
||||
robot: Robot;
|
||||
setRobot: (state: Robot) => void;
|
||||
windowSize: { width: number; height: number };
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const RobotPage = ({
|
||||
setPage,
|
||||
setCurrentOrder,
|
||||
torStatus,
|
||||
windowSize,
|
||||
robot,
|
||||
setRobot,
|
||||
baseUrl,
|
||||
}: RobotPageProps): JSX.Element => {
|
||||
const RobotPage = (): JSX.Element => {
|
||||
const { setPage, setCurrentOrder, fetchRobot, torStatus, windowSize, robot, setRobot, baseUrl } =
|
||||
useContext<AppContextProps>(AppContext);
|
||||
const { t } = useTranslation();
|
||||
const params = useParams();
|
||||
const refCode = params.refCode;
|
||||
@ -51,7 +34,6 @@ const RobotPage = ({
|
||||
const maxHeight = windowSize.height * 0.85 - 3;
|
||||
const theme = useTheme();
|
||||
|
||||
const [robotFound, setRobotFound] = useState<boolean>(false);
|
||||
const [badRequest, setBadRequest] = useState<string | undefined>(undefined);
|
||||
const [inputToken, setInputToken] = useState<string>('');
|
||||
const [view, setView] = useState<'welcome' | 'onboarding' | 'recovery' | 'profile'>(
|
||||
@ -63,82 +45,24 @@ const RobotPage = ({
|
||||
setInputToken(robot.token);
|
||||
}
|
||||
if (robot.nickname == null && robot.token) {
|
||||
getGenerateRobot(robot.token);
|
||||
fetchRobot({ action: 'login' });
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getGenerateRobot = (token: string) => {
|
||||
const strength = tokenStrength(token);
|
||||
setRobot({ ...robot, loading: true, avatarLoaded: false });
|
||||
setInputToken(token);
|
||||
|
||||
const requestBody = genKey(token).then(function (key) {
|
||||
return {
|
||||
token_sha256: sha256(token),
|
||||
public_key: key.publicKeyArmored,
|
||||
encrypted_private_key: key.encryptedPrivateKeyArmored,
|
||||
unique_values: strength.uniqueValues,
|
||||
counts: strength.counts,
|
||||
length: token.length,
|
||||
ref_code: refCode,
|
||||
};
|
||||
genKey(token).then(function (key) {
|
||||
fetchRobot({
|
||||
action: 'generate',
|
||||
newKeys: {
|
||||
pubKey: key.publicKeyArmored,
|
||||
encPrivKey: key.encryptedPrivateKeyArmored,
|
||||
},
|
||||
newToken: token,
|
||||
refCode,
|
||||
setBadRequest,
|
||||
});
|
||||
});
|
||||
|
||||
requestBody.then(
|
||||
async (body) =>
|
||||
await apiClient.post(baseUrl, '/api/user/', body).then((data: any) => {
|
||||
setRobotFound(data?.found);
|
||||
setBadRequest(data?.bad_request);
|
||||
setCurrentOrder(
|
||||
data.active_order_id
|
||||
? data.active_order_id
|
||||
: data.last_order_id
|
||||
? data.last_order_id
|
||||
: null,
|
||||
);
|
||||
// Add nick and token to App state (token only if not a bad request)
|
||||
data.bad_request
|
||||
? setRobot({
|
||||
...robot,
|
||||
avatarLoaded: true,
|
||||
loading: false,
|
||||
nickname: data.nickname ?? robot.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,
|
||||
stealthInvoices: data.wants_stealth ?? robot.stealthInvoices,
|
||||
tgEnabled: data.tg_enabled,
|
||||
tgBotName: data.tg_bot_name,
|
||||
tgToken: data.tg_token,
|
||||
})
|
||||
: setRobot({
|
||||
...robot,
|
||||
nickname: data.nickname,
|
||||
token,
|
||||
loading: false,
|
||||
activeOrderId: data.active_order_id ?? null,
|
||||
lastOrderId: data.last_order_id ?? null,
|
||||
referralCode: data.referral_code,
|
||||
earnedRewards: data.earned_rewards ?? 0,
|
||||
stealthInvoices: data.wants_stealth,
|
||||
tgEnabled: data.tg_enabled,
|
||||
tgBotName: data.tg_bot_name,
|
||||
tgToken: data.tg_token,
|
||||
bitsEntropy: data.token_bits_entropy,
|
||||
shannonEntropy: data.token_shannon_entropy,
|
||||
pubKey: data.public_key,
|
||||
encPrivKey: data.encrypted_private_key,
|
||||
copiedToken: data.found ? true : robot.copiedToken,
|
||||
}) &
|
||||
systemClient.setItem('robot_token', token) &
|
||||
systemClient.setItem('pub_key', data.public_key.split('\n').join('\\')) &
|
||||
systemClient.setItem(
|
||||
'enc_priv_key',
|
||||
data.encrypted_private_key.split('\n').join('\\'),
|
||||
);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const deleteRobot = () => {
|
||||
@ -148,7 +72,6 @@ const RobotPage = ({
|
||||
|
||||
const logoutRobot = () => {
|
||||
setInputToken('');
|
||||
setRobotFound(false);
|
||||
systemClient.deleteCookie('sessionid');
|
||||
systemClient.deleteItem('robot_token');
|
||||
systemClient.deleteItem('pub_key');
|
||||
@ -237,7 +160,6 @@ const RobotPage = ({
|
||||
<RobotProfile
|
||||
setView={setView}
|
||||
robot={robot}
|
||||
robotFound={robotFound}
|
||||
setRobot={setRobot}
|
||||
setCurrentOrder={setCurrentOrder}
|
||||
badRequest={badRequest}
|
||||
|
@ -1,27 +1,14 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Grid, Paper, useTheme } from '@mui/material';
|
||||
import SettingsForm from '../../components/SettingsForm';
|
||||
import { Settings, Favorites } from '../../models';
|
||||
import { AppContextProps, AppContext } from '../../contexts/AppContext';
|
||||
|
||||
interface SettingsPageProps {
|
||||
fav: Favorites;
|
||||
setFav: (state: Favorites) => void;
|
||||
settings: Settings;
|
||||
setSettings: (state: Settings) => void;
|
||||
windowSize: { width: number; height: number };
|
||||
}
|
||||
|
||||
const SettingsPage = ({
|
||||
fav,
|
||||
setFav,
|
||||
settings,
|
||||
setSettings,
|
||||
windowSize,
|
||||
}: SettingsPageProps): JSX.Element => {
|
||||
const SettingsPage = (): JSX.Element => {
|
||||
const { windowSize, navbarHeight } = useContext<AppContextProps>(AppContext);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const maxHeight = windowSize.height * 0.85 - 3;
|
||||
const maxHeight = (windowSize.height - navbarHeight) * 0.85 - 3;
|
||||
|
||||
return (
|
||||
<Paper
|
||||
@ -36,13 +23,7 @@ const SettingsPage = ({
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item>
|
||||
<SettingsForm
|
||||
fav={fav}
|
||||
setFav={setFav}
|
||||
settings={settings}
|
||||
setSettings={setSettings}
|
||||
showNetwork={!(window.NativeRobosats === undefined)}
|
||||
/>
|
||||
<SettingsForm showNetwork={!(window.NativeRobosats === undefined)} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Box,
|
||||
@ -17,7 +17,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { DataGrid, GridPagination } from '@mui/x-data-grid';
|
||||
import currencyDict from '../../../static/assets/currencies.json';
|
||||
import { Book, Favorites } from '../../models';
|
||||
import { PublicOrder } from '../../models';
|
||||
import { filterOrders, hexToRgb, statusBadgeColor, pn, amountToString } from '../../utils';
|
||||
import BookControl from './BookControl';
|
||||
|
||||
@ -27,11 +27,10 @@ import RobotAvatar from '../RobotAvatar';
|
||||
|
||||
// Icons
|
||||
import { Fullscreen, FullscreenExit, Refresh } from '@mui/icons-material';
|
||||
import { AppContext, AppContextProps } from '../../contexts/AppContext';
|
||||
|
||||
interface BookTableProps {
|
||||
clickRefresh?: () => void;
|
||||
book: Book;
|
||||
fav?: Favorites;
|
||||
orderList?: PublicOrder[];
|
||||
maxWidth: number;
|
||||
maxHeight: number;
|
||||
fullWidth?: number;
|
||||
@ -42,16 +41,11 @@ interface BookTableProps {
|
||||
showControls?: boolean;
|
||||
showFooter?: boolean;
|
||||
showNoResults?: boolean;
|
||||
setFav?: (state: Favorites) => void;
|
||||
onOrderClicked?: (id: number) => void;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const BookTable = ({
|
||||
clickRefresh,
|
||||
book,
|
||||
fav = { currency: 1, type: 0, mode: 'fiat' },
|
||||
setFav,
|
||||
orderList,
|
||||
maxWidth = 100,
|
||||
maxHeight = 70,
|
||||
fullWidth = 100,
|
||||
@ -63,10 +57,12 @@ const BookTable = ({
|
||||
showFooter = true,
|
||||
showNoResults = true,
|
||||
onOrderClicked = () => null,
|
||||
baseUrl,
|
||||
}: BookTableProps): JSX.Element => {
|
||||
const { book, fetchBook, fav, setFav, baseUrl } = useContext<AppContextProps>(AppContext);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const orders = orderList ?? book.orders;
|
||||
const [pageSize, setPageSize] = useState(0);
|
||||
const [fullscreen, setFullscreen] = useState(defaultFullscreen);
|
||||
const [paymentMethods, setPaymentMethods] = useState<string[]>([]);
|
||||
@ -641,7 +637,7 @@ const BookTable = ({
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<IconButton onClick={clickRefresh}>
|
||||
<IconButton onClick={() => fetchBook()}>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
@ -729,11 +725,11 @@ const BookTable = ({
|
||||
rows={
|
||||
showControls
|
||||
? filterOrders({
|
||||
orders: book.orders,
|
||||
orders,
|
||||
baseFilter: fav,
|
||||
paymentMethods,
|
||||
})
|
||||
: book.orders
|
||||
: orders
|
||||
}
|
||||
loading={book.loading}
|
||||
columns={columns}
|
||||
@ -748,7 +744,7 @@ const BookTable = ({
|
||||
setPaymentMethods,
|
||||
},
|
||||
}}
|
||||
pageSize={book.loading && book.orders.length == 0 ? 0 : pageSize}
|
||||
pageSize={book.loading && orders.length == 0 ? 0 : pageSize}
|
||||
rowsPerPageOptions={width < 22 ? [] : [0, pageSize, defaultPageSize * 2, 50, 100]}
|
||||
onPageSizeChange={(newPageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
@ -769,11 +765,11 @@ const BookTable = ({
|
||||
rows={
|
||||
showControls
|
||||
? filterOrders({
|
||||
orders: book.orders,
|
||||
orders,
|
||||
baseFilter: fav,
|
||||
paymentMethods,
|
||||
})
|
||||
: book.orders
|
||||
: orders
|
||||
}
|
||||
loading={book.loading}
|
||||
columns={columns}
|
||||
@ -788,7 +784,7 @@ const BookTable = ({
|
||||
setPaymentMethods,
|
||||
},
|
||||
}}
|
||||
pageSize={book.loading && book.orders.length == 0 ? 0 : pageSize}
|
||||
pageSize={book.loading && orders.length == 0 ? 0 : pageSize}
|
||||
rowsPerPageOptions={[0, pageSize, defaultPageSize * 2, 50, 100]}
|
||||
onPageSizeChange={(newPageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import {
|
||||
ResponsiveLine,
|
||||
Serie,
|
||||
@ -26,32 +26,24 @@ import { amountToString, matchMedian, statusBadgeColor } from '../../../utils';
|
||||
import currencyDict from '../../../../static/assets/currencies.json';
|
||||
import { PaymentStringAsIcons } from '../../PaymentMethods';
|
||||
import getNivoScheme from '../NivoScheme';
|
||||
import { AppContextProps, AppContext } from '../../../contexts/AppContext';
|
||||
|
||||
interface DepthChartProps {
|
||||
orders: PublicOrder[];
|
||||
lastDayPremium?: number | undefined;
|
||||
currency: number;
|
||||
limits: LimitList;
|
||||
maxWidth: number;
|
||||
maxHeight: number;
|
||||
fillContainer?: boolean;
|
||||
elevation?: number;
|
||||
onOrderClicked?: (id: number) => void;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const DepthChart: React.FC<DepthChartProps> = ({
|
||||
orders,
|
||||
lastDayPremium,
|
||||
currency,
|
||||
limits,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
fillContainer = false,
|
||||
elevation = 6,
|
||||
onOrderClicked = () => null,
|
||||
baseUrl,
|
||||
}) => {
|
||||
const { book, fav, info, limits, baseUrl } = useContext<AppContextProps>(AppContext);
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [enrichedOrders, setEnrichedOrders] = useState<Order[]>([]);
|
||||
@ -66,22 +58,22 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
||||
const width = maxWidth < 20 ? 20 : maxWidth > 72.8 ? 72.8 : maxWidth;
|
||||
|
||||
useEffect(() => {
|
||||
setCurrencyCode(currency === 0 ? 1 : currency);
|
||||
}, [currency]);
|
||||
setCurrencyCode(fav.currency === 0 ? 1 : fav.currency);
|
||||
}, [fav.currency]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(limits).length > 0) {
|
||||
const enriched = orders.map((order) => {
|
||||
if (Object.keys(limits.list).length > 0) {
|
||||
const enriched = book.orders.map((order) => {
|
||||
// We need to transform all currencies to the same base (ex. USD), we don't have the exchange rate
|
||||
// for EUR -> USD, but we know the rate of both to BTC, so we get advantage of it and apply a
|
||||
// simple rule of three
|
||||
order.base_amount =
|
||||
(order.price * limits[currencyCode].price) / limits[order.currency].price;
|
||||
(order.price * limits.list[currencyCode].price) / limits.list[order.currency].price;
|
||||
return order;
|
||||
});
|
||||
setEnrichedOrders(enriched);
|
||||
}
|
||||
}, [limits, orders, currencyCode]);
|
||||
}, [limits.list, book.orders, currencyCode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (enrichedOrders.length > 0) {
|
||||
@ -102,16 +94,16 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
||||
setXRange(maxRange);
|
||||
setRangeSteps(rangeSteps);
|
||||
} else {
|
||||
if (lastDayPremium === undefined) {
|
||||
if (info.last_day_nonkyc_btc_premium === undefined) {
|
||||
const premiums: number[] = enrichedOrders.map((order) => order?.premium || 0);
|
||||
setCenter(~~matchMedian(premiums));
|
||||
} else {
|
||||
setCenter(lastDayPremium);
|
||||
setCenter(info.last_day_nonkyc_btc_premium);
|
||||
}
|
||||
setXRange(8);
|
||||
setRangeSteps(0.5);
|
||||
}
|
||||
}, [enrichedOrders, xType, lastDayPremium, currencyCode]);
|
||||
}, [enrichedOrders, xType, info.last_day_nonkyc_btc_premium, currencyCode]);
|
||||
|
||||
const generateSeries: () => void = () => {
|
||||
const sortedOrders: PublicOrder[] =
|
||||
|
@ -117,7 +117,7 @@ const CoordinatorSummaryDialog = ({ open = false, onClose, info }: Props): JSX.E
|
||||
primaryTypographyProps={{ fontSize: '14px' }}
|
||||
secondaryTypographyProps={{ fontSize: '12px' }}
|
||||
primary={`${info.last_day_nonkyc_btc_premium}%`}
|
||||
secondary={t('24h non-KYC bitcoin premium')}
|
||||
secondary={t('Last 24h mean premium')}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
InputAdornment,
|
||||
@ -29,7 +29,6 @@ import { LimitList, Maker, Favorites, defaultMaker } from '../../models';
|
||||
|
||||
import { LocalizationProvider, TimePicker } from '@mui/x-date-pickers';
|
||||
import DateFnsUtils from '@date-io/date-fns';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { ConfirmationDialog } from '../Dialogs';
|
||||
import { apiClient } from '../../services/api';
|
||||
|
||||
@ -41,35 +40,21 @@ import { amountToString, pn } from '../../utils';
|
||||
|
||||
import { SelfImprovement, Lock, HourglassTop, DeleteSweep, Edit } from '@mui/icons-material';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { Page } from '../../basic/NavBar';
|
||||
import { AppContext, AppContextProps } from '../../contexts/AppContext';
|
||||
|
||||
interface MakerFormProps {
|
||||
limits: { list: LimitList; loading: boolean };
|
||||
fetchLimits: () => void;
|
||||
pricingMethods?: boolean;
|
||||
maker: Maker;
|
||||
fav: Favorites;
|
||||
setFav: (state: Favorites) => void;
|
||||
setMaker: (state: Maker) => void;
|
||||
disableRequest?: boolean;
|
||||
pricingMethods?: boolean;
|
||||
collapseAll?: boolean;
|
||||
onSubmit?: () => void;
|
||||
onReset?: () => void;
|
||||
submitButtonLabel?: string;
|
||||
onOrderCreated?: (id: number) => void;
|
||||
hasRobot?: boolean;
|
||||
setPage?: (state: Page) => void;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const MakerForm = ({
|
||||
limits,
|
||||
fetchLimits,
|
||||
pricingMethods = false,
|
||||
fav,
|
||||
setFav,
|
||||
maker,
|
||||
setMaker,
|
||||
disableRequest = false,
|
||||
collapseAll = false,
|
||||
onSubmit = () => {},
|
||||
@ -77,12 +62,12 @@ const MakerForm = ({
|
||||
submitButtonLabel = 'Create Order',
|
||||
onOrderCreated = () => null,
|
||||
hasRobot = true,
|
||||
setPage = () => null,
|
||||
baseUrl,
|
||||
}: MakerFormProps): JSX.Element => {
|
||||
const { fav, setFav, limits, fetchLimits, maker, setMaker, setPage, baseUrl } =
|
||||
useContext<AppContextProps>(AppContext);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const history = useHistory();
|
||||
const [badRequest, setBadRequest] = useState<string | null>(null);
|
||||
const [amountLimits, setAmountLimits] = useState<number[]>([1, 1000]);
|
||||
const [satoshisLimits, setSatoshisLimits] = useState<number[]>([20000, 4000000]);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AppContextProps, AppContext } from '../../contexts/AppContext';
|
||||
import {
|
||||
Grid,
|
||||
Paper,
|
||||
@ -14,7 +15,6 @@ import {
|
||||
ToggleButtonGroup,
|
||||
ToggleButton,
|
||||
} from '@mui/material';
|
||||
import { Favorites, Settings } from '../../models';
|
||||
import SelectLanguage from './SelectLanguage';
|
||||
import {
|
||||
Translate,
|
||||
@ -31,21 +31,11 @@ import SwapCalls from '@mui/icons-material/SwapCalls';
|
||||
|
||||
interface SettingsFormProps {
|
||||
dense?: boolean;
|
||||
fav: Favorites;
|
||||
setFav: (state: Favorites) => void;
|
||||
settings: Settings;
|
||||
setSettings: (state: Settings) => void;
|
||||
showNetwork?: boolean;
|
||||
}
|
||||
|
||||
const SettingsForm = ({
|
||||
dense = false,
|
||||
fav,
|
||||
setFav,
|
||||
settings,
|
||||
setSettings,
|
||||
showNetwork = false,
|
||||
}: SettingsFormProps): JSX.Element => {
|
||||
const SettingsForm = ({ dense = false, showNetwork = false }: SettingsFormProps): JSX.Element => {
|
||||
const { fav, setFav, settings, setSettings } = useContext<AppContextProps>(AppContext);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const fontSizes = [
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, CircularProgress, Tooltip } from '@mui/material';
|
||||
import { TorIcon } from './Icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AppContext, AppContextProps } from '../contexts/AppContext';
|
||||
|
||||
interface TorIndicatorProps {
|
||||
color: 'inherit' | 'error' | 'warning' | 'success' | 'primary' | 'secondary' | 'info' | undefined;
|
||||
@ -53,11 +54,8 @@ const TorIndicator = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface TorConnectionBadgeProps {
|
||||
torStatus: 'NOTINIT' | 'STARTING' | '"Done"' | 'DONE';
|
||||
}
|
||||
|
||||
const TorConnectionBadge = ({ torStatus }: TorConnectionBadgeProps): JSX.Element => {
|
||||
const TorConnectionBadge = (): JSX.Element => {
|
||||
const { torStatus } = useContext<AppContextProps>(AppContext);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (window?.NativeRobosats == null) {
|
||||
|
501
frontend/src/contexts/AppContext.tsx
Normal file
501
frontend/src/contexts/AppContext.tsx
Normal file
@ -0,0 +1,501 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Page } from '../basic/NavBar';
|
||||
import { OpenDialogs } from '../basic/MainDialogs';
|
||||
|
||||
import {
|
||||
Book,
|
||||
LimitList,
|
||||
Maker,
|
||||
Robot,
|
||||
Info,
|
||||
Settings,
|
||||
Favorites,
|
||||
defaultMaker,
|
||||
defaultInfo,
|
||||
Coordinator,
|
||||
Order,
|
||||
} from '../models';
|
||||
|
||||
import { apiClient } from '../services/api';
|
||||
import { checkVer, getHost, tokenStrength } from '../utils';
|
||||
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
|
||||
return {
|
||||
width: window.innerWidth / fontSize,
|
||||
height: window.innerHeight / fontSize,
|
||||
};
|
||||
};
|
||||
|
||||
// Refresh delays (ms) according to Order status
|
||||
const statusToDelay = [
|
||||
3000, // 'Waiting for maker bond'
|
||||
35000, // 'Public'
|
||||
180000, // 'Paused'
|
||||
3000, // 'Waiting for taker bond'
|
||||
999999, // 'Cancelled'
|
||||
999999, // 'Expired'
|
||||
8000, // 'Waiting for trade collateral and buyer invoice'
|
||||
8000, // 'Waiting only for seller trade collateral'
|
||||
8000, // 'Waiting only for buyer invoice'
|
||||
10000, // 'Sending fiat - In chatroom'
|
||||
10000, // 'Fiat sent - In chatroom'
|
||||
100000, // 'In dispute'
|
||||
999999, // 'Collaboratively cancelled'
|
||||
10000, // 'Sending satoshis to buyer'
|
||||
999999, // 'Sucessful trade'
|
||||
30000, // 'Failed lightning network routing'
|
||||
300000, // 'Wait for dispute resolution'
|
||||
300000, // 'Maker lost dispute'
|
||||
300000, // 'Taker lost dispute'
|
||||
];
|
||||
|
||||
export interface SlideDirection {
|
||||
in: 'left' | 'right' | undefined;
|
||||
out: 'left' | 'right' | undefined;
|
||||
}
|
||||
|
||||
export interface fetchRobotProps {
|
||||
action?: 'login' | 'generate';
|
||||
newKeys?: { encPrivKey: string; pubKey: string } | null;
|
||||
newToken?: string | null;
|
||||
refCode?: string | null;
|
||||
setBadRequest?: (state: string) => void;
|
||||
}
|
||||
|
||||
export type TorStatus = 'NOTINIT' | 'STARTING' | '"Done"' | 'DONE';
|
||||
|
||||
export interface AppContextProps {
|
||||
torStatus: TorStatus;
|
||||
federation: Coordinator[];
|
||||
setFederation: (state: Coordinator[]) => void;
|
||||
settings: Settings;
|
||||
setSettings: (state: Settings) => void;
|
||||
book: Book;
|
||||
info: Info;
|
||||
setBook: (state: Book) => void;
|
||||
fetchBook: () => void;
|
||||
limits: { list: LimitList; loading: boolean };
|
||||
setLimits: (state: { list: LimitList; loading: boolean }) => void;
|
||||
fetchLimits: () => void;
|
||||
maker: Maker;
|
||||
setMaker: (state: Maker) => void;
|
||||
clearOrder: () => void;
|
||||
robot: Robot;
|
||||
setRobot: (state: Robot) => void;
|
||||
focusedCoordinator: number;
|
||||
setFocusedCoordinator: (state: number) => void;
|
||||
baseUrl: string;
|
||||
setBaseUrl: (state: string) => void;
|
||||
fav: Favorites;
|
||||
setFav: (state: Favorites) => void;
|
||||
order: Order | undefined;
|
||||
setOrder: (state: Order | undefined) => void;
|
||||
badOrder: string;
|
||||
setBadOrder: (state: string | undefined) => void;
|
||||
setDelay: (state: number) => void;
|
||||
page: Page;
|
||||
setPage: (state: Page) => void;
|
||||
slideDirection: SlideDirection;
|
||||
setSlideDirection: (state: SlideDirection) => void;
|
||||
currentOrder: number | undefined;
|
||||
setCurrentOrder: (state: number) => void;
|
||||
navbarHeight: number;
|
||||
closeAll: OpenDialogs;
|
||||
open: OpenDialogs;
|
||||
setOpen: (state: OpenDialogs) => void;
|
||||
windowSize: { width: number; height: number };
|
||||
clientVersion: {
|
||||
semver: Version;
|
||||
short: string;
|
||||
long: string;
|
||||
};
|
||||
}
|
||||
|
||||
const entryPage: Page | '' =
|
||||
window.NativeRobosats === undefined ? window.location.pathname.split('/')[1] : '';
|
||||
|
||||
const closeAll = {
|
||||
more: false,
|
||||
learn: false,
|
||||
community: false,
|
||||
info: false,
|
||||
coordinator: false,
|
||||
exchange: false,
|
||||
client: false,
|
||||
update: false,
|
||||
profile: false,
|
||||
};
|
||||
|
||||
// export const initialState = {
|
||||
// federation: defaultFederation,
|
||||
// setFederation: () => null,
|
||||
// settings: new Settings(),
|
||||
// setSettings: () => null,
|
||||
// book: { orders: [], loading: true },
|
||||
// setBook: () => null,
|
||||
// fetchBook: () => null,
|
||||
// limits: {
|
||||
// list: [],
|
||||
// loading: true,
|
||||
// },
|
||||
// setLimits:() => null,
|
||||
// fetchLimits: ()=> null,
|
||||
// maker: defaultMaker,
|
||||
// setMaker: () => null,
|
||||
// clearOrder: () => null,
|
||||
// robot: new Robot(),
|
||||
// setRobot: () => null,
|
||||
// info: defaultExchange,
|
||||
// setExchange: () => null,
|
||||
// focusedCoordinator: 0,
|
||||
// setFocusedCoordinator: () => null,
|
||||
// baseUrl: '',
|
||||
// setBaseUrl: () => null,
|
||||
// fav: { type: null, currency: 0 },
|
||||
// setFav: () => null,
|
||||
// order: undefined,
|
||||
// setOrder: () => null,
|
||||
// badOrder: '',
|
||||
// setBadOrder: () => null,
|
||||
// setDelay: () => null,
|
||||
// page: entryPage == '' ? 'robot' : entryPage,
|
||||
// setPage: () => null,
|
||||
// slideDirection: {
|
||||
// in: undefined,
|
||||
// out: undefined,
|
||||
// },
|
||||
// setSlideDirection: () => null,
|
||||
// currentOrder: undefined,
|
||||
// setCurrentOrder: () => null,
|
||||
// navbarHeight: 2.5,
|
||||
// closeAll,
|
||||
// open: closeAll,
|
||||
// setOpen: () => null,
|
||||
// windowSize: getWindowSize(14),
|
||||
// }
|
||||
|
||||
export interface AppContextProviderProps {
|
||||
children: React.ReactNode;
|
||||
settings: Settings;
|
||||
setSettings: (state: Settings) => void;
|
||||
}
|
||||
|
||||
export const AppContextProvider = ({
|
||||
children,
|
||||
settings,
|
||||
setSettings,
|
||||
}: AppContextProviderProps): JSX.Element => {
|
||||
const theme = useTheme();
|
||||
|
||||
// All app data structured
|
||||
const [torStatus, setTorStatus] = useState<TorStatus>('NOTINIT');
|
||||
const [book, setBook] = useState<Book>({ orders: [], loading: true });
|
||||
const [limits, setLimits] = useState<{ list: LimitList; loading: boolean }>({
|
||||
list: [],
|
||||
loading: true,
|
||||
});
|
||||
const [robot, setRobot] = useState<Robot>(new Robot());
|
||||
const [maker, setMaker] = useState<Maker>(defaultMaker);
|
||||
const [info, setInfo] = useState<Info>(defaultInfo);
|
||||
const [coordinators, setCoordinators] = useState<Coordinator[]>(defaultCoordinators);
|
||||
const [baseUrl, setBaseUrl] = useState<string>('');
|
||||
const [fav, setFav] = useState<Favorites>({ type: null, mode: 'fiat', currency: 0 });
|
||||
|
||||
const [delay, setDelay] = useState<number>(60000);
|
||||
const [timer, setTimer] = useState<NodeJS.Timer | undefined>(setInterval(() => null, delay));
|
||||
const [order, setOrder] = useState<Order | undefined>(undefined);
|
||||
const [badOrder, setBadOrder] = useState<string | undefined>(undefined);
|
||||
|
||||
const entryPage: Page | '' =
|
||||
window.NativeRobosats === undefined ? window.location.pathname.split('/')[1] : '';
|
||||
const [page, setPage] = useState<Page>(entryPage == '' ? 'robot' : entryPage);
|
||||
const [slideDirection, setSlideDirection] = useState<SlideDirection>({
|
||||
in: undefined,
|
||||
out: undefined,
|
||||
});
|
||||
|
||||
const [currentOrder, setCurrentOrder] = useState<number | undefined>(undefined);
|
||||
|
||||
const navbarHeight = 2.5;
|
||||
const closeAll = {
|
||||
more: false,
|
||||
learn: false,
|
||||
community: false,
|
||||
info: false,
|
||||
coordinator: false,
|
||||
stats: false,
|
||||
update: false,
|
||||
profile: false,
|
||||
};
|
||||
const [open, setOpen] = useState<OpenDialogs>(closeAll);
|
||||
|
||||
const [windowSize, setWindowSize] = useState<{ width: number; height: number }>(
|
||||
getWindowSize(theme.typography.fontSize),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== undefined) {
|
||||
window.addEventListener('resize', onResize);
|
||||
}
|
||||
|
||||
if (baseUrl != '') {
|
||||
fetchBook();
|
||||
fetchLimits();
|
||||
}
|
||||
return () => {
|
||||
if (typeof window !== undefined) {
|
||||
window.removeEventListener('resize', onResize);
|
||||
}
|
||||
};
|
||||
}, [baseUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
let host = '';
|
||||
if (window.NativeRobosats === undefined) {
|
||||
host = getHost();
|
||||
} else {
|
||||
host =
|
||||
settings.network === 'mainnet'
|
||||
? coordinators[0].mainnetOnion
|
||||
: coordinators[0].testnetOnion;
|
||||
}
|
||||
setBaseUrl(`${location.protocol}//${host}`);
|
||||
}, [settings.network]);
|
||||
|
||||
useEffect(() => {
|
||||
setWindowSize(getWindowSize(theme.typography.fontSize));
|
||||
}, [theme.typography.fontSize]);
|
||||
|
||||
const onResize = function () {
|
||||
setWindowSize(getWindowSize(theme.typography.fontSize));
|
||||
};
|
||||
|
||||
const fetchBook = function () {
|
||||
setBook({ ...book, loading: true });
|
||||
apiClient.get(baseUrl, '/api/book/').then((data: any) =>
|
||||
setBook({
|
||||
loading: false,
|
||||
orders: data.not_found ? [] : data,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const fetchLimits = async () => {
|
||||
setLimits({ ...limits, loading: true });
|
||||
const data = apiClient.get(baseUrl, '/api/limits/').then((data) => {
|
||||
setLimits({ list: data ?? [], loading: false });
|
||||
return data;
|
||||
});
|
||||
return await data;
|
||||
};
|
||||
|
||||
const fetchInfo = function () {
|
||||
setInfo({ ...info, loading: true });
|
||||
apiClient.get(baseUrl, '/api/info/').then((data: Info) => {
|
||||
const versionInfo: any = checkVer(data.version.major, data.version.minor, data.version.patch);
|
||||
setInfo({
|
||||
...data,
|
||||
openUpdateClient: versionInfo.updateAvailable,
|
||||
coordinatorVersion: versionInfo.coordinatorVersion,
|
||||
clientVersion: versionInfo.clientVersion,
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open.stats || open.coordinator || info.coordinatorVersion == 'v?.?.?') {
|
||||
fetchInfo();
|
||||
}
|
||||
}, [open.stats, open.coordinator]);
|
||||
|
||||
useEffect(() => {
|
||||
// Sets Setting network from coordinator API param if accessing via web
|
||||
if (settings.network == undefined && info.network) {
|
||||
setSettings((settings: Settings) => {
|
||||
return { ...settings, network: info.network };
|
||||
});
|
||||
}
|
||||
}, [info]);
|
||||
|
||||
// Fetch current order at load and in a loop
|
||||
useEffect(() => {
|
||||
if (currentOrder != undefined && (page == 'order' || (order == badOrder) == undefined)) {
|
||||
fetchOrder();
|
||||
}
|
||||
}, [currentOrder, page]);
|
||||
|
||||
useEffect(() => {
|
||||
clearInterval(timer);
|
||||
setTimer(setInterval(fetchOrder, delay));
|
||||
return () => clearInterval(timer);
|
||||
}, [delay, currentOrder, page, badOrder]);
|
||||
|
||||
const orderReceived = function (data: any) {
|
||||
if (data.bad_request != undefined) {
|
||||
setBadOrder(data.bad_request);
|
||||
setDelay(99999999);
|
||||
setOrder(undefined);
|
||||
} else {
|
||||
setDelay(
|
||||
data.status >= 0 && data.status <= 18
|
||||
? page == 'order'
|
||||
? statusToDelay[data.status]
|
||||
: statusToDelay[data.status] * 5
|
||||
: 99999999,
|
||||
);
|
||||
setOrder(data);
|
||||
setBadOrder(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchOrder = function () {
|
||||
if (currentOrder != undefined) {
|
||||
apiClient.get(baseUrl, '/api/order/?order_id=' + currentOrder).then(orderReceived);
|
||||
}
|
||||
};
|
||||
|
||||
const clearOrder = function () {
|
||||
setOrder(undefined);
|
||||
setBadOrder(undefined);
|
||||
};
|
||||
|
||||
const fetchRobot = function ({
|
||||
action = 'login',
|
||||
newKeys = null,
|
||||
newToken = null,
|
||||
refCode = null,
|
||||
setBadRequest = () => {},
|
||||
}: fetchRobotProps) {
|
||||
setRobot({ ...robot, loading: true, avatarLoaded: false });
|
||||
setBadRequest('');
|
||||
|
||||
let requestBody = {};
|
||||
if (action == 'login') {
|
||||
requestBody.token_sha256 = sha256(newToken ?? robot.token);
|
||||
} else if (action == 'generate' && newToken != null) {
|
||||
const strength = tokenStrength(newToken);
|
||||
requestBody.token_sha256 = sha256(newToken);
|
||||
requestBody.unique_values = strength.uniqueValues;
|
||||
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;
|
||||
}
|
||||
|
||||
apiClient.post(baseUrl, '/api/user/', requestBody).then((data: any) => {
|
||||
setCurrentOrder(
|
||||
data.active_order_id
|
||||
? data.active_order_id
|
||||
: data.last_order_id
|
||||
? data.last_order_id
|
||||
: null,
|
||||
);
|
||||
if (data.bad_request) {
|
||||
setBadRequest(data.bad_request);
|
||||
setRobot({
|
||||
...robot,
|
||||
loading: false,
|
||||
nickname: data.nickname ?? robot.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,
|
||||
stealthInvoices: data.wants_stealth ?? robot.stealthInvoices,
|
||||
tgEnabled: data.tg_enabled,
|
||||
tgBotName: data.tg_bot_name,
|
||||
tgToken: data.tg_token,
|
||||
found: false,
|
||||
});
|
||||
} else {
|
||||
setRobot({
|
||||
...robot,
|
||||
nickname: data.nickname,
|
||||
token: newToken ?? robot.token,
|
||||
loading: false,
|
||||
activeOrderId: data.active_order_id ?? null,
|
||||
lastOrderId: data.last_order_id ?? null,
|
||||
referralCode: data.referral_code,
|
||||
earnedRewards: data.earned_rewards ?? 0,
|
||||
stealthInvoices: data.wants_stealth,
|
||||
tgEnabled: data.tg_enabled,
|
||||
tgBotName: data.tg_bot_name,
|
||||
tgToken: data.tg_token,
|
||||
found: data?.found,
|
||||
bitsEntropy: data.token_bits_entropy,
|
||||
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('\\'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (baseUrl != '' && page != 'robot') {
|
||||
if (open.profile || (robot.token && robot.nickname === null)) {
|
||||
fetchRobot({ action: 'login' }); // fetch 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)
|
||||
}
|
||||
}
|
||||
}, [open.profile, baseUrl]);
|
||||
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
torStatus,
|
||||
settings,
|
||||
setSettings,
|
||||
book,
|
||||
setBook,
|
||||
fetchBook,
|
||||
fetchRobot,
|
||||
limits,
|
||||
info,
|
||||
setLimits,
|
||||
fetchLimits,
|
||||
maker,
|
||||
setMaker,
|
||||
clearOrder,
|
||||
robot,
|
||||
setRobot,
|
||||
baseUrl,
|
||||
setBaseUrl,
|
||||
fav,
|
||||
setFav,
|
||||
order,
|
||||
setOrder,
|
||||
badOrder,
|
||||
setBadOrder,
|
||||
setDelay,
|
||||
page,
|
||||
setPage,
|
||||
slideDirection,
|
||||
setSlideDirection,
|
||||
currentOrder,
|
||||
setCurrentOrder,
|
||||
navbarHeight,
|
||||
closeAll,
|
||||
open,
|
||||
setOpen,
|
||||
windowSize,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const AppContext = React.createContext();
|
@ -22,6 +22,7 @@ class Robot {
|
||||
public tgBotName: string = 'unknown';
|
||||
public tgToken: string = 'unknown';
|
||||
public loading: boolean = false;
|
||||
public found: boolean = false;
|
||||
public avatarLoaded: boolean = false;
|
||||
public copiedToken: boolean = false;
|
||||
}
|
||||
|
@ -1,20 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useContext, useState } from 'react';
|
||||
import GridLayout, { Layout } from 'react-grid-layout';
|
||||
import { Grid, styled, useTheme } from '@mui/material';
|
||||
import { apiClient } from '../services/api';
|
||||
import checkVer from '../utils/checkVer';
|
||||
|
||||
import {
|
||||
Book,
|
||||
LimitList,
|
||||
Maker,
|
||||
Robot,
|
||||
Info,
|
||||
Settings,
|
||||
Favorites,
|
||||
defaultMaker,
|
||||
defaultInfo,
|
||||
} from '../models';
|
||||
|
||||
import {
|
||||
PlaceholderWidget,
|
||||
@ -26,21 +12,7 @@ import {
|
||||
import ToolBar from '../pro/ToolBar';
|
||||
import LandingDialog from '../pro/LandingDialog';
|
||||
|
||||
import defaultCoordinators from '../../static/federation.json';
|
||||
import { getHost } from '../utils';
|
||||
|
||||
const getWindowSize = function (fontSize: number) {
|
||||
// returns window size in EM units
|
||||
return {
|
||||
width: window.innerWidth / fontSize,
|
||||
height: window.innerHeight / fontSize,
|
||||
};
|
||||
};
|
||||
|
||||
interface MainProps {
|
||||
settings: Settings;
|
||||
setSettings: (state: Settings) => void;
|
||||
}
|
||||
import { AppContext, AppContextProps } from '../contexts/AppContext';
|
||||
|
||||
// To Do. Add dotted grid when layout is not frozen
|
||||
// ${freeze ?
|
||||
@ -57,115 +29,58 @@ const StyledRGL = styled(GridLayout)(
|
||||
`,
|
||||
);
|
||||
|
||||
const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
|
||||
const defaultLayout: Layout = [
|
||||
{ i: 'Maker', w: 10, h: 16, x: 67, y: 0, minW: 8, maxW: 22, minH: 10, maxH: 28 },
|
||||
{ i: 'Book', w: 43, h: 15, x: 34, y: 16, minW: 6, maxW: 70, minH: 9, maxH: 25 },
|
||||
{ i: 'DepthChart', w: 15, h: 10, x: 19, y: 16, minW: 6, maxW: 22, minH: 9, maxH: 25 },
|
||||
{ i: 'Garage', w: 52, h: 16, x: 0, y: 0, minW: 15, maxW: 78, minH: 8, maxH: 30 },
|
||||
{ i: 'History', w: 8, h: 10, x: 11, y: 16, minW: 6, maxW: 22, minH: 9, maxH: 25 },
|
||||
{ i: 'Trade', w: 15, h: 16, x: 52, y: 0, minW: 6, maxW: 22, minH: 9, maxH: 25 },
|
||||
{ i: 'Settings', w: 11, h: 15, x: 0, y: 16, minW: 6, maxW: 22, minH: 9, maxH: 25 },
|
||||
{ i: 'Other', w: 23, h: 5, x: 11, y: 26, minW: 2, maxW: 50, minH: 4, maxH: 25 },
|
||||
];
|
||||
|
||||
const Main = (): JSX.Element => {
|
||||
const {
|
||||
book,
|
||||
fetchBook,
|
||||
maker,
|
||||
setMaker,
|
||||
setSettings,
|
||||
clearOrder,
|
||||
torStatus,
|
||||
settings,
|
||||
limits,
|
||||
fetchLimits,
|
||||
robot,
|
||||
setRobot,
|
||||
fetchRobot,
|
||||
setOrder,
|
||||
setDelay,
|
||||
info,
|
||||
fav,
|
||||
setFav,
|
||||
baseUrl,
|
||||
order,
|
||||
page,
|
||||
setPage,
|
||||
currentOrder,
|
||||
setCurrentOrder,
|
||||
open,
|
||||
setOpen,
|
||||
windowSize,
|
||||
badOrder,
|
||||
setBadOrder,
|
||||
} = useContext<AppContextProps>(AppContext);
|
||||
|
||||
const theme = useTheme();
|
||||
const em: number = theme.typography.fontSize;
|
||||
const toolbarHeight: number = 3;
|
||||
const gridCellSize: number = 2;
|
||||
|
||||
const defaultLayout: Layout = [
|
||||
{ i: 'Maker', w: 10, h: 16, x: 67, y: 0, minW: 8, maxW: 22, minH: 10, maxH: 28 },
|
||||
{ i: 'Book', w: 43, h: 15, x: 34, y: 16, minW: 6, maxW: 70, minH: 9, maxH: 25 },
|
||||
{ i: 'DepthChart', w: 15, h: 10, x: 19, y: 16, minW: 6, maxW: 22, minH: 9, maxH: 25 },
|
||||
{ i: 'Garage', w: 52, h: 16, x: 0, y: 0, minW: 15, maxW: 78, minH: 8, maxH: 30 },
|
||||
{ i: 'History', w: 10, h: 10, x: 9, y: 16, minW: 6, maxW: 22, minH: 9, maxH: 25 },
|
||||
{ i: 'Trade', w: 15, h: 16, x: 52, y: 0, minW: 6, maxW: 22, minH: 9, maxH: 25 },
|
||||
{ i: 'Settings', w: 9, h: 15, x: 0, y: 16, minW: 6, maxW: 22, minH: 9, maxH: 25 },
|
||||
{ i: 'Other', w: 25, h: 5, x: 9, y: 26, minW: 2, maxW: 50, minH: 4, maxH: 25 },
|
||||
];
|
||||
|
||||
// All app data structured
|
||||
const [book, setBook] = useState<Book>({ orders: [], loading: true });
|
||||
const [limits, setLimits] = useState<{ list: LimitList; loading: boolean }>({
|
||||
list: [],
|
||||
loading: true,
|
||||
});
|
||||
const [robot, setRobot] = useState<Robot>(new Robot());
|
||||
const [maker, setMaker] = useState<Maker>(defaultMaker);
|
||||
const [info, setInfo] = useState<Info>(defaultInfo);
|
||||
const [coordinators, setCoordinators] = useState<Coordinator[]>(defaultCoordinators);
|
||||
const [fav, setFav] = useState<Favorites>({ type: null, currency: 0 });
|
||||
const [baseUrl, setBaseUrl] = useState<string>('');
|
||||
const [openLanding, setOpenLanding] = useState<boolean>(true);
|
||||
const [layout, setLayout] = useState<Layout>(defaultLayout);
|
||||
|
||||
const [openLanding, setOpenLanding] = useState<boolean>(true);
|
||||
const [windowSize, setWindowSize] = useState<{ width: number; height: number }>(
|
||||
getWindowSize(em),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== undefined) {
|
||||
window.addEventListener('resize', onResize);
|
||||
}
|
||||
|
||||
if (baseUrl != '') {
|
||||
fetchBook();
|
||||
fetchLimits();
|
||||
}
|
||||
return () => {
|
||||
if (typeof window !== undefined) {
|
||||
window.removeEventListener('resize', onResize);
|
||||
}
|
||||
};
|
||||
}, [baseUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
let host = '';
|
||||
if (window.NativeRobosats === undefined) {
|
||||
host = getHost();
|
||||
} else {
|
||||
host =
|
||||
settings.network === 'mainnet'
|
||||
? coordinators[0].mainnetOnion
|
||||
: coordinators[0].testnetOnion;
|
||||
}
|
||||
setBaseUrl(`http://${host}`);
|
||||
}, [settings.network]);
|
||||
|
||||
useEffect(() => {
|
||||
setWindowSize(getWindowSize(theme.typography.fontSize));
|
||||
}, [theme.typography.fontSize]);
|
||||
|
||||
const onResize = function () {
|
||||
setWindowSize(getWindowSize(em));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setWindowSize(getWindowSize(theme.typography.fontSize));
|
||||
}, [theme.typography.fontSize]);
|
||||
|
||||
const fetchLimits = async () => {
|
||||
setLimits({ ...limits, loading: true });
|
||||
const data = apiClient.get(baseUrl, '/api/limits/').then((data) => {
|
||||
setLimits({ list: data ?? [], loading: false });
|
||||
return data;
|
||||
});
|
||||
return await data;
|
||||
};
|
||||
|
||||
const fetchBook = function () {
|
||||
setBook({ ...book, loading: true });
|
||||
apiClient.get(baseUrl, '/api/book/').then((data: any) =>
|
||||
setBook({
|
||||
loading: false,
|
||||
orders: data.not_found ? [] : data,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const fetchInfo = function () {
|
||||
apiClient.get(baseUrl, '/api/info/').then((data: any) => {
|
||||
const versionInfo: any = checkVer(data.version.major, data.version.minor, data.version.patch);
|
||||
setInfo({
|
||||
...data,
|
||||
openUpdateClient: versionInfo.updateAvailable,
|
||||
coordinatorVersion: versionInfo.coordinatorVersion,
|
||||
clientVersion: versionInfo.clientVersion,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
console.log(layout);
|
||||
return (
|
||||
<Grid container direction='column' sx={{ width: `${windowSize.width}em` }}>
|
||||
<Grid item>
|
||||
@ -191,41 +106,16 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
|
||||
onLayoutChange={(layout: Layout) => setLayout(layout)}
|
||||
>
|
||||
<div key='Maker'>
|
||||
<MakerWidget
|
||||
baseUrl={baseUrl}
|
||||
limits={limits}
|
||||
fetchLimits={fetchLimits}
|
||||
fav={fav}
|
||||
setFav={setFav}
|
||||
maker={maker}
|
||||
setMaker={setMaker}
|
||||
/>
|
||||
<MakerWidget />
|
||||
</div>
|
||||
<div key='Book'>
|
||||
<BookWidget
|
||||
baseUrl={baseUrl}
|
||||
book={book}
|
||||
layout={layout[1]}
|
||||
gridCellSize={gridCellSize}
|
||||
fetchBook={fetchBook}
|
||||
fav={fav}
|
||||
setFav={setFav}
|
||||
windowSize={windowSize}
|
||||
/>
|
||||
<BookWidget layout={layout[1]} gridCellSize={gridCellSize} />
|
||||
</div>
|
||||
<div key='DepthChart'>
|
||||
<DepthChartWidget
|
||||
baseUrl={baseUrl}
|
||||
orders={book.orders}
|
||||
gridCellSize={gridCellSize}
|
||||
limitList={limits.list}
|
||||
layout={layout[2]}
|
||||
currency={fav.currency}
|
||||
windowSize={windowSize}
|
||||
/>
|
||||
<DepthChartWidget gridCellSize={gridCellSize} layout={layout[2]} />
|
||||
</div>
|
||||
<div key='Settings'>
|
||||
<SettingsWidget settings={settings} setSettings={setSettings} />
|
||||
<SettingsWidget />
|
||||
</div>
|
||||
<div key='Garage'>
|
||||
<PlaceholderWidget label='Robot Garage' />
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { AppContext, AppContextProps } from '../../contexts/AppContext';
|
||||
|
||||
import { Book, Favorites } from '../../models';
|
||||
import { Paper, useTheme } from '@mui/material';
|
||||
import { Paper } from '@mui/material';
|
||||
import BookTable from '../../components/BookTable';
|
||||
|
||||
interface BookWidgetProps {
|
||||
@ -24,13 +25,7 @@ const BookWidget = React.forwardRef(
|
||||
(
|
||||
{
|
||||
layout,
|
||||
baseUrl,
|
||||
gridCellSize = 2,
|
||||
book,
|
||||
fetchBook,
|
||||
fav,
|
||||
setFav,
|
||||
windowSize,
|
||||
style,
|
||||
className,
|
||||
onMouseDown,
|
||||
@ -39,24 +34,18 @@ const BookWidget = React.forwardRef(
|
||||
}: BookWidgetProps,
|
||||
ref,
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
const { book, windowSize, fav } = useContext<AppContextProps>(AppContext);
|
||||
return React.useMemo(() => {
|
||||
return (
|
||||
<Paper elevation={3} style={{ width: '100%', height: '100%' }}>
|
||||
<BookTable
|
||||
baseUrl={baseUrl}
|
||||
elevation={0}
|
||||
clickRefresh={() => fetchBook()}
|
||||
book={book}
|
||||
fav={fav}
|
||||
fillContainer={true}
|
||||
maxWidth={layout.w * gridCellSize} // EM units
|
||||
maxHeight={layout.h * gridCellSize} // EM units
|
||||
fullWidth={windowSize.width} // EM units
|
||||
fullHeight={windowSize.height} // EM units
|
||||
defaultFullscreen={false}
|
||||
onCurrencyChange={(e) => setFav({ ...fav, currency: e.target.value })}
|
||||
onTypeChange={(mouseEvent, val) => setFav({ ...fav, type: val })}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
|
@ -1,22 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Order, LimitList } from '../../models';
|
||||
import React, { useContext } from 'react';
|
||||
import { AppContext, AppContextProps } from '../../contexts/AppContext';
|
||||
import { Paper, useTheme } from '@mui/material';
|
||||
import DepthChart from '../../components/Charts/DepthChart';
|
||||
|
||||
interface DepthChartWidgetProps {
|
||||
layout: any;
|
||||
gridCellSize: number;
|
||||
orders: PublicOrder[];
|
||||
currency: number;
|
||||
limitList: LimitList;
|
||||
windowSize: { width: number; height: number };
|
||||
style?: Object;
|
||||
className?: string;
|
||||
onMouseDown?: () => void;
|
||||
onMouseUp?: () => void;
|
||||
onTouchEnd?: () => void;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const DepthChartWidget = React.forwardRef(
|
||||
@ -24,11 +18,6 @@ const DepthChartWidget = React.forwardRef(
|
||||
{
|
||||
layout,
|
||||
gridCellSize,
|
||||
limitList,
|
||||
orders,
|
||||
baseUrl,
|
||||
currency,
|
||||
windowSize,
|
||||
style,
|
||||
className,
|
||||
onMouseDown,
|
||||
@ -38,22 +27,19 @@ const DepthChartWidget = React.forwardRef(
|
||||
ref,
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
const { fav, book, limits } = useContext<AppContextProps>(AppContext);
|
||||
return React.useMemo(() => {
|
||||
return (
|
||||
<Paper elevation={3} style={{ width: '100%', height: '100%' }}>
|
||||
<DepthChart
|
||||
baseUrl={baseUrl}
|
||||
elevation={0}
|
||||
orders={orders}
|
||||
currency={currency}
|
||||
limits={limitList}
|
||||
maxWidth={layout.w * gridCellSize} // EM units
|
||||
maxHeight={layout.h * gridCellSize} // EM units
|
||||
fillContainer={true}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
}, [currency, orders, limitList, layout]);
|
||||
}, [fav.currency, book, limits, layout]);
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -1,17 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { AppContext, AppContextProps } from '../../contexts/AppContext';
|
||||
|
||||
import MakerForm from '../../components/MakerForm';
|
||||
import { LimitList, Maker, Favorites } from '../../models';
|
||||
import { Paper } from '@mui/material';
|
||||
|
||||
interface MakerWidgetProps {
|
||||
limits: { list: LimitList; loading: boolean };
|
||||
fetchLimits: () => void;
|
||||
fav: Favorites;
|
||||
maker: Maker;
|
||||
setFav: (state: Favorites) => void;
|
||||
setMaker: (state: Maker) => void;
|
||||
baseUrl: string;
|
||||
style?: Object;
|
||||
className?: string;
|
||||
onMouseDown?: () => void;
|
||||
@ -20,38 +14,15 @@ interface MakerWidgetProps {
|
||||
}
|
||||
|
||||
const MakerWidget = React.forwardRef(
|
||||
(
|
||||
{
|
||||
maker,
|
||||
setMaker,
|
||||
limits,
|
||||
fetchLimits,
|
||||
fav,
|
||||
setFav,
|
||||
baseUrl,
|
||||
style,
|
||||
className,
|
||||
onMouseDown,
|
||||
onMouseUp,
|
||||
onTouchEnd,
|
||||
}: MakerWidgetProps,
|
||||
ref,
|
||||
) => {
|
||||
({ style, className, onMouseDown, onMouseUp, onTouchEnd }: MakerWidgetProps, ref) => {
|
||||
const { maker, fav, limits } = useContext<AppContextProps>(AppContext);
|
||||
return React.useMemo(() => {
|
||||
return (
|
||||
<Paper
|
||||
elevation={3}
|
||||
style={{ padding: 8, overflow: 'auto', width: '100%', height: '100%' }}
|
||||
>
|
||||
<MakerForm
|
||||
baseUrl={baseUrl}
|
||||
limits={limits}
|
||||
fetchLimits={fetchLimits}
|
||||
maker={maker}
|
||||
setMaker={setMaker}
|
||||
fav={fav}
|
||||
setFav={setFav}
|
||||
/>
|
||||
<MakerForm />
|
||||
</Paper>
|
||||
);
|
||||
}, [maker, limits, fav]);
|
||||
|
@ -1,12 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { AppContextProps, AppContext } from '../../contexts/AppContext';
|
||||
import { Settings } from '../../models';
|
||||
import { Paper, useTheme } from '@mui/material';
|
||||
import { Paper } from '@mui/material';
|
||||
import SettingsForm from '../../components/SettingsForm';
|
||||
|
||||
interface SettingsWidgetProps {
|
||||
settings: Settings;
|
||||
setSettings: (state: Settings) => void;
|
||||
style?: Object;
|
||||
className?: string;
|
||||
onMouseDown?: () => void;
|
||||
@ -15,26 +13,15 @@ interface SettingsWidgetProps {
|
||||
}
|
||||
|
||||
const SettingsWidget = React.forwardRef(
|
||||
(
|
||||
{
|
||||
settings,
|
||||
setSettings,
|
||||
style,
|
||||
className,
|
||||
onMouseDown,
|
||||
onMouseUp,
|
||||
onTouchEnd,
|
||||
}: SettingsWidgetProps,
|
||||
ref,
|
||||
) => {
|
||||
const theme = useTheme();
|
||||
({ style, className, onMouseDown, onMouseUp, onTouchEnd }: SettingsWidgetProps, ref) => {
|
||||
const { settings } = useContext<AppContextProps>(AppContext);
|
||||
return React.useMemo(() => {
|
||||
return (
|
||||
<Paper
|
||||
elevation={3}
|
||||
style={{ width: '100%', height: '100%', position: 'relative', top: '0.6em', left: '0em' }}
|
||||
>
|
||||
<SettingsForm dense={true} settings={settings} setSettings={setSettings} />
|
||||
<SettingsForm dense={true} />
|
||||
</Paper>
|
||||
);
|
||||
}, [settings]);
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connexió xifrada i anònima mitjançant TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "Això garanteix la màxima privadesa, però és possible que sentis que l'aplicació es comporta lenta. Si es perd la connexió, reinicia l'aplicació.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Si us plau, introdueix el teu token per reconstruir el teu robot i accedir a les seves operacions.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Introdueix el teu token per reconstruir el teu robot i accedir a les seves operacions.",
|
||||
"Paste token here": "Enganxa el token aquí",
|
||||
"Recover": "Recuperar",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
@ -41,7 +41,8 @@
|
||||
"Connection encrypted and anonymized using TOR.": "Connection encrypted and anonymized using TOR.",
|
||||
"This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.": "This ensures maximum privacy, however you might feel the app behaves slow. If connection is lost, restart the app.",
|
||||
"#7": "Phrases in basic/RobotPage/Recovery.tsx",
|
||||
"Please, introduce your robot token to re-build your robot and gain access to its trades.": "Please, introduce your robot token to re-build your robot and gain access to its trades.",
|
||||
"Robot recovery": "Robot recovery",
|
||||
"Enter your robot token to re-build your robot and gain access to its trades.": "Enter your robot token to re-build your robot and gain access to its trades.",
|
||||
"Paste token here": "Paste token here",
|
||||
"Recover": "Recover",
|
||||
"#8": "Phrases in basic/RobotPage/Welcome.tsx",
|
||||
|
Loading…
Reference in New Issue
Block a user