Small fixes and partial TradeBox functional component (#309)

* Add Order model

* Add permanent settings

* Fix maker on book page

* Add chat models

* Attempt to fix Android cookies

* Add Tradebox Dialogs

* Add Lock Invoice box

* Add taker found prompt

* Fix load setting cookies

* Revert TradeBox for test release

* Refactor ordering of constructing theme

* Add load encrypted seetings
This commit is contained in:
Reckless_Satoshi 2022-11-07 10:13:02 +00:00 committed by GitHub
parent 5ae1f8ca18
commit b25230378e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 2384 additions and 611 deletions

View File

@ -10,38 +10,28 @@ import { I18nextProvider } from 'react-i18next';
import i18n from './i18n/Web';
import { systemClient } from './services/System';
import { Settings, defaultSettings } from './models';
import { Settings } from './models';
const defaultTheme: Theme = createTheme({
palette: {
mode: defaultSettings.mode,
background: {
default: defaultSettings.mode === 'dark' ? '#070707' : '#fff',
const makeTheme = function (settings: Settings) {
const theme: Theme = createTheme({
palette: {
mode: settings.mode,
background: {
default: settings.mode === 'dark' ? '#070707' : '#fff',
},
},
},
typography: { fontSize: defaultSettings.fontSize },
});
typography: { fontSize: settings.fontSize },
});
return theme;
};
const App = (): JSX.Element => {
const [theme, setTheme] = useState<Theme>(defaultTheme);
const [settings, setSettings] = useState<Settings>(defaultSettings);
const updateTheme = function () {
setTheme(
createTheme({
palette: {
mode: settings.mode,
background: {
default: settings.mode === 'dark' ? '#070707' : '#fff',
},
},
typography: { fontSize: settings.fontSize },
}),
);
};
const [theme, setTheme] = useState<Theme>(makeTheme(new Settings()));
const [settings, setSettings] = useState<Settings>(new Settings());
useEffect(() => {
updateTheme();
setTheme(makeTheme(settings));
}, [settings.fontSize, settings.mode]);
useEffect(() => {
@ -53,8 +43,11 @@ const App = (): JSX.Element => {
<I18nextProvider i18n={i18n}>
<ThemeProvider theme={theme}>
<CssBaseline />
<TorConnection />
<UnsafeAlert className='unsafeAlert' />
{window.NativeRobosats === undefined ? (
<UnsafeAlert settings={settings} setSettings={setSettings} />
) : (
<TorConnection />
)}
<Main settings={settings} setSettings={setSettings} />
</ThemeProvider>
</I18nextProvider>

View File

@ -26,7 +26,7 @@ interface BookPageProps {
setMaker: (state: Maker) => void;
hasRobot: boolean;
setPage: (state: Page) => void;
setOrder: (state: number) => void;
setCurrentOrder: (state: number) => void;
}
const BookPage = ({
@ -42,7 +42,7 @@ const BookPage = ({
windowSize,
hasRobot = false,
setPage = () => null,
setOrder = () => null,
setCurrentOrder = () => null,
}: BookPageProps): JSX.Element => {
const { t } = useTranslation();
const history = useHistory();
@ -76,7 +76,7 @@ const BookPage = ({
if (hasRobot) {
history.push('/order/' + id);
setPage('order');
setOrder(id);
setCurrentOrder(id);
} else {
setOpenNoRobot(true);
}
@ -125,6 +125,11 @@ const BookPage = ({
setFav={setFav}
setPage={setPage}
hasRobot={hasRobot}
onOrderCreated={(id) => {
setCurrentOrder(id);
setPage('order');
history.push('/order/' + id);
}}
/>
</Box>
</Dialog>

View File

@ -73,7 +73,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
in: undefined,
out: undefined,
});
const [order, setOrder] = useState<number | null>(null);
const [currentOrder, setCurrentOrder] = useState<number | null>(null);
const navbarHeight = 2.5;
const closeAll = {
@ -163,7 +163,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
setRobot({ ...robot, loading: true });
apiClient.post('/api/user/', requestBody).then((data: any) => {
setOrder(
setCurrentOrder(
data.active_order_id
? data.active_order_id
: data.last_order_id
@ -230,8 +230,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
<div>
<UserGenPage
setPage={setPage}
order={order}
setOrder={setOrder}
setCurrentOrder={setCurrentOrder}
match={props.match}
theme={theme}
robot={robot}
@ -262,7 +261,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
windowSize={windowSize}
hasRobot={robot.avatarLoaded}
setPage={setPage}
setOrder={setOrder}
setCurrentOrder={setCurrentOrder}
/>
</div>
</Slide>
@ -282,7 +281,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
maker={maker}
setMaker={setMaker}
setPage={setPage}
setOrder={setOrder}
setCurrentOrder={setCurrentOrder}
fav={fav}
setFav={setFav}
windowSize={{ ...windowSize, height: windowSize.height - navbarHeight }}
@ -334,13 +333,15 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
setOpen={setOpen}
closeAll={closeAll}
setSlideDirection={setSlideDirection}
order={order}
currentOrder={currentOrder}
hasRobot={robot.avatarLoaded}
/>
<MainDialogs
open={open}
setOpen={setOpen}
setRobot={setRobot}
setPage={setPage}
setCurrentOrder={setCurrentOrder}
info={info}
robot={robot}
closeAll={closeAll}

View File

@ -9,6 +9,7 @@ import {
StatsDialog,
UpdateClientDialog,
} from '../../components/Dialogs';
import { Page } from '../NavBar';
export interface OpenDialogs {
more: boolean;
@ -27,6 +28,8 @@ interface MainDialogsProps {
info: Info;
robot: Robot;
setRobot: (state: Robot) => void;
setPage: (state: Page) => void;
setCurrentOrder: (state: number) => void;
closeAll: OpenDialogs;
}
@ -37,6 +40,8 @@ const MainDialogs = ({
closeAll,
robot,
setRobot,
setPage,
setCurrentOrder,
}: MainDialogsProps): JSX.Element => {
useEffect(() => {
if (info.openUpdateClient) {
@ -77,6 +82,8 @@ const MainDialogs = ({
onClose={() => setOpen({ ...open, profile: false })}
robot={robot}
setRobot={setRobot}
setPage={setPage}
setCurrentOrder={setCurrentOrder}
/>
</>
);

View File

@ -1,5 +1,6 @@
import React, { 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 } from '../../models';
@ -21,7 +22,7 @@ interface MakerPageProps {
setMaker: (state: Maker) => void;
windowSize: { width: number; height: number };
hasRobot: boolean;
setOrder: (state: number) => void;
setCurrentOrder: (state: number) => void;
setPage: (state: Page) => void;
}
@ -34,11 +35,12 @@ const MakerPage = ({
setFav,
setMaker,
windowSize,
setOrder,
setCurrentOrder,
setPage,
hasRobot = false,
}: MakerPageProps): JSX.Element => {
const { t } = useTranslation();
const history = useHistory();
const maxHeight = windowSize.height * 0.85 - 3;
const [showMatches, setShowMatches] = useState<boolean>(false);
@ -95,8 +97,9 @@ const MakerPage = ({
maker={maker}
setMaker={setMaker}
onOrderCreated={(id) => {
setOrder(id);
setCurrentOrder(id);
setPage('order');
history.push('/order/' + id);
}}
hasRobot={hasRobot}
disableRequest={matches.length > 0 && !showMatches}

View File

@ -30,7 +30,7 @@ interface NavBarProps {
open: OpenDialogs;
setOpen: (state: OpenDialogs) => void;
closeAll: OpenDialogs;
order: number | null;
currentOrder: number | null;
hasRobot: boolean;
}
@ -44,7 +44,7 @@ const NavBar = ({
closeAll,
width,
height,
order,
currentOrder,
hasRobot = false,
}: NavBarProps): JSX.Element => {
const theme = useTheme();
@ -77,7 +77,7 @@ const NavBar = ({
} else {
handleSlideDirection(page, newPage);
setPage(newPage);
const param = newPage === 'order' ? order ?? '' : '';
const param = newPage === 'order' ? currentOrder ?? '' : '';
setTimeout(
() => history.push(`/${newPage}/${param}`),
theme.transitions.duration.leavingScreen * 3,
@ -92,7 +92,7 @@ const NavBar = ({
return (
<Paper
elevation={6}
sx={{ height: `${height}em`, width: `${width * 0.9}em`, position: 'fixed', bottom: 0 }}
sx={{ height: `${height}em`, width: `100%`, position: 'fixed', bottom: 0, borderRadius: 0 }}
>
<Tabs
TabIndicatorProps={{ sx: { height: '0.3em', position: 'absolute', top: 0 } }}
@ -144,7 +144,7 @@ const NavBar = ({
sx={tabSx}
label={smallBar ? undefined : t('Order')}
value='order'
disabled={!hasRobot || order == null}
disabled={!hasRobot || currentOrder == null}
icon={<Assignment />}
iconPosition='start'
/>
@ -176,472 +176,3 @@ const NavBar = ({
};
export default NavBar;
// constructor(props) {
// super(props);
// this.state = {
// profileShown: false,
// openStatsForNerds: false,
// openCommunity: false,
// openExchangeSummary: false,
// openClaimRewards: false,
// openProfile: false,
// showRewards: false,
// rewardInvoice: null,
// badInvoice: false,
// showRewardsSpinner: false,
// withdrawn: false,
// };
// }
// handleClickOpenStatsForNerds = () => {
// this.setState({ openStatsForNerds: true });
// };
// handleClickCloseStatsForNerds = () => {
// this.setState({ openStatsForNerds: false });
// };
// handleClickOpenCommunity = () => {
// this.setState({ openCommunity: true });
// };
// handleClickCloseCommunity = () => {
// this.setState({ openCommunity: false });
// };
// handleClickOpenProfile = () => {
// this.setState({ openProfile: true, profileShown: true });
// };
// handleClickCloseProfile = () => {
// this.setState({ openProfile: false });
// };
// handleClickOpenExchangeSummary = () => {
// this.setState({ openExchangeSummary: true });
// };
// handleClickCloseExchangeSummary = () => {
// this.setState({ openExchangeSummary: false });
// };
// handleSubmitInvoiceClicked = (e, rewardInvoice) => {
// this.setState({ badInvoice: false, showRewardsSpinner: true });
// apiClient
// .post('/api/reward/', {
// invoice: rewardInvoice,
// })
// .then((data) => {
// this.setState({ badInvoice: data.bad_invoice, showRewardsSpinner: false });
// this.props.setInfo({
// ...this.props.info,
// badInvoice: data.bad_invoice,
// openClaimRewards: !data.successful_withdrawal,
// withdrawn: !!data.successful_withdrawal,
// showRewardsSpinner: false,
// });
// this.props.setRobot({
// ...this.props.robot,
// earnedRewards: data.successful_withdrawal ? 0 : this.props.robot.earnedRewards,
// });
// });
// e.preventDefault();
// };
// handleSetStealthInvoice = (wantsStealth) => {
// apiClient
// .put('/api/stealth/', { wantsStealth })
// .then((data) =>
// this.props.setRobot({ ...this.props.robot, stealthInvoices: data?.wantsStealth }),
// );
// };
// showProfileButton = () => {
// return (
// this.props.robot.avatarLoaded &&
// (this.props.robot.token
// ? systemClient.getCookie('robot_token') === this.props.robot.token
// : true) &&
// systemClient.getCookie('sessionid')
// );
// };
// bottomBarDesktop = () => {
// const { t } = this.props;
// const hasRewards = this.props.robot.earnedRewards > 0;
// const hasOrder = !!(
// (this.props.robot.activeOrderId > 0) &
// !this.state.profileShown &
// this.props.robot.avatarLoaded
// );
// const fontSize = this.props.theme.typography.fontSize;
// const fontSizeFactor = fontSize / 14; // default fontSize is 14
// const typographyProps = {
// primaryTypographyProps: { fontSize },
// secondaryTypographyProps: { fontSize: (fontSize * 12) / 14 },
// };
// return (
// <Paper
// elevation={6}
// style={{ height: '2.5em', width: `${(this.props.windowSize.width / 16) * 14}em` }}
// >
// <Grid container>
// <Grid item xs={1.9}>
// <div style={{ display: this.showProfileButton() ? '' : 'none' }}>
// <ListItemButton onClick={this.handleClickOpenProfile}>
// <Tooltip
// open={(hasRewards || hasOrder) && this.showProfileButton()}
// title={
// (hasRewards ? t('You can claim satoshis!') + ' ' : '') +
// (hasOrder ? t('You have an active order') : '')
// }
// >
// <ListItemAvatar sx={{ width: 30 * fontSizeFactor, height: 30 * fontSizeFactor }}>
// <RobotAvatar
// style={{ marginTop: -13 }}
// statusColor={
// (this.props.robot.activeOrderId > 0) & !this.state.profileShown
// ? 'primary'
// : undefined
// }
// nickname={this.props.robot.nickname}
// onLoad={() =>
// this.props.setRobot({ ...this.props.robot, avatarLoaded: true })
// }
// />
// </ListItemAvatar>
// </Tooltip>
// <ListItemText primary={this.props.robot.nickname} />
// </ListItemButton>
// </div>
// </Grid>
// <Grid item xs={1.9}>
// <ListItem className='bottomItem'>
// <ListItemIcon size='small'>
// <IconButton
// disabled={!this.showProfileButton()}
// color='primary'
// to={`/book/`}
// component={LinkRouter}
// >
// <InventoryIcon />
// </IconButton>
// </ListItemIcon>
// <ListItemText
// {...typographyProps}
// primary={this.props.info.num_public_buy_orders}
// secondary={t('Public Buy Orders')}
// />
// </ListItem>
// </Grid>
// <Grid item xs={1.9}>
// <ListItem className='bottomItem'>
// <ListItemIcon size='small'>
// <IconButton
// disabled={!this.showProfileButton()}
// color='primary'
// to={`/book/`}
// component={LinkRouter}
// >
// <SellIcon />
// </IconButton>
// </ListItemIcon>
// <ListItemText
// {...typographyProps}
// primary={this.props.info.num_public_sell_orders}
// secondary={t('Public Sell Orders')}
// />
// </ListItem>
// </Grid>
// <Grid item xs={1.9}>
// <ListItem className='bottomItem'>
// <ListItemIcon size='small'>
// <IconButton
// disabled={!this.showProfileButton()}
// color='primary'
// to={`/`}
// component={LinkRouter}
// >
// <SmartToyIcon />
// </IconButton>
// </ListItemIcon>
// <ListItemText
// {...typographyProps}
// primary={this.props.info.active_robots_today}
// secondary={t('Today Active Robots')}
// />
// </ListItem>
// </Grid>
// <Grid item xs={1.9}>
// <ListItem className='bottomItem'>
// <ListItemIcon size='small'>
// <IconButton color='primary' onClick={this.handleClickOpenExchangeSummary}>
// <PriceChangeIcon />
// </IconButton>
// </ListItemIcon>
// <ListItemText
// {...typographyProps}
// primary={this.props.info.last_day_nonkyc_btc_premium + '%'}
// secondary={t('24h Avg Premium')}
// />
// </ListItem>
// </Grid>
// <Grid item xs={1.5}>
// <ListItem className='bottomItem'>
// <ListItemIcon size='small'>
// <IconButton color='primary' onClick={this.handleClickOpenExchangeSummary}>
// <PercentIcon />
// </IconButton>
// </ListItemIcon>
// <ListItemText
// {...typographyProps}
// primary={(this.props.info.maker_fee + this.props.info.taker_fee) * 100}
// secondary={t('Trade Fee')}
// />
// </ListItem>
// </Grid>
// <Grid container item xs={1}>
// <Grid item xs={6}>
// {this.LangSelect()}
// </Grid>
// <Grid item xs={3}>
// <Tooltip enterTouchDelay={250} title={t('Show community and support links')}>
// <IconButton
// color='primary'
// aria-label='Community'
// onClick={this.handleClickOpenCommunity}
// >
// <PeopleIcon />
// </IconButton>
// </Tooltip>
// </Grid>
// <Grid item xs={3}>
// <Tooltip enterTouchDelay={250} title={t('Show stats for nerds')}>
// <IconButton
// color='primary'
// aria-label='Stats for Nerds'
// onClick={this.handleClickOpenStatsForNerds}
// >
// <BarChartIcon />
// </IconButton>
// </Tooltip>
// </Grid>
// </Grid>
// </Grid>
// </Paper>
// );
// };
// bottomBarPhone = () => {
// const { t } = this.props;
// const hasRewards = this.props.robot.earnedRewards > 0;
// const hasOrder = !!(
// (this.props.info.active_order_id > 0) &
// !this.state.profileShown &
// this.props.robot.avatarLoaded
// );
// return (
// <Paper
// elevation={6}
// style={{ height: '2.85em', width: `${(this.props.windowSize.width / 16) * 14}em` }}
// >
// <Grid container>
// <Grid item xs={1.6}>
// <div style={{ display: this.showProfileButton() ? '' : 'none' }}>
// <Tooltip
// open={(hasRewards || hasOrder) && this.showProfileButton()}
// title={
// (hasRewards ? t('You can claim satoshis!') + ' ' : '') +
// (hasOrder ? t('You have an active order') : '')
// }
// >
// <IconButton
// onClick={this.handleClickOpenProfile}
// sx={{ margin: 0, bottom: 17, right: 8 }}
// >
// <RobotAvatar
// style={{ width: 55, height: 55 }}
// avatarClass='phoneFlippedSmallAvatar'
// statusColor={
// (this.props.activeOrderId > 0) & !this.state.profileShown
// ? 'primary'
// : undefined
// }
// nickname={this.props.robot.nickname}
// onLoad={() => this.props.setRobot({ ...this.props.robot, avatarLoaded: true })}
// />
// </IconButton>
// </Tooltip>
// </div>
// </Grid>
// <Grid item xs={1.6} align='center'>
// <Tooltip enterTouchDelay={300} title={t('Number of public BUY orders')}>
// <IconButton
// disabled={!this.showProfileButton()}
// color='primary'
// to={`/book/`}
// component={LinkRouter}
// >
// <Badge badgeContent={this.props.info.num_public_buy_orders} color='action'>
// <InventoryIcon />
// </Badge>
// </IconButton>
// </Tooltip>
// </Grid>
// <Grid item xs={1.6} align='center'>
// <Tooltip enterTouchDelay={300} title={t('Number of public SELL orders')}>
// <IconButton
// disabled={!this.showProfileButton()}
// color='primary'
// to={`/book/`}
// component={LinkRouter}
// >
// <Badge badgeContent={this.props.info.num_public_sell_orders} color='action'>
// <SellIcon />
// </Badge>
// </IconButton>
// </Tooltip>
// </Grid>
// <Grid item xs={1.6} align='center'>
// <Tooltip enterTouchDelay={300} title={t('Today active robots')}>
// <IconButton
// disabled={!this.showProfileButton()}
// color='primary'
// to={`/`}
// component={LinkRouter}
// >
// <Badge badgeContent={this.props.info.active_robots_today} color='action'>
// <SmartToyIcon />
// </Badge>
// </IconButton>
// </Tooltip>
// </Grid>
// <Grid item xs={1.8} align='center'>
// <Tooltip enterTouchDelay={300} title={t('24h non-KYC bitcoin premium')}>
// <IconButton color='primary' onClick={this.handleClickOpenExchangeSummary}>
// <Badge
// badgeContent={this.props.info.last_day_nonkyc_btc_premium + '%'}
// color='action'
// >
// <PriceChangeIcon />
// </Badge>
// </IconButton>
// </Tooltip>
// </Grid>
// <Grid container item xs={3.8}>
// <Grid item xs={6}>
// {this.LangSelect()}
// </Grid>
// <Grid item xs={3}>
// <Tooltip enterTouchDelay={250} title={t('Show community and support links')}>
// <IconButton
// color='primary'
// aria-label='Community'
// onClick={this.handleClickOpenCommunity}
// >
// <PeopleIcon />
// </IconButton>
// </Tooltip>
// </Grid>
// <Grid item xs={3}>
// <Tooltip enterTouchDelay={250} title={t('Show stats for nerds')}>
// <IconButton
// color='primary'
// aria-label='Stats for Nerds'
// onClick={() => this.props.fetchInfo()}
// onClick={this.handleClickOpenStatsForNerds}
// >
// <BarChartIcon />
// </IconButton>
// </Tooltip>
// </Grid>
// </Grid>
// </Grid>
// </Paper>
// );
// };
// render() {
// return (
// <div>
// <UpdateClientDialog
// open={this.state.openUpdateClient}
// coordinatorVersion={this.props.info.coordinatorVersion}
// clientVersion={this.props.info.clientVersion}
// handleClickClose={() =>
// this.props.setInfo({ ...this.props.info, openUpdateClient: false })
// }
// />
// <ExchangeSummaryDialog
// open={this.state.openExchangeSummary}
// handleClickCloseExchangeSummary={this.handleClickCloseExchangeSummary}
// numPublicBuyOrders={this.props.info.num_public_buy_orders}
// numPublicSellOrders={this.props.info.num_public_sell_orders}
// bookLiquidity={this.props.info.book_liquidity}
// activeRobotsToday={this.props.info.active_robots_today}
// lastDayNonkycBtcPremium={this.props.info.last_day_nonkyc_btc_premium}
// makerFee={this.props.info.maker_fee}
// takerFee={this.props.info.taker_fee}
// swapFeeRate={this.props.info.current_swap_fee_rate}
// />
// <ProfileDialog
// open={this.state.openProfile}
// handleClickCloseProfile={this.handleClickCloseProfile}
// nickname={this.props.robot.nickname}
// activeOrderId={this.props.robot.activeOrderId}
// lastOrderId={this.props.robot.lastOrderId}
// referralCode={this.props.robot.referralCode}
// tgEnabled={this.props.robot.tgEnabled}
// tgBotName={this.props.robot.tgBotName}
// tgToken={this.props.robot.tgToken}
// handleSubmitInvoiceClicked={this.handleSubmitInvoiceClicked}
// showRewardsSpinner={this.state.showRewardsSpinner}
// withdrawn={this.props.info.withdrawn}
// badInvoice={this.props.info.badInvoice}
// earnedRewards={this.props.robot.earnedRewards}
// updateRobot={(newParam) => this.props.setRobot({ ...robot, ...newParam })}
// stealthInvoices={this.props.robot.stealthInvoices}
// handleSetStealthInvoice={this.handleSetStealthInvoice}
// />
// <StatsDialog
// open={this.state.openStatsForNerds}
// handleClickCloseStatsForNerds={this.handleClickCloseStatsForNerds}
// coordinatorVersion={this.props.info.coordinatorVersion}
// clientVersion={this.props.info.clientVersion}
// lndVersion={this.props.info.lnd_version}
// network={this.props.info.network}
// nodeAlias={this.props.info.node_alias}
// nodeId={this.props.info.node_id}
// alternativeName={this.props.info.alternative_name}
// alternativeSite={this.props.info.alternative_site}
// commitHash={this.props.info.robosats_running_commit_hash}
// lastDayVolume={this.props.info.last_day_volume}
// lifetimeVolume={this.props.info.lifetime_volume}
// />
// <MediaQuery minWidth={1200}>{this.bottomBarDesktop()}</MediaQuery>
// <MediaQuery maxWidth={1199}>{this.bottomBarPhone()}</MediaQuery>
// </div>
// );
// }
// }
// export default NavBar;

View File

@ -73,12 +73,12 @@ class UserGenPage extends Component {
requestBody.then((body) =>
apiClient.post('/api/user/', body).then((data) => {
this.setState({ found: data.found, bad_request: data.bad_request });
this.props.setOrder(
this.props.setCurrentOrder(
data.active_order_id
? data.active_order_id
: data.last_order_id
? data.last_order_id
: this.props.order,
: null,
);
// Add nick and token to App state (token only if not a bad request)
data.bad_request

View File

@ -21,7 +21,7 @@ import {
import { AddCircleOutline, RemoveCircleOutline } from '@mui/icons-material';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { Order, LimitList } from '../../../models';
import { PublicOrder, LimitList } from '../../../models';
import RobotAvatar from '../../RobotAvatar';
import { amountToString, matchMedian, statusBadgeColor } from '../../../utils';
import currencyDict from '../../../../static/assets/currencies.json';
@ -29,7 +29,7 @@ import { PaymentStringAsIcons } from '../../PaymentMethods';
import getNivoScheme from '../NivoScheme';
interface DepthChartProps {
orders: Order[];
orders: PublicOrder[];
lastDayPremium?: number | undefined;
currency: number;
limits: LimitList;
@ -114,15 +114,17 @@ const DepthChart: React.FC<DepthChartProps> = ({
}, [enrichedOrders, xType, lastDayPremium, currencyCode]);
const generateSeries: () => void = () => {
const sortedOrders: Order[] =
const sortedOrders: PublicOrder[] =
xType === 'base_amount'
? enrichedOrders.sort(
(order1, order2) => (order1?.base_amount || 0) - (order2?.base_amount || 0),
)
: enrichedOrders.sort((order1, order2) => order1.premium - order2.premium);
const sortedBuyOrders: Order[] = sortedOrders.filter((order) => order.type == 0).reverse();
const sortedSellOrders: Order[] = sortedOrders.filter((order) => order.type == 1);
const sortedBuyOrders: PublicOrder[] = sortedOrders
.filter((order) => order.type == 0)
.reverse();
const sortedSellOrders: PublicOrder[] = sortedOrders.filter((order) => order.type == 1);
const buySerie: Datum[] = generateSerie(sortedBuyOrders);
const sellSerie: Datum[] = generateSerie(sortedSellOrders);
@ -142,7 +144,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
]);
};
const generateSerie = (orders: Order[]): Datum[] => {
const generateSerie = (orders: PublicOrder[]): Datum[] => {
if (center == undefined) {
return [];
}
@ -159,7 +161,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
y: lastSumOrders,
},
{
// Order Point
// PublicOrder Point
x: xType === 'base_amount' ? order.base_amount : order.premium,
y: sumOrders,
order,
@ -220,7 +222,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
const generateTooltip: React.FunctionComponent<PointTooltipProps> = (
pointTooltip: PointTooltipProps,
) => {
const order: Order = pointTooltip.point.data.order;
const order: PublicOrder = pointTooltip.point.data.order;
return order ? (
<Paper elevation={12} style={{ padding: 10, width: 250 }}>
<Grid container justifyContent='space-between'>

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useTheme } from '@mui/material/styles';
import { Link as LinkRouter } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import {
Badge,
@ -41,16 +41,27 @@ import { getHost, getWebln } from '../../utils';
import RobotAvatar from '../RobotAvatar';
import { apiClient } from '../../services/api';
import { Robot } from '../../models';
import { Page } from '../../basic/NavBar';
interface Props {
open: boolean;
onClose: () => void;
robot: Robot;
setRobot: (state: Robot) => void;
setPage: (state: Page) => void;
setCurrentOrder: (state: number) => void;
}
const ProfileDialog = ({ open = false, onClose, robot, setRobot }: Props): JSX.Element => {
const ProfileDialog = ({
open = false,
onClose,
robot,
setRobot,
setPage,
setCurrentOrder,
}: Props): JSX.Element => {
const { t } = useTranslation();
const history = useHistory();
const theme = useTheme();
const host = getHost();
@ -179,9 +190,12 @@ const ProfileDialog = ({ open = false, onClose, robot, setRobot }: Props): JSX.E
{robot.activeOrderId ? (
<ListItemButton
onClick={onClose}
to={`/order/${robot.activeOrderId}`}
component={LinkRouter}
onClick={() => {
history.push('/order/' + robot.activeOrderId);
setPage('order');
setCurrentOrder(robot.activeOrderId);
onClose();
}}
>
<ListItemIcon>
<Badge badgeContent='' color='primary'>
@ -195,9 +209,12 @@ const ProfileDialog = ({ open = false, onClose, robot, setRobot }: Props): JSX.E
</ListItemButton>
) : robot.lastOrderId ? (
<ListItemButton
onClick={onClose}
to={`/order/${robot.lastOrderId}`}
component={LinkRouter}
onClick={() => {
history.push('/order/' + robot.lastOrderId);
setPage('order');
setCurrentOrder(robot.lastOrderId);
onClose();
}}
>
<ListItemIcon>
<NumbersIcon color='primary' />

View File

@ -266,7 +266,6 @@ const MakerForm = ({
apiClient.post('/api/make/', body).then((data: object) => {
setBadRequest(data.bad_request);
if (data.id) {
history.push('/order/' + data.id);
onOrderCreated(data.id);
}
setSubmittingRequest(false);

View File

@ -24,6 +24,7 @@ import {
SettingsOverscan,
Link,
} from '@mui/icons-material';
import { systemClient } from '../../services/System';
interface SettingsFormProps {
dense?: boolean;
@ -58,7 +59,10 @@ const SettingsForm = ({
</ListItemIcon>
<SelectLanguage
language={settings.language}
setLanguage={(language) => setSettings({ ...settings, language })}
setLanguage={(language) => {
setSettings({ ...settings, language });
systemClient.setCookie('settings_language', language);
}}
/>
</ListItem>
@ -103,9 +107,11 @@ const SettingsForm = ({
<LightMode sx={{ width: '0.67em', height: '0.67em', color: '#666' }} />
</Paper>
}
onChange={(e) =>
setSettings({ ...settings, mode: e.target.checked ? 'dark' : 'light' })
}
onChange={(e) => {
const mode = e.target.checked ? 'dark' : 'light';
setSettings({ ...settings, mode });
systemClient.setCookie('settings_mode', mode);
}}
/>
}
/>
@ -120,7 +126,14 @@ const SettingsForm = ({
min={settings.frontend == 'basic' ? 12 : 10}
max={settings.frontend == 'basic' ? 16 : 14}
step={1}
onChange={(e) => setSettings({ ...settings, fontSize: e.target.value })}
onChange={(e) => {
const fontSize = e.target.value;
setSettings({ ...settings, fontSize });
systemClient.setCookie(
`settings_fontsize_${settings.frontend}`,
fontSize.toString(),
);
}}
valueLabelDisplay='off'
marks={fontSizes.map(({ label, value }) => ({
label: <Typography variant='caption'>{t(label)}</Typography>,
@ -137,7 +150,7 @@ const SettingsForm = ({
<ToggleButtonGroup
exclusive={true}
value={settings.network}
onChange={(e, value) => setSettings({ ...settings, network: value })}
onChange={(e, network) => setSettings({ ...settings, network })}
>
<ToggleButton value='mainnet' color='primary'>
{t('Mainnet')}

View File

@ -0,0 +1,44 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Typography } from '@mui/material';
import { Lock, LockOpen, Balance } from '@mui/icons-material';
interface BondStatusProps {
status: 'locked' | 'settled' | 'returned' | 'hide';
isMaker: boolean;
}
const BondStatus = ({ status, isMaker }: BondStatusProps): JSX.Element => {
const { t } = useTranslation();
let Icon = Lock;
if (status === 'returned') {
Icon = LockOpen;
} else if (status === 'settled') {
Icon = Balance;
}
if (status === 'hide') {
return <></>;
} else {
return (
<Box>
<Typography color='primary' variant='subtitle1' align='center'>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexWrap: 'wrap',
}}
>
<Icon />
{t(`Your ${isMaker ? 'maker' : 'taker'} bond is ${status}`)}
</div>
</Typography>
</Box>
);
}
};
export default BondStatus;

View File

@ -0,0 +1,51 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
Dialog,
DialogTitle,
DialogActions,
DialogContent,
DialogContentText,
Button,
} from '@mui/material';
interface ConfirmDisputeDialogProps {
open: boolean;
onClose: () => void;
onAgreeClick: () => void;
}
export const ConfirmDisputeDialog = ({
open,
onClose,
onAgreeClick,
}: ConfirmDisputeDialogProps): JSX.Element => {
const { t } = useTranslation();
return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>{t('Do you want to open a dispute?')}</DialogTitle>
<DialogContent>
<DialogContentText>
{t(
'The RoboSats staff will examine the statements and evidence provided. You need to build a complete case, as the staff cannot read the chat. It is best to provide a burner contact method with your statement. The satoshis in the trade escrow will be sent to the dispute winner, while the dispute loser will lose the bond.',
)}
</DialogContentText>
<br />
<DialogContentText>
{t(
'Make sure to EXPORT the chat log. The staff might request your exported chat log JSON in order to solve discrepancies. It is your responsibility to store it.',
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose} autoFocus>
{t('Disagree')}
</Button>
<Button onClick={onAgreeClick}>{t('Agree and open dispute')}</Button>
</DialogActions>
</Dialog>
);
};
export default ConfirmDisputeDialog;

View File

@ -0,0 +1,60 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
Dialog,
DialogTitle,
DialogActions,
DialogContent,
DialogContentText,
Button,
} from '@mui/material';
import { Order } from '../../../models';
import currencies from '../../../../static/assets/currencies.json';
import { pn } from '../../../utils';
import { LoadingButton } from '@mui/lab';
interface ConfirmFiatReceivedDialogProps {
open: boolean;
loadingButton: boolean;
order: Order;
onClose: () => void;
onConfirmClick: () => void;
}
export const ConfirmFiatReceivedDialog = ({
open,
loadingButton,
onClose,
order,
onConfirmClick,
}: ConfirmFiatReceivedDialogProps): JSX.Element => {
const { t } = useTranslation();
const currencyCode = currencies[order.currency.toString()];
const amount = pn(parseFloat(parseFloat(order.amount).toFixed(order.currency == 1000 ? 8 : 4)));
return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>
{t('Confirm you received {{amount}} {{currencyCode}}?', { currencyCode, amount })}
</DialogTitle>
<DialogContent>
<DialogContentText id='alert-dialog-description'>
{t(
'Confirming that you received the fiat will finalize the trade. The satoshis in the escrow will be released to the buyer. Only confirm after the {{amount}} {{currencyCode}} have arrived to your account. In addition, if you have received the payment and do not confirm it, you risk losing your bond.',
{ currencyCode, amount },
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose} autoFocus>
{t('Go back')}
</Button>
<LoadingButton loading={loadingButton} onClick={onConfirmClick}>
{t('Confirm')}
</LoadingButton>
</DialogActions>
</Dialog>
);
};
export default ConfirmFiatReceivedDialog;

View File

@ -0,0 +1,2 @@
export { ConfirmDisputeDialog } from './ConfirmDispute';
export { ConfirmFiatReceivedDialog } from './ConfirmFiatReceived';

View File

@ -28,21 +28,13 @@ import CircularProgress from '@mui/material/CircularProgress';
import KeyIcon from '@mui/icons-material/Key';
import { ExportIcon } from '../../Icons';
import { useTheme } from '@mui/system';
import { WebSocketsChatMessage } from '../../../models';
interface Props {
orderId: number;
userNick: string;
}
interface EncryptedChatMessage {
userNick: string;
validSignature: boolean;
plainTextMessage: string;
encryptedMessage: string;
time: string;
index: number;
}
const EncryptedChat: React.FC<Props> = ({ orderId, userNick }: Props): JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
@ -58,7 +50,7 @@ const EncryptedChat: React.FC<Props> = ({ orderId, userNick }: Props): JSX.Eleme
);
const [peerPubKey, setPeerPubKey] = useState<string>();
const [token] = useState<string>(systemClient.getCookie('robot_token') || '');
const [messages, setMessages] = useState<EncryptedChatMessage[]>([]);
const [messages, setMessages] = useState<WebSocketsChatMessage[]>([]);
const [serverMessages, setServerMessages] = useState<any[]>([]);
const [value, setValue] = useState<string>('');
const [connection, setConnection] = useState<WebsocketConnection>();

View File

@ -0,0 +1,114 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Grid, Link, Typography, TextField, Tooltip, useTheme } from '@mui/material';
import { AccountBalanceWallet, ContentCopy } from '@mui/icons-material';
import { NewTabIcon } from '../../Icons';
import QRCode from 'react-qr-code';
import { Order } from '../../../models';
import { systemClient } from '../../../services/System';
import currencies from '../../../../static/assets/currencies.json';
interface LockInvoicePromptProps {
order: Order;
concept: 'bond' | 'escrow';
}
export const LockInvoicePrompt = ({ order, concept }: LockInvoicePromptProps): JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
const currencyCode: string = currencies[`${order.currency}`];
const invoice = concept === 'bond' ? order.bond_invoice : order.escrow_invoice;
const helperText =
concept === 'bond'
? t(
'This is a hold invoice, it will freeze in your wallet. It will be charged only if you cancel or lose a dispute.',
)
: t(
'This is a hold invoice, it will freeze in your wallet. It will be released to the buyer once you confirm to have received the {{currencyCode}}.',
{ currencyCode },
);
const CompatibleWalletsButton = function () {
return (
<Button
color='primary'
component={Link}
href={'https://learn.robosats.com/docs/wallets/'}
target='_blank'
align='center'
>
<AccountBalanceWallet />
{t('See Compatible Wallets')}
<NewTabIcon sx={{ width: '1.1em', height: '1.1em' }} />
</Button>
);
};
const depositHoursMinutes = function () {
const hours = Math.floor(order.escrow_duration / 3600);
const minutes = Math.floor((order.escrow_duration - hours * 3600) / 60);
const dict = { deposit_timer_hours: hours, deposit_timer_minutes: minutes };
return dict;
};
const ExpirationWarning = function () {
return (
<Typography variant='body2'>
{t(
'You risk losing your bond if you do not lock the collateral. Total time available is {{deposit_timer_hours}}h {{deposit_timer_minutes}}m.',
depositHoursMinutes(),
)}
</Typography>
);
};
return (
<Grid container spacing={1}>
<Grid item xs={12}>
{concept === 'bond' ? <CompatibleWalletsButton /> : <ExpirationWarning />}
</Grid>
<Grid item xs={12}>
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
<QRCode
bgColor={'rgba(255, 255, 255, 0)'}
fgColor={theme.palette.text.primary}
value={invoice}
size={theme.typography.fontSize * 21.8}
onClick={() => {
systemClient.copyToClipboard(invoice);
}}
/>
</Tooltip>
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
<Button
size='small'
color='inherit'
onClick={() => {
systemClient.copyToClipboard(invoice);
}}
>
<ContentCopy />
{t('Copy to clipboard')}
</Button>
</Tooltip>
</Grid>
<Grid item xs={12}>
<TextField
hiddenLabel
variant='standard'
size='small'
defaultValue={invoice}
disabled={true}
helperText={helperText}
color='secondary'
/>
</Grid>
</Grid>
);
};
export default LockInvoicePrompt;

View File

@ -0,0 +1,36 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Grid, Typography } from '@mui/material';
import { Order } from '../../../models';
import stepXofY from '../stepXofY';
interface TakerFoundPrompProps {
order: Order;
}
export const TakerFoundPrompt = ({ order }: TakerFoundPrompProps): JSX.Element => {
const { t } = useTranslation();
const Title = function () {
return (
<Typography color='primary' variant='subtitle1'>
<b>{t('A taker has been found!')}</b>
{` ${stepXofY(order)}`}
</Typography>
);
};
return (
<Grid container spacing={1}>
<Grid item>
<Typography variant='body2'>
{t(
'Please wait for the taker to lock a bond. If the taker does not lock a bond in time, the order will be made public again.',
)}
</Typography>
</Grid>
</Grid>
);
};
export default TakerFoundPrompt;

View File

@ -0,0 +1,2 @@
export { LockInvoicePrompt } from './LockInvoice';
export { TakerFoundPrompt } from './TakerFound';

View File

@ -0,0 +1,96 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Typography, useTheme } from '@mui/material';
import { Order } from '../../../models';
import stepXofY from '../stepXofY';
import currencies from '../../../../static/assets/currencies.json';
import { pn } from '../../../utils';
interface TakerFoundPrompProps {
order: Order;
}
export const Title = ({ order }: TakerFoundPrompProps): JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
const currencyCode: string = currencies[`${order.currency}`];
let text = '';
if (order.is_maker && order.status === 0) {
text = t('Lock {{amountSats}} Sats to PUBLISH order', { amountSats: pn(order.bond_satoshis) });
} else if (order.is_taker && order.status === 3) {
text = t('Lock {{amountSats}} Sats to TAKE order', { amountSats: pn(order.bond_satoshis) });
} else if (order.is_seller && [6, 7].includes(order.status)) {
text = t('Lock {{amountSats}} Sats as collateral', { amountSats: pn(order.escrow_satoshis) });
}
{
/* Maker and taker Bond request */
}
// {this.props.data.is_maker & (this.props.data.status == 0) ? this.showQRInvoice() : ''}
// {this.props.data.is_taker & (this.props.data.status == 3) ? this.showQRInvoice() : ''}
// {/* Waiting for taker and taker bond request */}
// {this.props.data.is_maker & (this.props.data.status == 2) ? this.showPausedOrder() : ''}
// {this.props.data.is_maker & (this.props.data.status == 1) ? this.showMakerWait() : ''}
// {this.props.data.is_maker & (this.props.data.status == 3) ? this.showTakerFound() : ''}
// {/* Send Invoice (buyer) and deposit collateral (seller) */}
// {this.props.data.is_seller &
// (this.props.data.status == 6 || this.props.data.status == 7)
// ? this.showEscrowQRInvoice()
// : ''}
// {this.props.data.is_buyer & (this.props.data.status == 6 || this.props.data.status == 8)
// ? this.showInputInvoice()
// : ''}
// {this.props.data.is_buyer & (this.props.data.status == 7)
// ? this.showWaitingForEscrow()
// : ''}
// {this.props.data.is_seller & (this.props.data.status == 8)
// ? this.showWaitingForBuyerInvoice()
// : ''}
// {/* In Chatroom */}
// {this.props.data.status == 9 || this.props.data.status == 10 ? this.showChat() : ''}
// {/* Trade Finished */}
// {this.props.data.is_seller & [13, 14, 15].includes(this.props.data.status)
// ? this.showRateSelect()
// : ''}
// {this.props.data.is_buyer & (this.props.data.status == 14) ? this.showRateSelect() : ''}
// {/* Trade Finished - Payment Routing Failed */}
// {this.props.data.is_buyer & (this.props.data.status == 13)
// ? this.showSendingPayment()
// : ''}
// {/* Trade Finished - Payment Routing Failed */}
// {this.props.data.is_buyer & (this.props.data.status == 15)
// ? this.showRoutingFailed()
// : ''}
// {/* Trade Finished - TODO Needs more planning */}
// {this.props.data.status == 11 ? this.showInDisputeStatement() : ''}
// {this.props.data.status == 16 ? this.showWaitForDisputeResolution() : ''}
// {(this.props.data.status == 17) & this.props.data.is_taker ||
// (this.props.data.status == 18) & this.props.data.is_maker
// ? this.showDisputeWinner()
// : ''}
// {(this.props.data.status == 18) & this.props.data.is_taker ||
// (this.props.data.status == 17) & this.props.data.is_maker
// ? this.showDisputeLoser()
// : ''}
// {/* Order has expired */}
// {this.props.data.status == 5 ? this.showOrderExpired() : ''}
return (
<Typography variant='body2'>
<b>{text}</b>
{stepXofY(order)}
</Typography>
);
};
export default Title;

View File

@ -0,0 +1,47 @@
import { Order } from '../../models';
const stepXofY = function (order: Order): string {
// set y value
let x: number | null = null;
let y: number | null = null;
if (order.is_maker) {
y = 5;
} else if (order.is_taker) {
y = 4;
}
// set x values
if (order.is_maker) {
if (order.status === 0) {
x = 1;
} else if ([1, 2, 3].includes(order.status)) {
x = 2;
} else if ([6, 7, 8].includes(order.status)) {
x = 3;
} else if (order.status === 9) {
x = 4;
} else if (order.status === 10) {
x = 5;
}
} else if (order.is_taker) {
if (order.status === 3) {
x = 1;
} else if ([6, 7, 8].includes(order.status)) {
x = 2;
} else if (order.status === 9) {
x = 3;
} else if (order.status === 10) {
x = 4;
}
}
// Return "(x/y)"
if (x != null && y != null) {
return `(${x}/${y})`;
} else {
return '';
}
};
export default stepXofY;

File diff suppressed because it is too large Load Diff

View File

@ -9,21 +9,10 @@ class UnsafeAlert extends Component {
super(props);
this.state = {
show: true,
isSelfhosted: this.isSelfhosted(),
};
}
isSelfhosted() {
const http = new XMLHttpRequest();
try {
http.open('HEAD', `${location.protocol}//${getHost()}/selfhosted`, false);
http.send();
return http.status === 200;
} catch {
return false;
}
}
// To do. Read from Coordinators state Obj.
safe_urls = [
'robosats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion',
'robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion',
@ -32,6 +21,26 @@ class UnsafeAlert extends Component {
'r7r4sckft6ptmk4r2jajiuqbowqyxiwsle4iyg4fijtoordc6z7a.b32.i2p',
];
checkClient() {
const http = new XMLHttpRequest();
const unsafeClient = !this.safe_urls.includes(getHost());
try {
http.open('HEAD', `${location.protocol}//${getHost()}/selfhosted`, false);
http.send();
this.props.setSettings({
...this.props.settings,
unsafeClient,
selfhostedClient: http.status === 200,
});
} catch {
this.props.setSettings({ ...this.props.settings, unsafeClient, selfhostedClient: false });
}
}
componentDidMount() {
this.checkClient();
}
render() {
const { t } = this.props;
@ -41,7 +50,7 @@ class UnsafeAlert extends Component {
}
// Show selfhosted notice
if (this.state.isSelfhosted) {
else if (this.props.settings.selfhostedClient) {
return (
<div>
<Paper elevation={6} className='alertUnsafe'>
@ -65,7 +74,7 @@ class UnsafeAlert extends Component {
}
// Show unsafe alert
if (!window.NativeRobosats && !this.safe_urls.includes(getHost())) {
else if (this.props.settings.unsafeClient) {
return (
<div>
<MediaQuery minWidth={800}>

View File

@ -1,4 +1,4 @@
export interface Order {
export interface PublicOrder {
id: number;
created_at: Date;
expires_at: Date;
@ -24,8 +24,8 @@ export interface Order {
}
export interface Book {
orders: Order[];
orders: PublicOrder[];
loading: boolean;
}
export default Order;
export default PublicOrder;

View File

@ -0,0 +1,23 @@
export interface WebSocketsChatMessage {
userNick: string;
validSignature: boolean;
plainTextMessage: string;
encryptedMessage: string;
time: Date;
index: number;
}
export interface APIChatMessage {
nick: string;
time: Date;
message: string;
index: number;
}
export interface APIChat {
peer_pubkey: string;
peer_connected: boolean;
messages: APIChatMessage[];
}
export default APIChat;

View File

@ -0,0 +1,100 @@
import { APIChat } from '.';
export interface TradeRobotSummary {
sent_fiat: number;
received_sats: number;
is_swap: boolean;
received_onchain_sats: number;
mining_fee_sats: number;
swap_fee_sats: number;
swap_fee_percent: number;
sent_sats: number;
received_fiat: number;
trade_fee_sats: number;
}
export interface TradeCoordinatorSummary {
contract_timestamp: Date;
contract_total_time: number;
routing_fee_sats: number;
trade_revenue_sats: number;
}
export interface Order {
id: number;
status: number;
created_at: Date;
expires_at: Date;
type: number;
currency: number;
amount: string;
has_range: boolean;
min_amount: string;
max_amount: string;
payment_method: string;
is_explicit: true;
premium: number;
satoshis: number;
bondless_taker: true;
maker: number;
taker: number;
escrow_duration: number;
total_secs_exp: number;
penalty: Date;
is_maker: boolean;
is_taker: boolean;
is_participant: boolean;
maker_status: 'Active' | 'Seen recently' | 'Inactive';
taker_status: 'Active' | 'Seen recently' | 'Inactive';
price_now: number;
premium_percentile: number;
num_similar_orders: number;
tg_enabled: boolean; // deprecated
tg_token: string;
tg_bot_name: string;
is_buyer: boolean;
is_seller: boolean;
maker_nick: string;
taker_nick: string;
status_message: string;
is_fiat_sent: boolean;
is_disputed: boolean;
ur_nick: string;
maker_locked: boolean;
taker_locked: boolean;
escrow_locked: boolean;
trade_satoshis: number;
bond_invoice: string;
bond_satoshis: number;
escrow_invoice: string;
escrow_satoshis: number;
invoice_amount: number;
swap_allowed: boolean;
swap_failure_reason: string;
suggested_mining_fee_rate: number;
swap_fee_rate: number;
pending_cancel: boolean;
asked_for_cancel: boolean;
statement_submitted: boolean;
retries: number;
next_retry_time: Date;
failure_reason: string;
invoice_expired: boolean;
public_duration: number;
bond_size: string;
trade_fee_percent: number;
bond_size_sats: number;
bond_size_percent: number;
chat: APIChat;
maker_summary: TradeRobotSummary;
taker_summary: TradeRobotSummary;
platform_summary: TradeCoordinatorSummary;
expiry_reason: number;
expiry_message: string;
num_satoshis: number;
sent_satoshis: number;
txid: string;
network: 'mainnet' | 'testnet';
}
export default Order;

View File

@ -1,8 +1,13 @@
import { baseSettings, Settings } from './Settings.model';
import { systemClient } from '../services/System';
import BaseSettings from './Settings.model';
export const defaultSettings: Settings = {
...baseSettings,
frontend: 'basic',
};
class Settings extends BaseSettings {
constructor() {
super();
const fontSizeCookie = systemClient.getCookie('settings_fontsize_basic');
this.fontSize = fontSizeCookie !== '' ? Number(fontSizeCookie) : 14;
}
public frontend: 'basic' | 'pro' = 'basic';
}
export default defaultSettings;
export default Settings;

View File

@ -1,9 +1,13 @@
import { baseSettings, Settings } from './Settings.model';
import { systemClient } from '../services/System';
import BaseSettings from './Settings.model';
export const defaultSettings: Settings = {
...baseSettings,
fontSize: 12,
frontend: 'pro',
};
class Settings extends BaseSettings {
constructor() {
super();
const fontSizeCookie = systemClient.getCookie('settings_fontsize_pro');
this.fontSize = fontSizeCookie !== '' ? Number(fontSizeCookie) : 12;
}
public frontend: 'basic' | 'pro' = 'pro';
}
export default defaultSettings;
export default Settings;

View File

@ -1,4 +1,5 @@
import i18n from '../i18n/Web';
import { systemClient } from '../services/System';
import type Coordinator from './Coordinator.model';
export type Language =
@ -19,28 +20,34 @@ export type Language =
| 'zh-SI'
| 'zh-TR';
export interface Settings {
frontend: 'basic' | 'pro';
mode: 'light' | 'dark';
fontSize: number;
language: Language;
freezeViewports: boolean;
network: 'mainnet' | 'testnet' | undefined;
coordinator: Coordinator | undefined;
class BaseSettings {
constructor() {
const modeCookie: 'light' | 'dark' | '' = systemClient.getCookie('settings_mode');
this.mode =
modeCookie !== ''
? modeCookie
: window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
const languageCookie = systemClient.getCookie('settings_language');
this.language =
languageCookie !== ''
? languageCookie
: i18n.resolvedLanguage == null
? 'en'
: i18n.resolvedLanguage.substring(0, 2);
}
public frontend: 'basic' | 'pro' = 'basic';
public mode: 'light' | 'dark' = 'light';
public fontSize: number = 14;
public language?: Language;
public freezeViewports: boolean = false;
public network: 'mainnet' | 'testnet' | undefined = 'mainnet';
public coordinator: Coordinator | undefined = undefined;
public unsafeClient: boolean = false;
public hostedClient: boolean = false;
}
export const baseSettings: Settings = {
frontend: 'basic',
mode:
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light',
fontSize: 14,
language:
i18n.resolvedLanguage == null ? 'en' : (i18n.resolvedLanguage.substring(0, 2) as Language),
freezeViewports: false,
network: undefined,
coordinator: undefined,
};
export default Settings;
export default BaseSettings;

View File

@ -1,17 +1,18 @@
import Robot from './Robot.model';
import Settings from './Settings.default.basic';
export { Robot, Settings };
export type { LimitList } from './Limit.model';
export type { Limit } from './Limit.model';
export type { Order } from './Book.model';
export type { Maker } from './Maker.model';
export type { Order } from './Order.model';
export type { PublicOrder } from './Book.model';
export type { Book } from './Book.model';
export type { Info } from './Info.model';
export type { Settings } from './Settings.model';
export type { Language } from './Settings.model';
export type { Favorites } from './Favorites.model';
export type { Coordinator } from './Coordinator.model';
export type { Maker } from './Maker.model';
export { Robot };
export type { APIChat, WebSocketsChatMessage, APIChatMessage } from './Chat.model';
export { defaultMaker } from './Maker.model';
export { defaultSettings } from './Settings.default.basic';
export { defaultInfo } from './Info.model';

View File

@ -22,8 +22,9 @@ class SystemNativeClient implements SystemClient {
});
};
public getCookie: (key: string) => string | undefined = (key) => {
return window.NativeRobosats?.cookies[key];
public getCookie: (key: string) => string = (key) => {
const cookie = window.NativeRobosats?.cookies[key];
return cookie === null || cookie === undefined ? '' : cookie;
};
public setCookie: (key: string, value: string) => void = (key, value) => {

View File

@ -27,7 +27,7 @@ class SystemWebClient implements SystemClient {
}
};
public getCookie: (key: string) => string | undefined = (key) => {
public getCookie: (key: string) => string = (key) => {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');

View File

@ -47,6 +47,9 @@ const App = () => {
EncryptedStorage.removeItem('csrftoken');
loadCookie('robot_token');
loadCookie('pub_key');
loadCookie('settings_fontsize_basic');
loadCookie('settings_language');
loadCookie('settings_mode');
loadCookie('enc_priv_key').then(() => injectMessageResolve(reponseId));
};