Fix order workflow (#957)

* Order workflow working

* Refactoring
This commit is contained in:
KoalaSat 2023-11-20 14:45:58 +00:00 committed by Reckless_Satoshi
parent 937ac62c5d
commit eb840c5b14
20 changed files with 312 additions and 385 deletions

View File

@ -1,4 +1,4 @@
import React, { useState, useContext, useEffect } from 'react';
import React, { useContext } from 'react';
import {
CommunityDialog,
ExchangeDialog,
@ -9,16 +9,16 @@ import {
ClientDialog,
UpdateDialog,
} from '../../components/Dialogs';
import { pn } from '../../utils';
import { AppContext, type UseAppStoreType, closeAll } from '../../contexts/AppContext';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { UseGarageStoreType, GarageContext } from '../../contexts/GarageContext';
export interface OpenDialogs {
more: boolean;
learn: boolean;
community: boolean;
info: boolean;
coordinator: boolean;
coordinator: string;
exchange: boolean;
client: boolean;
update: boolean;
@ -29,19 +29,8 @@ export interface OpenDialogs {
const MainDialogs = (): JSX.Element => {
const { open, setOpen, settings, clientVersion, hostUrl } =
useContext<UseAppStoreType>(AppContext);
const { federation, focusedCoordinator, coordinatorUpdatedAt } =
useContext<UseFederationStoreType>(FederationContext);
const [maxAmount, setMaxAmount] = useState<string>('...loading...');
useEffect(() => {
if (focusedCoordinator !== null && focusedCoordinator !== '') {
const limits = federation.getCoordinator(focusedCoordinator).limits;
if (limits[1000] !== undefined) {
setMaxAmount(pn(limits[1000].max_amount * 100000000));
}
}
}, [coordinatorUpdatedAt]);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
return (
<>
@ -56,7 +45,7 @@ const MainDialogs = (): JSX.Element => {
/>
<AboutDialog
open={open.info}
maxAmount={maxAmount}
maxAmount={'100000'} //FIXME About dialog shoul allow to change coordinator
onClose={() => {
setOpen((open) => {
return { ...open, info: false };
@ -103,16 +92,12 @@ const MainDialogs = (): JSX.Element => {
}}
/>
<CoordinatorDialog
open={open.coordinator}
open={Boolean(open.coordinator)}
network={settings.network}
onClose={() => {
setOpen(closeAll);
}}
coordinator={
focusedCoordinator !== null && focusedCoordinator !== ''
? federation.getCoordinator(focusedCoordinator)
: null
}
shortAlias={open.coordinator}
/>
</>
);

View File

@ -6,7 +6,6 @@ import { useNavigate, useParams } from 'react-router-dom';
import TradeBox from '../../components/TradeBox';
import OrderDetails from '../../components/OrderDetails';
import { apiClient } from '../../services/api';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
@ -15,8 +14,7 @@ import { type Order } from '../../models';
const OrderPage = (): JSX.Element => {
const { windowSize, setOpen, settings, navbarHeight, hostUrl, origin } =
useContext<UseAppStoreType>(AppContext);
const { setFocusedCoordinator, federation, focusedCoordinator } =
useContext<UseFederationStoreType>(FederationContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage, badOrder, setBadOrder } = useContext<UseGarageStoreType>(GarageContext);
const { t } = useTranslation();
const navigate = useNavigate();
@ -40,32 +38,18 @@ const OrderPage = (): JSX.Element => {
setBaseUrl(`${url}${basePath}`);
if (garage.getSlot().activeOrderId === Number(params.orderId)) {
if (garage.getSlot().order != null) {
setCurrentOrder(garage.getSlot().order);
} else {
coordinator
.fetchOrder(Number(params.orderId) ?? null, garage.getSlot().robot)
.then((order) => {
if (order?.bad_request !== undefined) {
setBadOrder(order.bad_request);
} else {
setCurrentOrder(order);
garage.updateOrder(order as Order);
}
})
.catch((e) => {
console.log(e);
});
}
} else {
if (currentOrder?.id !== Number(params.orderId)) {
const coordinator = federation.getCoordinator(params.shortAlias ?? '');
coordinator
.fetchOrder(Number(params.orderId) ?? null, garage.getSlot().robot)
.then((order) => {
if (order?.bad_request !== undefined) {
setBadOrder(order.bad_request);
} else {
} else if (order !== null && order?.id !== null) {
setCurrentOrder(order);
if (order.is_participant) {
garage.updateOrder(order as Order);
}
}
})
.catch((e) => {
@ -76,50 +60,9 @@ const OrderPage = (): JSX.Element => {
const onClickCoordinator = function (): void {
if (currentOrder?.shortAlias != null) {
setFocusedCoordinator(currentOrder.shortAlias);
}
setOpen((open) => {
return { ...open, coordinator: true };
});
};
const renewOrder = function (): void {
const order = currentOrder;
if (order !== null && focusedCoordinator != null) {
const body = {
type: order.type,
currency: order.currency,
amount: order.has_range ? null : order.amount,
has_range: order.has_range,
min_amount: order.min_amount,
max_amount: order.max_amount,
payment_method: order.payment_method,
is_explicit: order.is_explicit,
premium: order.is_explicit ? null : order.premium,
satoshis: order.is_explicit ? order.satoshis : null,
public_duration: order.public_duration,
escrow_duration: order.escrow_duration,
bond_size: order.bond_size,
latitude: order.latitude,
longitude: order.longitude,
};
const { url, basePath } = federation
.getCoordinator(order.shortAlias)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
apiClient
.post(url + basePath, '/api/make/', body, {
tokenSHA256: garage.getSlot().robot.tokenSHA256,
})
.then((data: any) => {
if (data.bad_request !== undefined) {
setBadOrder(data.bad_request);
} else if (data.id !== undefined) {
navigate(`/order/${String(currentOrder?.shortAlias)}/${String(data.id)}`);
}
})
.catch(() => {
setBadOrder('Request error');
});
setOpen((open) => {
return { ...open, coordinator: shortAlias };
});
}
};
@ -127,6 +70,26 @@ const OrderPage = (): JSX.Element => {
navigate('/robot');
};
const orderDetailsSpace = currentOrder ? (
<OrderDetails
shortAlias={String(currentOrder.shortAlias)}
currentOrder={currentOrder}
onClickCoordinator={onClickCoordinator}
baseUrl={baseUrl}
onClickGenerateRobot={() => {
navigate('/robot');
}}
/>
) : (
<></>
);
const tradeBoxSpace = currentOrder ? (
<TradeBox baseUrl={baseUrl} onStartAgain={startAgain} />
) : (
<></>
);
return (
<Box>
{currentOrder === null && badOrder === undefined && <CircularProgress />}
@ -156,15 +119,7 @@ const OrderPage = (): JSX.Element => {
overflow: 'auto',
}}
>
<OrderDetails
shortAlias={String(currentOrder.shortAlias)}
currentOrder={currentOrder}
onClickCoordinator={onClickCoordinator}
baseUrl={baseUrl}
onClickGenerateRobot={() => {
navigate('/robot');
}}
/>
{orderDetailsSpace}
</Paper>
</Grid>
<Grid item xs={6} style={{ width: '21em' }}>
@ -176,15 +131,7 @@ const OrderPage = (): JSX.Element => {
overflow: 'auto',
}}
>
<TradeBox
robot={garage.getSlot().robot}
currentOrder={currentOrder}
settings={settings}
setBadOrder={setBadOrder}
baseUrl={baseUrl}
onRenewOrder={renewOrder}
onStartAgain={startAgain}
/>
{tradeBoxSpace}
</Paper>
</Grid>
</Grid>
@ -194,7 +141,7 @@ const OrderPage = (): JSX.Element => {
<Box sx={{ borderBottom: 1, borderColor: 'divider', width: '21em' }}>
<Tabs
value={tab}
onChange={(mouseEvent, value) => {
onChange={(_mouseEvent, value) => {
setTab(value);
}}
variant='fullWidth'
@ -211,28 +158,8 @@ const OrderPage = (): JSX.Element => {
overflow: 'auto',
}}
>
<div style={{ display: tab === 'order' ? '' : 'none' }}>
<OrderDetails
shortAlias={String(currentOrder.shortAlias)}
currentOrder={currentOrder}
onClickCoordinator={onClickCoordinator}
baseUrl={baseUrl}
onClickGenerateRobot={() => {
navigate('/robot');
}}
/>
</div>
<div style={{ display: tab === 'contract' ? '' : 'none' }}>
<TradeBox
robot={garage.getSlot().robot}
currentOrder={currentOrder}
settings={settings}
setBadOrder={setBadOrder}
baseUrl={baseUrl}
onRenewOrder={renewOrder}
onStartAgain={startAgain}
/>
</div>
<div style={{ display: tab === 'order' ? '' : 'none' }}>{orderDetailsSpace}</div>
<div style={{ display: tab === 'contract' ? '' : 'none' }}>{tradeBoxSpace}</div>
</Paper>
</Box>
)
@ -245,15 +172,7 @@ const OrderPage = (): JSX.Element => {
overflow: 'auto',
}}
>
<OrderDetails
shortAlias={String(currentOrder.shortAlias)}
currentOrder={currentOrder}
onClickCoordinator={onClickCoordinator}
baseUrl={hostUrl}
onClickGenerateRobot={() => {
navigate('/robot');
}}
/>
{orderDetailsSpace}
</Paper>
)
) : (

View File

@ -25,14 +25,7 @@ const SettingsPage = (): JSX.Element => {
<SettingsForm showNetwork={!(window.NativeRobosats === undefined)} />
</Grid>
<Grid item>
<FederationTable
openCoordinator={() => {
setOpen({ ...open, coordinator: true });
}}
baseUrl={hostUrl}
maxHeight={14}
network={settings.network}
/>
<FederationTable baseUrl={hostUrl} maxHeight={14} network={settings.network} />
</Grid>
</Grid>
</Paper>

View File

@ -108,7 +108,7 @@ const BookTable = ({
}: BookTableProps): JSX.Element => {
const { fav, setFav, settings, setOpen, hostUrl, origin } =
useContext<UseAppStoreType>(AppContext);
const { federation, setFocusedCoordinator, coordinatorUpdatedAt } =
const { federation, coordinatorUpdatedAt } =
useContext<UseFederationStoreType>(FederationContext);
const { t } = useTranslation();
@ -273,9 +273,8 @@ const BookTable = ({
}, []);
const onClickCoordinator = function (shortAlias: string): void {
setFocusedCoordinator(shortAlias);
setOpen((open) => {
return { ...open, coordinator: true };
return { ...open, coordinator: shortAlias };
});
};

View File

@ -62,11 +62,12 @@ import {
import { AppContext } from '../../contexts/AppContext';
import { systemClient } from '../../services/System';
import { type Badges } from '../../models/Coordinator.model';
import { UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
interface Props {
open: boolean;
onClose: () => void;
coordinator: Coordinator | null;
shortAlias: string | null;
network: 'mainnet' | 'testnet' | undefined;
}
@ -335,9 +336,11 @@ const BadgesHall = ({ badges }: BadgesProps): JSX.Element => {
);
};
const CoordinatorDialog = ({ open = false, onClose, coordinator, network }: Props): JSX.Element => {
const CoordinatorDialog = ({ open = false, onClose, network, shortAlias }: Props): JSX.Element => {
const { t } = useTranslation();
const { clientVersion, page, hostUrl } = useContext(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const coordinator = federation.getCoordinator(shortAlias);
const [expanded, setExpanded] = useState<'summary' | 'stats' | 'policies' | undefined>(undefined);

View File

@ -9,22 +9,20 @@ import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
interface FederationTableProps {
openCoordinator: () => void;
maxWidth?: number;
maxHeight?: number;
fillContainer?: boolean;
}
const FederationTable = ({
openCoordinator,
maxWidth = 90,
maxHeight = 50,
fillContainer = false,
}: FederationTableProps): JSX.Element => {
const { t } = useTranslation();
const { federation, sortedCoordinators, setFocusedCoordinator, coordinatorUpdatedAt } =
const { federation, sortedCoordinators, coordinatorUpdatedAt } =
useContext<UseFederationStoreType>(FederationContext);
const { hostUrl } = useContext<UseAppStoreType>(AppContext);
const { hostUrl, setOpen } = useContext<UseAppStoreType>(AppContext);
const theme = useTheme();
const [pageSize, setPageSize] = useState<number>(0);
@ -52,8 +50,9 @@ const FederationTable = ({
};
const onClickCoordinator = function (shortAlias: string): void {
setFocusedCoordinator(shortAlias);
openCoordinator();
setOpen((open) => {
return { ...open, coordinator: shortAlias };
});
};
const aliasObj = useCallback((width: number) => {

View File

@ -68,7 +68,7 @@ const MakerForm = ({
onClickGenerateRobot = () => null,
}: MakerFormProps): JSX.Element => {
const { fav, setFav, settings, hostUrl, origin } = useContext<UseAppStoreType>(AppContext);
const { federation, focusedCoordinator, coordinatorUpdatedAt, federationUpdatedAt } =
const { federation, coordinatorUpdatedAt, federationUpdatedAt } =
useContext<UseFederationStoreType>(FederationContext);
const { maker, setMaker, garage } = useContext<UseGarageStoreType>(GarageContext);
@ -93,8 +93,8 @@ const MakerForm = ({
useEffect(() => {
setCurrencyCode(currencyDict[fav.currency === 0 ? 1 : fav.currency]);
if (focusedCoordinator != null) {
const newLimits = federation.getCoordinator(focusedCoordinator).limits;
if (maker.coordinator != null) {
const newLimits = federation.getCoordinator(maker.coordinator).limits;
if (Object.keys(newLimits).length !== 0) {
updateAmountLimits(newLimits, fav.currency, maker.premium);
updateCurrentPrice(newLimits, fav.currency, maker.premium);
@ -285,15 +285,9 @@ const MakerForm = ({
.getCoordinator(maker.coordinator)
?.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl) ?? {};
const auth = {
tokenSHA256: garage.getSlot().robot.tokenSHA256,
keys: {
pubKey: garage.getSlot().robot.pubKey?.split('\n').join('\\'),
encPrivKey: garage.getSlot().robot.encPrivKey?.split('\n').join('\\'),
},
};
const auth = garage.getSlot().robot.getAuthHeaders();
if (!disableRequest && focusedCoordinator != null) {
if (!disableRequest && maker.coordinator != null && auth !== null) {
setSubmittingRequest(true);
const body = {
type: fav.type === 0 ? 1 : 0,
@ -320,6 +314,7 @@ const MakerForm = ({
setBadRequest(data.bad_request);
if (data.id !== undefined) {
onOrderCreated(maker.coordinator, data.id);
garage.updateOrder(data);
}
setSubmittingRequest(false);
})
@ -446,9 +441,9 @@ const MakerForm = ({
};
const amountLabel = useMemo(() => {
if (!(focusedCoordinator != null)) return;
if (!(maker.coordinator != null)) return;
const info = federation.getCoordinator(focusedCoordinator)?.info;
const info = federation.getCoordinator(maker.coordinator)?.info;
const defaultRoutingBudget = 0.001;
let label = t('Amount');
let helper = '';

View File

@ -22,15 +22,13 @@ interface SelectCoordinatorProps {
const SelectCoordinator: React.FC<SelectCoordinatorProps> = ({ coordinator, setCoordinator }) => {
const { setOpen, hostUrl } = useContext<UseAppStoreType>(AppContext);
const { federation, setFocusedCoordinator, sortedCoordinators } =
useContext<UseFederationStoreType>(FederationContext);
const { federation, sortedCoordinators } = useContext<UseFederationStoreType>(FederationContext);
const theme = useTheme();
const { t } = useTranslation();
const onClickCurrentCoordinator = function (shortAlias: string): void {
setFocusedCoordinator(shortAlias);
setOpen((open) => {
return { ...open, coordinator: true };
return { ...open, coordinator: shortAlias };
});
};

View File

@ -47,9 +47,6 @@ const RobotAvatar: React.FC<Props> = ({
coordinator = false,
baseUrl,
}) => {
const { settings, origin, hostUrl } = useContext<UseAppStoreType>(AppContext);
const { federation, focusedCoordinator } = useContext<UseFederationStoreType>(FederationContext);
const [avatarSrc, setAvatarSrc] = useState<string>();
const [nicknameReady, setNicknameReady] = useState<boolean>(false);
const [activeBackground, setActiveBackground] = useState<boolean>(true);
@ -66,13 +63,10 @@ const RobotAvatar: React.FC<Props> = ({
if (window.NativeRobosats === undefined) {
setAvatarSrc(`${baseUrl}${path}${nickname}${small ? '.small' : ''}.webp`);
setNicknameReady(true);
} else if (focusedCoordinator != null) {
} else if (baseUrl != null && apiClient.fileImageUrl !== undefined) {
setNicknameReady(true);
const { url, basePath } = federation
.getCoordinator(focusedCoordinator)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
void apiClient
.fileImageUrl(url + basePath, `${path}${nickname}${small ? '.small' : ''}.webp`)
.fileImageUrl(baseUrl, `${path}${nickname}${small ? '.small' : ''}.webp`)
.then(setAvatarSrc);
}
} else {

View File

@ -25,7 +25,6 @@ const audioPath =
interface Props {
orderId: number;
status: number;
robot: Robot;
userNick: string;
takerNick: string;
messages: EncryptedChatMessage[];
@ -38,7 +37,6 @@ interface Props {
const EncryptedSocketChat: React.FC<Props> = ({
orderId,
status,
robot,
userNick,
takerNick,
messages,
@ -110,7 +108,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
setConnected(true);
connection.send({
message: robot.pubKey,
message: garage.getSlot().robot.pubKey,
nick: userNick,
});
@ -132,10 +130,10 @@ const EncryptedSocketChat: React.FC<Props> = ({
const createJsonFile: () => object = () => {
return {
credentials: {
own_public_key: robot.pubKey,
own_public_key: garage.getSlot().robot.pubKey,
peer_public_key: peerPubKey,
encrypted_private_key: robot.encPrivKey,
passphrase: robot.token,
encrypted_private_key: garage.getSlot().robot.encPrivKey,
passphrase: garage.getSlot().robot.token,
},
messages,
};
@ -143,7 +141,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
const onMessage: (message: any) => void = (message) => {
const dataFromServer = JSON.parse(message.data);
const robot = garage.getSlot().robot;
if (dataFromServer != null && !receivedIndexes.includes(dataFromServer.index)) {
setReceivedIndexes((prev) => [...prev, dataFromServer.index]);
setPeerConnected(dataFromServer.peer_connected);
@ -213,6 +211,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
};
const onButtonClicked = (e: React.FormEvent<HTMLFormElement>): void => {
const robot = garage.getSlot().robot;
if (robot.token !== undefined && value.includes(robot.token)) {
alert(
`Aye! You just sent your own robot robot.token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`,
@ -264,10 +263,10 @@ const EncryptedSocketChat: React.FC<Props> = ({
}}
orderId={Number(orderId)}
messages={messages}
ownPubKey={robot.pubKey ?? ''}
ownEncPrivKey={robot.encPrivKey ?? ''}
ownPubKey={garage.getSlot().robot.pubKey ?? ''}
ownEncPrivKey={garage.getSlot().robot.encPrivKey ?? ''}
peerPubKey={peerPubKey ?? 'Not received yet'}
passphrase={robot.token ?? ''}
passphrase={garage.getSlot().robot.token ?? ''}
onClickBack={() => {
setAudit(false);
}}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, TextField, Grid, Paper, Typography } from '@mui/material';
import { encryptMessage, decryptMessage } from '../../../../pgp';
@ -14,10 +14,12 @@ import ChatHeader from '../ChatHeader';
import { type EncryptedChatMessage, type ServerMessage } from '..';
import { apiClient } from '../../../../services/api';
import ChatBottom from '../ChatBottom';
import { UseAppStoreType, AppContext } from '../../../../contexts/AppContext';
import { UseFederationStoreType, FederationContext } from '../../../../contexts/FederationContext';
import { UseGarageStoreType, GarageContext } from '../../../../contexts/GarageContext';
interface Props {
orderId: number;
robot: Robot;
userNick: string;
takerNick: string;
chatOffset: number;
@ -35,7 +37,6 @@ const audioPath =
const EncryptedTurtleChat: React.FC<Props> = ({
orderId,
robot,
userNick,
takerNick,
chatOffset,
@ -48,7 +49,8 @@ const EncryptedTurtleChat: React.FC<Props> = ({
const { t } = useTranslation();
const theme = useTheme();
const { origin, hostUrl, settings } = useContext<UseAppStoreType>(AppContext);
const { federation, focusedCoordinator } = useContext<UseFederationStoreType>(FederationContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const [audio] = useState(() => new Audio(`${audioPath}/chat-open.mp3`));
const [peerConnected, setPeerConnected] = useState<boolean>(false);
@ -83,11 +85,11 @@ const EncryptedTurtleChat: React.FC<Props> = ({
const loadMessages: () => void = () => {
const { url, basePath } = federation
.getCoordinator(focusedCoordinator)
.getCoordinator(garage.getSlot().activeOrderShortAlias)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
apiClient
.get(url + basePath, `/api/chat/?order_id=${orderId}&offset=${lastIndex}`, {
tokenSHA256: robot.tokenSHA256,
tokenSHA256: garage.getSlot().robot.tokenSHA256,
})
.then((results: any) => {
if (results != null) {
@ -104,16 +106,17 @@ const EncryptedTurtleChat: React.FC<Props> = ({
const createJsonFile = (): object => {
return {
credentials: {
own_public_key: robot.pubKey,
own_public_key: garage.getSlot().robot.pubKey,
peer_public_key: peerPubKey,
encrypted_private_key: robot.encPrivKey,
passphrase: robot.token,
encrypted_private_key: garage.getSlot().robot.encPrivKey,
passphrase: garage.getSlot().robot.token,
},
messages,
};
};
const onMessage = (dataFromServer: ServerMessage): void => {
const robot = garage.getSlot().robot;
if (dataFromServer != null) {
// If we receive an encrypted message
if (dataFromServer.message.substring(0, 27) === `-----BEGIN PGP MESSAGE-----`) {
@ -169,6 +172,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
};
const onButtonClicked = (e: React.FormEvent<HTMLFormElement>): void => {
const robot = garage.getSlot().robot;
if (robot.token !== undefined && value.includes(robot.token)) {
alert(
`Aye! You just sent your own robot robot.token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`,
@ -178,7 +182,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
// If input string contains '#' send unencrypted and unlogged message
else if (value.substring(0, 1) === '#') {
const { url, basePath } = federation
.getCoordinator(focusedCoordinator)
.getCoordinator(garage.getSlot().activeOrderShortAlias ?? '')
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
apiClient
.post(
@ -211,7 +215,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
encryptMessage(value, robot.pubKey, peerPubKey, robot.encPrivKey, robot.token)
.then((encryptedMessage) => {
const { url, basePath } = federation
.getCoordinator(focusedCoordinator)
.getCoordinator(garage.getSlot().activeOrderShortAlias ?? '')
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
apiClient
.post(
@ -259,10 +263,10 @@ const EncryptedTurtleChat: React.FC<Props> = ({
}}
orderId={Number(orderId)}
messages={messages}
ownPubKey={robot.pubKey ?? ''}
ownEncPrivKey={robot.encPrivKey ?? ''}
ownPubKey={garage.getSlot().robot.pubKey ?? ''}
ownEncPrivKey={garage.getSlot().robot.encPrivKey ?? ''}
peerPubKey={peerPubKey ?? 'Not received yet'}
passphrase={robot.token ?? ''}
passphrase={garage.getSlot().robot.token ?? ''}
onClickBack={() => {
setAudit(false);
}}

View File

@ -35,7 +35,6 @@ export interface ServerMessage {
const EncryptedChat: React.FC<Props> = ({
orderId,
takerNick,
robot,
userNick,
chatOffset,
baseUrl,
@ -48,7 +47,6 @@ const EncryptedChat: React.FC<Props> = ({
return turtleMode ? (
<EncryptedTurtleChat
messages={messages}
robot={robot}
setMessages={setMessages}
orderId={orderId}
takerNick={takerNick}
@ -62,7 +60,6 @@ const EncryptedChat: React.FC<Props> = ({
<EncryptedSocketChat
status={status}
messages={messages}
robot={robot}
setMessages={setMessages}
orderId={orderId}
takerNick={takerNick}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Grid, Typography, Tooltip, Collapse } from '@mui/material';
import currencies from '../../../../static/assets/currencies.json';
@ -8,10 +8,10 @@ import { pn } from '../../../utils';
import EncryptedChat, { type EncryptedChatMessage } from '../EncryptedChat';
import Countdown, { zeroPad } from 'react-countdown';
import { LoadingButton } from '@mui/lab';
import { UseGarageStoreType, GarageContext } from '../../../contexts/GarageContext';
interface ChatPromptProps {
order: Order;
robot: Robot;
onClickConfirmSent: () => void;
onClickUndoConfirmSent: () => void;
loadingSent: boolean;
@ -27,7 +27,6 @@ interface ChatPromptProps {
export const ChatPrompt = ({
order,
robot,
onClickConfirmSent,
onClickUndoConfirmSent,
onClickConfirmReceived,
@ -41,6 +40,7 @@ export const ChatPrompt = ({
setMessages,
}: ChatPromptProps): JSX.Element => {
const { t } = useTranslation();
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const [sentButton, setSentButton] = useState<boolean>(false);
const [receivedButton, setReceivedButton] = useState<boolean>(false);
@ -49,9 +49,9 @@ export const ChatPrompt = ({
const [enableDisputeTime, setEnableDisputeTime] = useState<Date>(new Date(order.expires_at));
const [text, setText] = useState<string>('');
const currencyCode: string = currencies[`${order.currency}`];
const currencyCode: string = currencies[`${garage.getSlot().order.currency}`];
const amount: string = pn(
parseFloat(parseFloat(order.amount).toFixed(order.currency === 1000 ? 8 : 4)),
parseFloat(parseFloat(garage.getSlot().order.amount).toFixed(order.currency === 1000 ? 8 : 4)),
);
const disputeCountdownRenderer = function ({
@ -133,7 +133,6 @@ export const ChatPrompt = ({
<Grid item>
<EncryptedChat
status={order.status}
robot={robot}
chatOffset={order.chat_last_index}
orderId={order.id}
takerNick={order.taker_nick}

View File

@ -45,14 +45,15 @@ import {
defaultDispute,
} from './Forms';
import { type Order, type Robot, type Settings } from '../../models';
import { type Order } from '../../models';
import { type EncryptedChatMessage } from './EncryptedChat';
import CollabCancelAlert from './CollabCancelAlert';
import { Bolt } from '@mui/icons-material';
import { signCleartextMessage } from '../../pgp';
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
import { type UseGarageStoreType, GarageContext } from '../../contexts/GarageContext';
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
import { useNavigate } from 'react-router-dom';
interface loadingButtonsProps {
cancel: boolean;
@ -101,27 +102,24 @@ const closeAll: OpenDialogProps = {
};
interface TradeBoxProps {
robot: Robot;
currentOrder: Order;
setBadOrder: (state: string | undefined) => void;
onRenewOrder: () => void;
onStartAgain: () => void;
settings: Settings;
baseUrl: string;
onStartAgain: () => void;
}
const TradeBox = ({
robot,
currentOrder,
settings,
baseUrl,
setBadOrder,
onRenewOrder,
onStartAgain,
}: TradeBoxProps): JSX.Element => {
interface Contract {
title: string;
titleVariables: object;
titleColor: string;
prompt: () => JSX.Element;
bondStatus: 'hide' | 'locked' | 'unlocked' | 'settled';
titleIcon: () => JSX.Element;
}
const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
const { garage, orderUpdatedAt, setBadOrder } = useContext<UseGarageStoreType>(GarageContext);
const { settings, hostUrl, origin } = useContext<UseAppStoreType>(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
const { origin, hostUrl } = useContext<UseAppStoreType>(AppContext);
const navigate = useNavigate();
// Buttons and Dialogs
const [loadingButtons, setLoadingButtons] = useState<loadingButtonsProps>(noLoadingButtons);
@ -156,6 +154,46 @@ const TradeBox = ({
rating?: number;
}
const renewOrder = function (): void {
const currentOrder = garage.getSlot().order;
if (currentOrder !== null) {
const body = {
type: currentOrder.type,
currency: currentOrder.currency,
amount: currentOrder.has_range ? null : currentOrder.amount,
has_range: currentOrder.has_range,
min_amount: currentOrder.min_amount,
max_amount: currentOrder.max_amount,
payment_method: currentOrder.payment_method,
is_explicit: currentOrder.is_explicit,
premium: currentOrder.is_explicit ? null : currentOrder.premium,
satoshis: currentOrder.is_explicit ? currentOrder.satoshis : null,
public_duration: currentOrder.public_duration,
escrow_duration: currentOrder.escrow_duration,
bond_size: currentOrder.bond_size,
latitude: currentOrder.latitude,
longitude: currentOrder.longitude,
};
const { url, basePath } = federation
.getCoordinator(currentOrder.shortAlias)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
apiClient
.post(url + basePath, '/api/make/', body, {
tokenSHA256: garage.getSlot().robot.tokenSHA256,
})
.then((data: any) => {
if (data.bad_request !== undefined) {
setBadOrder(data.bad_request);
} else if (data.id !== undefined) {
navigate(`/order/${String(currentOrder?.shortAlias)}/${String(data.id)}`);
}
})
.catch(() => {
setBadOrder('Request error');
});
}
};
const submitAction = function ({
action,
invoice,
@ -165,13 +203,13 @@ const TradeBox = ({
statement,
rating,
}: SubmitActionProps): void {
const { url, basePath } = federation
.getCoordinator(currentOrder.shortAlias)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
const robot = garage.getSlot().robot;
const currentOrder = garage.getSlot().order;
void apiClient
.post(
url + basePath,
`/api/order/?order_id=${Number(currentOrder.id)}`,
baseUrl,
`/api/order/?order_id=${Number(currentOrder?.id)}`,
{
action,
invoice,
@ -268,6 +306,7 @@ const TradeBox = ({
};
const submitStatement = function (): void {
const robot = garage.getSlot().robot;
let statement = dispute.statement;
if (dispute.attachLogs) {
const payload = { statement, messages, token: robot.token };
@ -322,44 +361,48 @@ const TradeBox = ({
// Effect on Order Status change (used for WebLN)
useEffect(() => {
const currentOrder = garage.getSlot().order;
if (currentOrder !== null && currentOrder.status !== lastOrderStatus) {
setLastOrderStatus(currentOrder.status);
void handleWebln(currentOrder);
}
// FIXME this should trigger with current order, not garage order
}, [orderUpdatedAt]);
const statusToContract = function (order: Order): JSX.Element {
const statusToContract = function (): Contract {
const order = garage.getSlot().order;
const baseContract: Contract = {
title: 'Unknown Order Status',
titleVariables: {},
titleColor: 'primary',
prompt: () => <span>Wops!</span>,
bondStatus: 'hide',
titleIcon: () => <></>,
};
if (order === null) return baseContract;
const status = order.status;
const isBuyer = order.is_buyer;
const isMaker = order.is_maker;
let title: string = 'Unknown Order Status';
let titleVariables: object = {};
let titleColor: string = 'primary';
let titleIcon: () => JSX.Element = function () {
return <></>;
};
let prompt = (): JSX.Element => <span>Wops!</span>;
let bondStatus: 'hide' | 'locked' | 'unlocked' | 'settled' = 'hide';
switch (status) {
// 0: 'Waiting for maker bond'
case 0:
if (isMaker) {
title = 'Lock {{amountSats}} Sats to PUBLISH order';
titleVariables = { amountSats: pn(order.bond_satoshis) };
prompt = () => {
baseContract.title = 'Lock {{amountSats}} Sats to PUBLISH order';
baseContract.titleVariables = { amountSats: pn(order.bond_satoshis) };
baseContract.prompt = () => {
return <LockInvoicePrompt order={order} concept={'bond'} />;
};
bondStatus = 'hide';
baseContract.bondStatus = 'hide';
}
break;
// 1: 'Public'
case 1:
if (isMaker) {
title = 'Your order is public';
prompt = () => {
baseContract.title = 'Your order is public';
baseContract.prompt = () => {
return (
<PublicWaitPrompt
order={order}
@ -368,14 +411,14 @@ const TradeBox = ({
/>
);
};
bondStatus = 'locked';
baseContract.bondStatus = 'locked';
}
break;
// 2: 'Paused'
case 2:
if (isMaker) {
title = 'Your order is paused';
prompt = () => {
baseContract.title = 'Your order is paused';
baseContract.prompt = () => {
return (
<PausedPrompt
pauseLoading={loadingButtons.pauseOrder}
@ -383,53 +426,53 @@ const TradeBox = ({
/>
);
};
bondStatus = 'locked';
baseContract.bondStatus = 'locked';
}
break;
// 3: 'Waiting for taker bond'
case 3:
if (isMaker) {
title = 'A taker has been found!';
prompt = () => {
baseContract.title = 'A taker has been found!';
baseContract.prompt = () => {
return <TakerFoundPrompt />;
};
bondStatus = 'locked';
baseContract.bondStatus = 'locked';
} else {
title = 'Lock {{amountSats}} Sats to TAKE order';
titleVariables = { amountSats: pn(order.bond_satoshis) };
prompt = () => {
baseContract.title = 'Lock {{amountSats}} Sats to TAKE order';
baseContract.titleVariables = { amountSats: pn(order.bond_satoshis) };
baseContract.prompt = () => {
return <LockInvoicePrompt order={order} concept={'bond'} />;
};
bondStatus = 'hide';
baseContract.bondStatus = 'hide';
}
break;
// 5: 'Expired'
case 5:
title = 'The order has expired';
prompt = () => {
baseContract.title = 'The order has expired';
baseContract.prompt = () => {
return (
<ExpiredPrompt
loadingRenew={loadingButtons.renewOrder}
order={order}
onClickRenew={() => {
onRenewOrder();
renewOrder();
setLoadingButtons({ ...noLoadingButtons, renewOrder: true });
}}
/>
);
};
bondStatus = 'hide'; // To do: show bond status according to expiry message.
baseContract.bondStatus = 'hide'; // To do: show bond status according to expiry message.
break;
// 6: 'Waiting for trade collateral and buyer invoice'
case 6:
bondStatus = 'locked';
baseContract.bondStatus = 'locked';
if (isBuyer) {
title = 'Submit payout info';
titleVariables = { amountSats: pn(order.invoice_amount) };
prompt = function () {
baseContract.title = 'Submit payout info';
baseContract.titleVariables = { amountSats: pn(order.invoice_amount) };
baseContract.prompt = function () {
return (
<PayoutPrompt
order={order}
@ -446,10 +489,10 @@ const TradeBox = ({
);
};
} else {
title = 'Lock {{amountSats}} Sats as collateral';
titleVariables = { amountSats: pn(order.escrow_satoshis) };
titleColor = 'warning';
prompt = () => {
baseContract.title = 'Lock {{amountSats}} Sats as collateral';
baseContract.titleVariables = { amountSats: pn(order.escrow_satoshis) };
baseContract.titleColor = 'warning';
baseContract.prompt = () => {
return <LockInvoicePrompt order={order} concept={'escrow'} />;
};
}
@ -457,17 +500,17 @@ const TradeBox = ({
// 7: 'Waiting only for seller trade collateral'
case 7:
bondStatus = 'locked';
baseContract.bondStatus = 'locked';
if (isBuyer) {
title = 'Your info looks good!';
prompt = () => {
baseContract.title = 'Your info looks good!';
baseContract.prompt = () => {
return <EscrowWaitPrompt />;
};
} else {
title = 'Lock {{amountSats}} Sats as collateral';
titleVariables = { amountSats: pn(order.escrow_satoshis) };
titleColor = 'warning';
prompt = () => {
baseContract.title = 'Lock {{amountSats}} Sats as collateral';
baseContract.titleVariables = { amountSats: pn(order.escrow_satoshis) };
baseContract.titleColor = 'warning';
baseContract.prompt = () => {
return <LockInvoicePrompt order={order} concept={'escrow'} />;
};
}
@ -475,11 +518,11 @@ const TradeBox = ({
// 8: 'Waiting only for buyer invoice'
case 8:
bondStatus = 'locked';
baseContract.bondStatus = 'locked';
if (isBuyer) {
title = 'Submit payout info';
titleVariables = { amountSats: pn(order.invoice_amount) };
prompt = () => {
baseContract.title = 'Submit payout info';
baseContract.titleVariables = { amountSats: pn(order.invoice_amount) };
baseContract.prompt = () => {
return (
<PayoutPrompt
order={order}
@ -496,8 +539,8 @@ const TradeBox = ({
);
};
} else {
title = 'The trade collateral is locked!';
prompt = () => {
baseContract.title = 'The trade collateral is locked!';
baseContract.prompt = () => {
return <PayoutWaitPrompt />;
};
}
@ -507,12 +550,11 @@ const TradeBox = ({
// 10: 'Fiat sent - In chatroom'
case 9:
case 10:
title = isBuyer ? 'Chat with the seller' : 'Chat with the buyer';
prompt = function () {
baseContract.title = isBuyer ? 'Chat with the seller' : 'Chat with the buyer';
baseContract.prompt = function () {
return (
<ChatPrompt
order={order}
robot={robot}
onClickConfirmSent={() => {
setOpen({ ...open, confirmFiatSent: true });
}}
@ -535,20 +577,20 @@ const TradeBox = ({
/>
);
};
bondStatus = 'locked';
baseContract.bondStatus = 'locked';
break;
// 11: 'In dispute'
case 11:
bondStatus = 'settled';
baseContract.bondStatus = 'settled';
if (order.statement_submitted) {
title = 'We have received your statement';
prompt = function () {
baseContract.title = 'We have received your statement';
baseContract.prompt = function () {
return <DisputeWaitPeerPrompt />;
};
} else {
title = 'A dispute has been opened';
prompt = function () {
baseContract.title = 'A dispute has been opened';
baseContract.prompt = function () {
return (
<DisputePrompt
loading={loadingButtons.submitStatement}
@ -567,18 +609,18 @@ const TradeBox = ({
// 13: 'Sending satoshis to buyer'
case 13:
if (isBuyer) {
bondStatus = 'unlocked';
title = 'Attempting Lightning Payment';
prompt = function () {
baseContract.bondStatus = 'unlocked';
baseContract.title = 'Attempting Lightning Payment';
baseContract.prompt = function () {
return <SendingSatsPrompt />;
};
} else {
title = 'Trade finished!';
titleColor = 'success';
titleIcon = function () {
baseContract.title = 'Trade finished!';
baseContract.titleColor = 'success';
baseContract.titleIcon = function () {
return <Bolt xs={{ width: '1em', height: '1em' }} color='warning' />;
};
prompt = function () {
baseContract.prompt = function () {
return (
<SuccessfulPrompt
baseUrl={baseUrl}
@ -587,7 +629,7 @@ const TradeBox = ({
onClickStartAgain={onStartAgain}
loadingRenew={loadingButtons.renewOrder}
onClickRenew={() => {
onRenewOrder();
renewOrder();
setLoadingButtons({ ...noLoadingButtons, renewOrder: true });
}}
/>
@ -598,12 +640,12 @@ const TradeBox = ({
// 14: 'Sucessful trade'
case 14:
title = 'Trade finished!';
titleColor = 'success';
titleIcon = function () {
baseContract.title = 'Trade finished!';
baseContract.titleColor = 'success';
baseContract.titleIcon = function () {
return <Bolt xs={{ width: '1em', height: '1em' }} color='warning' />;
};
prompt = function () {
baseContract.prompt = function () {
return (
<SuccessfulPrompt
baseUrl={baseUrl}
@ -612,7 +654,7 @@ const TradeBox = ({
onClickStartAgain={onStartAgain}
loadingRenew={loadingButtons.renewOrder}
onClickRenew={() => {
onRenewOrder();
renewOrder();
setLoadingButtons({ ...noLoadingButtons, renewOrder: true });
}}
/>
@ -623,9 +665,9 @@ const TradeBox = ({
// 15: 'Failed lightning network routing'
case 15:
if (isBuyer) {
bondStatus = 'unlocked';
title = 'Lightning Routing Failed';
prompt = function () {
baseContract.bondStatus = 'unlocked';
baseContract.title = 'Lightning Routing Failed';
baseContract.prompt = function () {
return (
<RoutingFailedPrompt
order={order}
@ -638,12 +680,12 @@ const TradeBox = ({
);
};
} else {
title = 'Trade finished!';
titleColor = 'success';
titleIcon = function () {
baseContract.title = 'Trade finished!';
baseContract.titleColor = 'success';
baseContract.titleIcon = function () {
return <Bolt xs={{ width: '1em', height: '1em' }} color='warning' />;
};
prompt = function () {
baseContract.prompt = function () {
return (
<SuccessfulPrompt
baseUrl={baseUrl}
@ -652,7 +694,7 @@ const TradeBox = ({
onClickStartAgain={onStartAgain}
loadingRenew={loadingButtons.renewOrder}
onClickRenew={() => {
onRenewOrder();
renewOrder();
setLoadingButtons({ ...noLoadingButtons, renewOrder: true });
}}
/>
@ -663,9 +705,9 @@ const TradeBox = ({
// 16: 'Wait for dispute resolution'
case 16:
bondStatus = 'settled';
title = 'We have the statements';
prompt = function () {
baseContract.bondStatus = 'settled';
baseContract.title = 'We have the statements';
baseContract.prompt = function () {
return <DisputeWaitResolutionPrompt />;
};
break;
@ -675,14 +717,14 @@ const TradeBox = ({
case 17:
case 18:
if ((status === 17 && isMaker) || (status === 18 && !isMaker)) {
title = 'You have lost the dispute';
prompt = function () {
baseContract.title = 'You have lost the dispute';
baseContract.prompt = function () {
return <DisputeLoserPrompt />;
};
bondStatus = 'settled';
baseContract.bondStatus = 'settled';
} else if ((status === 17 && !isMaker) || (status === 18 && isMaker)) {
title = 'You have won the dispute';
prompt = function () {
baseContract.title = 'You have won the dispute';
baseContract.prompt = function () {
return <DisputeWinnerPrompt />;
};
}
@ -692,10 +734,10 @@ const TradeBox = ({
break;
}
return { title, titleVariables, titleColor, prompt, bondStatus, titleIcon };
return baseContract;
};
const contract = currentOrder != null ? statusToContract(currentOrder) : null;
const contract = statusToContract();
return (
<Box>
@ -705,7 +747,7 @@ const TradeBox = ({
setOpen(closeAll);
}}
waitingWebln={waitingWebln}
isBuyer={currentOrder?.is_buyer ?? false}
isBuyer={garage.getSlot().order?.is_buyer ?? false}
/>
<ConfirmDisputeDialog
open={open.confirmDispute}
@ -728,11 +770,11 @@ const TradeBox = ({
}}
onCollabCancelClick={cancel}
loading={loadingButtons.cancel}
peerAskedCancel={currentOrder?.pending_cancel ?? false}
peerAskedCancel={garage.getSlot().order?.pending_cancel ?? false}
/>
<ConfirmFiatSentDialog
open={open.confirmFiatSent}
order={currentOrder}
order={garage.getSlot().order}
loadingButton={loadingButtons.fiatSent}
onClose={() => {
setOpen(closeAll);
@ -749,14 +791,14 @@ const TradeBox = ({
/>
<ConfirmFiatReceivedDialog
open={open.confirmFiatReceived}
order={currentOrder}
order={garage.getSlot().order}
loadingButton={loadingButtons.fiatReceived}
onClose={() => {
setOpen(closeAll);
}}
onConfirmClick={confirmFiatReceived}
/>
<CollabCancelAlert order={currentOrder} />
<CollabCancelAlert order={garage.getSlot().order} />
<Grid
container
padding={1}
@ -767,7 +809,7 @@ const TradeBox = ({
>
<Grid item>
<Title
order={currentOrder}
order={garage.getSlot().order}
text={contract?.title}
color={contract?.titleColor}
icon={contract?.titleIcon}
@ -781,7 +823,10 @@ const TradeBox = ({
{contract?.bondStatus !== 'hide' ? (
<Grid item sx={{ width: '100%' }}>
<Divider />
<BondStatus status={contract?.bondStatus} isMaker={currentOrder?.is_maker ?? false} />
<BondStatus
status={contract?.bondStatus}
isMaker={garage.getSlot().order?.is_maker ?? false}
/>
</Grid>
) : (
<></>
@ -789,7 +834,7 @@ const TradeBox = ({
<Grid item>
<CancelButton
order={currentOrder}
order={garage.getSlot().order}
onClickCancel={cancel}
openCancelDialog={() => {
setOpen({ ...closeAll, confirmCancel: true });

View File

@ -51,8 +51,6 @@ export interface fetchRobotProps {
export interface UseFederationStoreType {
federation: Federation;
sortedCoordinators: string[];
focusedCoordinator: string | null;
setFocusedCoordinator: Dispatch<SetStateAction<string>>;
setDelay: Dispatch<SetStateAction<number>>;
coordinatorUpdatedAt: string;
federationUpdatedAt: string;
@ -61,8 +59,6 @@ export interface UseFederationStoreType {
export const initialFederationContext: UseFederationStoreType = {
federation: new Federation(),
sortedCoordinators: [],
focusedCoordinator: '',
setFocusedCoordinator: () => {},
setDelay: () => {},
coordinatorUpdatedAt: '',
federationUpdatedAt: '',
@ -73,7 +69,7 @@ export const FederationContext = createContext<UseFederationStoreType>(initialFe
export const useFederationStore = (): UseFederationStoreType => {
const { settings, page, origin, hostUrl, open, torStatus } =
useContext<UseAppStoreType>(AppContext);
const { setMaker, garage, orderUpdatedAt, robotUpdatedAt, badOrder } =
const { setMaker, garage, setBadOrder, robotUpdatedAt, badOrder, orderUpdatedAt } =
useContext<UseGarageStoreType>(GarageContext);
const [federation, setFederation] = useState(initialFederationContext.federation);
const sortedCoordinators = useMemo(() => {
@ -88,13 +84,10 @@ export const useFederationStore = (): UseFederationStoreType => {
);
const [federationUpdatedAt, setFederationUpdatedAt] = useState<string>(new Date().toISOString());
const [focusedCoordinator, setFocusedCoordinator] = useState<string>(sortedCoordinators[0]);
const [delay, setDelay] = useState<number>(60000);
const [delay, setDelay] = useState<number>(5000);
const [timer, setTimer] = useState<NodeJS.Timer | undefined>(() =>
setInterval(() => null, delay),
);
const [currentOrder, setCurrentOrder] = useState<Order | null>(null);
useEffect(() => {
// On bitcoin network change we reset book, limits and federation info and fetch everything again
@ -112,27 +105,34 @@ export const useFederationStore = (): UseFederationStoreType => {
}, [settings.network, torStatus]);
const fetchCurrentOrder = (): void => {
if (currentOrder?.id != null && (page === 'order' || badOrder === undefined)) {
void federation.fetchOrder(currentOrder, garage);
const activeSlot = garage.getSlot();
if (activeSlot.activeOrderShortAlias !== null && activeSlot.activeOrderId !== null) {
const coordinator = federation.getCoordinator(activeSlot.activeOrderShortAlias);
coordinator
.fetchOrder(activeSlot.activeOrderId, activeSlot.robot)
.then((order) => {
if (order?.bad_request !== undefined) {
setBadOrder(order.bad_request);
}
if (order?.id !== null) {
garage.updateOrder(order as Order);
}
})
.finally(() => {
setTimer(setTimeout(fetchCurrentOrder, delay));
});
} else {
setTimer(setTimeout(fetchCurrentOrder, delay));
}
};
// Fetch current order at load and in a loop
useEffect(() => {
fetchCurrentOrder();
}, [currentOrder, page]);
useEffect(() => {
setCurrentOrder(garage.getOrder());
}, [orderUpdatedAt]);
useEffect(() => {
clearInterval(timer);
setTimer(setInterval(fetchCurrentOrder, delay));
fetchCurrentOrder();
return () => {
clearInterval(timer);
};
}, [delay, currentOrder, page, badOrder]);
}, []);
useEffect(() => {
const robot = garage.getSlot().robot;
@ -154,8 +154,6 @@ export const useFederationStore = (): UseFederationStoreType => {
return {
federation,
sortedCoordinators,
focusedCoordinator,
setFocusedCoordinator,
setDelay,
coordinatorUpdatedAt,
federationUpdatedAt,

View File

@ -188,7 +188,7 @@ export class Coordinator {
apiClient
.get(this.url, `${this.basePath}/api/limits/`)
.then((data) => {
if (data != null) {
if (data !== null) {
const newLimits = data as LimitList;
for (const currency in this.limits) {
@ -215,8 +215,10 @@ export class Coordinator {
apiClient
.get(this.url, `${this.basePath}/api/info/`)
.then((data) => {
this.info = data as Info;
onDataLoad();
if (data !== null) {
this.info = data as Info;
onDataLoad();
}
})
.catch((e) => {
console.log(e);

View File

@ -96,20 +96,6 @@ export class Federation {
});
};
fetchOrder = async (order: Order, garage: Garage): Promise<Order | null> => {
if (order.shortAlias !== null) {
const coordinator = this.coordinators[order.shortAlias];
if (coordinator != null && order.id !== null) {
const newOrder = await coordinator.fetchOrder(order.id, garage.getSlot().robot);
if (newOrder != null) {
garage.updateOrder(newOrder);
return newOrder;
}
}
}
return order;
};
// Coordinators
getCoordinator = (shortAlias: string): Coordinator => {
return this.coordinators[shortAlias];

View File

@ -146,7 +146,17 @@ class Garage {
// Orders
updateOrder: (order: Order, index?: number) => void = (order, index = this.currentSlot) => {
this.slots[index].order = order;
const updatedOrder = this.slots[index].order;
if (updatedOrder !== null && updatedOrder.id === order.id) {
Object.assign(updatedOrder, order);
this.slots[index].order = updatedOrder;
} else {
this.slots[index].order = order;
}
if (this.slots[index].order?.is_participant) {
this.slots[index].activeOrderId = this.slots[index].order?.id ?? null;
this.slots[index].activeOrderShortAlias = this.slots[index].order?.shortAlias ?? null;
}
this.triggerHook('onOrderUpdate');
this.save();
};

View File

@ -106,6 +106,9 @@ export interface Order {
network: 'mainnet' | 'testnet';
shortAlias: string;
bad_request?: string;
bad_address?: string;
bad_invoice?: string;
bad_statement?: string;
}
export const defaultOrder: Order = {

View File

@ -35,7 +35,6 @@ const FederationWidget = React.forwardRef(function Component(
return (
<Paper elevation={3} style={{ width: '100%', height: '100%' }}>
<FederationTable
openCoordinator={() => setOpen({ ...open, coordinator: true })}
maxWidth={layout.w * gridCellSize} // EM units
maxHeight={layout.h * gridCellSize} // EM units
/>