Fix order page

This commit is contained in:
KoalaSat 2023-11-15 13:59:54 +01:00 committed by Reckless_Satoshi
parent 8d8e3a5688
commit 7c06c229b4
15 changed files with 387 additions and 249 deletions

View File

@ -18,7 +18,7 @@ import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageCon
const BookPage = (): JSX.Element => { const BookPage = (): JSX.Element => {
const { windowSize } = useContext<UseAppStoreType>(AppContext); const { windowSize } = useContext<UseAppStoreType>(AppContext);
const { setDelay } = useContext<UseFederationStoreType>(FederationContext); const { setDelay } = useContext<UseFederationStoreType>(FederationContext);
const { garage, clearOrder } = useContext<UseGarageStoreType>(GarageContext); const { garage } = useContext<UseGarageStoreType>(GarageContext);
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const [view, setView] = useState<'list' | 'depth' | 'map'>('list'); const [view, setView] = useState<'list' | 'depth' | 'map'>('list');
@ -32,7 +32,6 @@ const BookPage = (): JSX.Element => {
const onOrderClicked = function (id: number, shortAlias: string): void { const onOrderClicked = function (id: number, shortAlias: string): void {
if (garage.getRobot().avatarLoaded) { if (garage.getRobot().avatarLoaded) {
clearOrder();
setDelay(10000); setDelay(10000);
navigate(`/order/${shortAlias}/${id}`); navigate(`/order/${shortAlias}/${id}`);
} else { } else {

View File

@ -14,7 +14,7 @@ import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageCon
const MakerPage = (): JSX.Element => { const MakerPage = (): JSX.Element => {
const { fav, windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext); const { fav, windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
const { setDelay, federation } = useContext<UseFederationStoreType>(FederationContext); const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage, maker } = useContext<UseGarageStoreType>(GarageContext); const { garage, maker } = useContext<UseGarageStoreType>(GarageContext);
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
@ -50,15 +50,9 @@ const MakerPage = (): JSX.Element => {
maker.paymentMethods, maker.paymentMethods,
]); ]);
const onViewOrder = function (): void {
garage.updateOrder(null);
setDelay(10000);
};
const onOrderClicked = function (id: number): void { const onOrderClicked = function (id: number): void {
if (garage.getRobot().avatarLoaded) { if (garage.getRobot().avatarLoaded) {
navigate(`/order/${id}`); navigate(`/order/${id}`);
onViewOrder();
} else { } else {
setOpenNoRobot(true); setOpenNoRobot(true);
} }

View File

@ -16,7 +16,6 @@ import {
} from '@mui/icons-material'; } from '@mui/icons-material';
import RobotAvatar from '../../components/RobotAvatar'; import RobotAvatar from '../../components/RobotAvatar';
import { AppContext, type UseAppStoreType, closeAll } from '../../contexts/AppContext'; import { AppContext, type UseAppStoreType, closeAll } from '../../contexts/AppContext';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext'; import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
const NavBar = (): JSX.Element => { const NavBar = (): JSX.Element => {
@ -33,8 +32,7 @@ const NavBar = (): JSX.Element => {
navbarHeight, navbarHeight,
hostUrl, hostUrl,
} = useContext<UseAppStoreType>(AppContext); } = useContext<UseAppStoreType>(AppContext);
const { currentOrder } = useContext<UseFederationStoreType>(FederationContext); const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
@ -67,7 +65,7 @@ const NavBar = (): JSX.Element => {
if (isPage(pathPage)) { if (isPage(pathPage)) {
setPage(pathPage); setPage(pathPage);
} }
}, [location, navigate, setPage]); }, [location, navigate, setPage, orderUpdatedAt]);
const handleSlideDirection = function (oldPage: Page, newPage: Page): void { const handleSlideDirection = function (oldPage: Page, newPage: Page): void {
const oldPos: number = pagesPosition[oldPage]; const oldPos: number = pagesPosition[oldPage];
@ -79,10 +77,15 @@ const NavBar = (): JSX.Element => {
const changePage = function (mouseEvent: any, newPage: Page): void { const changePage = function (mouseEvent: any, newPage: Page): void {
if (newPage !== 'none') { if (newPage !== 'none') {
const slot = garage.getSlot();
handleSlideDirection(page, newPage); handleSlideDirection(page, newPage);
setPage(newPage); setPage(newPage);
const param = const param =
newPage === 'order' ? `${String(currentOrder.shortAlias)}/${String(currentOrder.id)}` : ''; newPage === 'order'
? `${String(slot.activeOrderShortAlias)}/${String(
slot.activeOrderId ?? slot.lastOrderId,
)}`
: '';
setTimeout(() => { setTimeout(() => {
navigate(`/${newPage}/${param}`); navigate(`/${newPage}/${param}`);
}, theme.transitions.duration.leavingScreen * 3); }, theme.transitions.duration.leavingScreen * 3);
@ -159,7 +162,7 @@ const NavBar = (): JSX.Element => {
sx={tabSx} sx={tabSx}
label={smallBar ? undefined : t('Order')} label={smallBar ? undefined : t('Order')}
value='order' value='order'
disabled={!garage.getRobot().avatarLoaded || currentOrder.id == null} disabled={!garage.getRobot().avatarLoaded || !garage.getSlot().activeOrderId}
icon={<Assignment />} icon={<Assignment />}
iconPosition='start' iconPosition='start'
/> />

View File

@ -10,11 +10,12 @@ import { apiClient } from '../../services/api';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext'; import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext'; import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext'; import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
import { type Order } from '../../models';
const OrderPage = (): JSX.Element => { const OrderPage = (): JSX.Element => {
const { windowSize, setOpen, settings, navbarHeight, hostUrl, origin } = const { windowSize, setOpen, settings, navbarHeight, hostUrl, origin } =
useContext<UseAppStoreType>(AppContext); useContext<UseAppStoreType>(AppContext);
const { setFocusedCoordinator, federation, currentOrder, setCurrentOrder, focusedCoordinator } = const { setFocusedCoordinator, federation, focusedCoordinator } =
useContext<UseFederationStoreType>(FederationContext); useContext<UseFederationStoreType>(FederationContext);
const { garage, badOrder, setBadOrder } = useContext<UseGarageStoreType>(GarageContext); const { garage, badOrder, setBadOrder } = useContext<UseGarageStoreType>(GarageContext);
const { t } = useTranslation(); const { t } = useTranslation();
@ -26,26 +27,47 @@ const OrderPage = (): JSX.Element => {
const [tab, setTab] = useState<'order' | 'contract'>('contract'); const [tab, setTab] = useState<'order' | 'contract'>('contract');
const [baseUrl, setBaseUrl] = useState<string>(hostUrl); const [baseUrl, setBaseUrl] = useState<string>(hostUrl);
const [currentOrder, setCurrentOrder] = useState<Order | null>(null);
useEffect(() => { useEffect(() => {
const newOrder = { const coordinator = federation.getCoordinator(params.shortAlias ?? '');
shortAlias: params.shortAlias ?? '', const { url, basePath } = coordinator.getEndpoint(
id: Number(params.orderId) ?? null, settings.network,
order: null, origin,
}; settings.selfhostedClient,
hostUrl,
);
const { url, basePath } = federation
.getCoordinator(newOrder.shortAlias)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
setBaseUrl(`${url}${basePath}`); setBaseUrl(`${url}${basePath}`);
if (currentOrder.id !== newOrder.id || currentOrder.shortAlias !== newOrder.shortAlias) { if (garage.getSlot().activeOrderId === Number(params.orderId)) {
setCurrentOrder(newOrder); if (garage.getSlot().order != null) {
setCurrentOrder(garage.getSlot().order);
} else {
coordinator
.fetchOrder(Number(params.orderId) ?? null, garage.getRobot())
.then((response) => {
setCurrentOrder(response);
garage.updateOrder(response as Order);
})
.catch((e) => {
console.log(e);
});
}
} else {
coordinator
.fetchOrder(Number(params.orderId) ?? null, garage.getRobot())
.then((response) => {
setCurrentOrder(response);
})
.catch((e) => {
console.log(e);
});
} }
}, [params]); }, [params]);
const onClickCoordinator = function (): void { const onClickCoordinator = function (): void {
if (currentOrder.shortAlias != null) { if (currentOrder?.shortAlias != null) {
setFocusedCoordinator(currentOrder.shortAlias); setFocusedCoordinator(currentOrder.shortAlias);
} }
setOpen((open) => { setOpen((open) => {
@ -54,7 +76,7 @@ const OrderPage = (): JSX.Element => {
}; };
const renewOrder = function (): void { const renewOrder = function (): void {
const order = currentOrder.order; const order = currentOrder;
if (order !== null && focusedCoordinator != null) { if (order !== null && focusedCoordinator != null) {
const body = { const body = {
type: order.type, type: order.type,
@ -73,16 +95,16 @@ const OrderPage = (): JSX.Element => {
latitude: order.latitude, latitude: order.latitude,
longitude: order.longitude, longitude: order.longitude,
}; };
const { url } = federation const { url, basePath } = federation
.getCoordinator(focusedCoordinator) .getCoordinator(order.shortAlias)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl); .getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
apiClient apiClient
.post(url, '/api/make/', body, { tokenSHA256: garage.getRobot().tokenSHA256 }) .post(url + basePath, '/api/make/', body, { tokenSHA256: garage.getRobot().tokenSHA256 })
.then((data: any) => { .then((data: any) => {
if (data.bad_request !== undefined) { if (data.bad_request !== undefined) {
setBadOrder(data.bad_request); setBadOrder(data.bad_request);
} else if (data.id !== undefined) { } else if (data.id !== undefined) {
navigate(`/order/${String(currentOrder.shortAlias)}/${String(data.id)}`); navigate(`/order/${String(currentOrder?.shortAlias)}/${String(data.id)}`);
} }
}) })
.catch(() => { .catch(() => {
@ -97,14 +119,14 @@ const OrderPage = (): JSX.Element => {
return ( return (
<Box> <Box>
{currentOrder.order === null && badOrder === undefined && <CircularProgress />} {currentOrder === null && badOrder === undefined && <CircularProgress />}
{badOrder !== undefined ? ( {badOrder !== undefined ? (
<Typography align='center' variant='subtitle2' color='secondary'> <Typography align='center' variant='subtitle2' color='secondary'>
{t(badOrder)} {t(badOrder)}
</Typography> </Typography>
) : null} ) : null}
{currentOrder.order !== null && badOrder === undefined ? ( {currentOrder !== null && badOrder === undefined ? (
currentOrder.order.is_participant ? ( currentOrder.is_participant ? (
windowSize.width > doublePageWidth ? ( windowSize.width > doublePageWidth ? (
// DOUBLE PAPER VIEW // DOUBLE PAPER VIEW
<Grid <Grid
@ -125,7 +147,8 @@ const OrderPage = (): JSX.Element => {
}} }}
> >
<OrderDetails <OrderDetails
coordinator={federation.getCoordinator(String(currentOrder.shortAlias))} shortAlias={String(currentOrder.shortAlias)}
currentOrder={currentOrder}
onClickCoordinator={onClickCoordinator} onClickCoordinator={onClickCoordinator}
baseUrl={baseUrl} baseUrl={baseUrl}
onClickGenerateRobot={() => { onClickGenerateRobot={() => {
@ -145,6 +168,7 @@ const OrderPage = (): JSX.Element => {
> >
<TradeBox <TradeBox
robot={garage.getRobot()} robot={garage.getRobot()}
currentOrder={currentOrder}
settings={settings} settings={settings}
setBadOrder={setBadOrder} setBadOrder={setBadOrder}
baseUrl={baseUrl} baseUrl={baseUrl}
@ -179,7 +203,8 @@ const OrderPage = (): JSX.Element => {
> >
<div style={{ display: tab === 'order' ? '' : 'none' }}> <div style={{ display: tab === 'order' ? '' : 'none' }}>
<OrderDetails <OrderDetails
coordinator={federation.getCoordinator(String(currentOrder.shortAlias))} shortAlias={String(currentOrder.shortAlias)}
currentOrder={currentOrder}
onClickCoordinator={onClickCoordinator} onClickCoordinator={onClickCoordinator}
baseUrl={baseUrl} baseUrl={baseUrl}
onClickGenerateRobot={() => { onClickGenerateRobot={() => {
@ -190,6 +215,7 @@ const OrderPage = (): JSX.Element => {
<div style={{ display: tab === 'contract' ? '' : 'none' }}> <div style={{ display: tab === 'contract' ? '' : 'none' }}>
<TradeBox <TradeBox
robot={garage.getRobot()} robot={garage.getRobot()}
currentOrder={currentOrder}
settings={settings} settings={settings}
setBadOrder={setBadOrder} setBadOrder={setBadOrder}
baseUrl={baseUrl} baseUrl={baseUrl}
@ -210,7 +236,8 @@ const OrderPage = (): JSX.Element => {
}} }}
> >
<OrderDetails <OrderDetails
coordinator={federation.getCoordinator(String(currentOrder.shortAlias))} shortAlias={String(currentOrder.shortAlias)}
currentOrder={currentOrder}
onClickCoordinator={onClickCoordinator} onClickCoordinator={onClickCoordinator}
baseUrl={hostUrl} baseUrl={hostUrl}
onClickGenerateRobot={() => { onClickGenerateRobot={() => {

View File

@ -22,7 +22,6 @@ import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { genBase62Token } from '../../utils'; import { genBase62Token } from '../../utils';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext'; import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
interface RobotProfileProps { interface RobotProfileProps {
robot: Robot; robot: Robot;
@ -45,7 +44,6 @@ const RobotProfile = ({
width, width,
}: RobotProfileProps): JSX.Element => { }: RobotProfileProps): JSX.Element => {
const { windowSize, hostUrl } = useContext<UseAppStoreType>(AppContext); const { windowSize, hostUrl } = useContext<UseAppStoreType>(AppContext);
const { currentOrder } = useContext<UseFederationStoreType>(FederationContext);
const { garage, robotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext); const { garage, robotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
const { t } = useTranslation(); const { t } = useTranslation();
@ -146,28 +144,36 @@ const RobotProfile = ({
)} )}
</Grid> </Grid>
{Boolean(garage.getRobot().activeOrderId) && {Boolean(garage.getSlot().activeOrderId) &&
garage.getRobot().avatarLoaded && garage.getRobot().avatarLoaded &&
Boolean(garage.getRobot().nickname) ? ( Boolean(garage.getRobot().nickname) ? (
<Grid item> <Grid item>
<Button <Button
onClick={() => { onClick={() => {
navigate(`/order/${String(currentOrder.shortAlias)}/${String(currentOrder.id)}`); navigate(
`/order/${String(garage.getSlot().activeOrderShortAlias)}/${String(
garage.getSlot().activeOrderId,
)}`,
);
}} }}
> >
{t('Active order #{{orderID}}', { orderID: garage.getRobot().activeOrderId })} {t('Active order #{{orderID}}', { orderID: garage.getSlot().activeOrderId })}
</Button> </Button>
</Grid> </Grid>
) : null} ) : null}
{Boolean(garage.getRobot().lastOrderId) && {Boolean(garage.getSlot().lastOrderId) &&
garage.getRobot().avatarLoaded && garage.getRobot().avatarLoaded &&
Boolean(garage.getRobot().nickname) ? ( Boolean(garage.getRobot().nickname) ? (
<Grid item container direction='column' alignItems='center'> <Grid item container direction='column' alignItems='center'>
<Grid item> <Grid item>
<Button <Button
onClick={() => { onClick={() => {
navigate(`/order/${String(currentOrder.shortAlias)}/${String(currentOrder.id)}`); navigate(
`/order/${String(garage.getRobot()?.shortAlias)}/${String(
garage.getRobot()?.lastOrderId,
)}`,
);
}} }}
> >
{t('Last order #{{orderID}}', { orderID: garage.getRobot().lastOrderId })} {t('Last order #{{orderID}}', { orderID: garage.getRobot().lastOrderId })}

View File

@ -30,7 +30,7 @@ import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext'; import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
interface TakeButtonProps { interface TakeButtonProps {
baseUrl: string; currentOrder: Order;
info?: Info; info?: Info;
onClickGenerateRobot?: () => void; onClickGenerateRobot?: () => void;
} }
@ -42,7 +42,7 @@ interface OpenDialogsProps {
const closeAll = { inactiveMaker: false, confirmation: false }; const closeAll = { inactiveMaker: false, confirmation: false };
const TakeButton = ({ const TakeButton = ({
baseUrl, currentOrder,
info, info,
onClickGenerateRobot = () => null, onClickGenerateRobot = () => null,
}: TakeButtonProps): JSX.Element => { }: TakeButtonProps): JSX.Element => {
@ -50,7 +50,7 @@ const TakeButton = ({
const theme = useTheme(); const theme = useTheme();
const { settings, origin, hostUrl } = useContext<UseAppStoreType>(AppContext); const { settings, origin, hostUrl } = useContext<UseAppStoreType>(AppContext);
const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext); const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
const { federation, focusedCoordinator } = useContext<UseFederationStoreType>(FederationContext); const { federation } = useContext<UseFederationStoreType>(FederationContext);
const [takeAmount, setTakeAmount] = useState<string>(''); const [takeAmount, setTakeAmount] = useState<string>('');
const [badRequest, setBadRequest] = useState<string>(''); const [badRequest, setBadRequest] = useState<string>('');
@ -59,18 +59,18 @@ const TakeButton = ({
const [satoshis, setSatoshis] = useState<string>(''); const [satoshis, setSatoshis] = useState<string>('');
const satoshisNow = (): string | undefined => { const satoshisNow = (): string | undefined => {
const order = garage.getOrder(); if (currentOrder === null) return;
if (order === null) return;
const tradeFee = info?.taker_fee ?? 0; const tradeFee = info?.taker_fee ?? 0;
const defaultRoutingBudget = 0.001; const defaultRoutingBudget = 0.001;
const btcNow = order.satoshis_now / 100000000; const btcNow = currentOrder.satoshis_now / 100000000;
const rate = order.amount != null ? order.amount / btcNow : order.max_amount / btcNow; const rate =
const amount = order.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount); currentOrder.amount != null ? currentOrder.amount / btcNow : currentOrder.max_amount / btcNow;
const amount =
currentOrder.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount);
const satoshis = computeSats({ const satoshis = computeSats({
amount, amount,
routingBudget: order.is_buyer ? defaultRoutingBudget : 0, routingBudget: currentOrder.is_buyer ? defaultRoutingBudget : 0,
fee: tradeFee, fee: tradeFee,
rate, rate,
}); });
@ -82,9 +82,7 @@ const TakeButton = ({
}, [orderUpdatedAt, takeAmount, info]); }, [orderUpdatedAt, takeAmount, info]);
const currencyCode: string = const currencyCode: string =
garage.getOrder()?.currency === 1000 currentOrder?.currency === 1000 ? 'Sats' : currencies[`${Number(currentOrder?.currency)}`];
? 'Sats'
: currencies[`${Number(garage.getOrder()?.currency)}`];
const InactiveMakerDialog = function (): JSX.Element { const InactiveMakerDialog = function (): JSX.Element {
return ( return (
@ -156,14 +154,13 @@ const TakeButton = ({
}; };
const amountHelperText = useMemo(() => { const amountHelperText = useMemo(() => {
const order = garage.getOrder(); if (currentOrder === null) return;
if (order === null) return; const amount =
currentOrder.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount);
const amount = order.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount); if (amount < Number(currentOrder.min_amount) && takeAmount !== '') {
if (amount < Number(order.min_amount) && takeAmount !== '') {
return t('Too low'); return t('Too low');
} else if (amount > Number(order.max_amount) && takeAmount !== '') { } else if (amount > Number(currentOrder.max_amount) && takeAmount !== '') {
return t('Too high'); return t('Too high');
} else { } else {
return null; return null;
@ -171,7 +168,7 @@ const TakeButton = ({
}, [orderUpdatedAt, takeAmount]); }, [orderUpdatedAt, takeAmount]);
const onTakeOrderClicked = function (): void { const onTakeOrderClicked = function (): void {
if (garage.getOrder()?.maker_status === 'Inactive') { if (currentOrder?.maker_status === 'Inactive') {
setOpen({ inactiveMaker: true, confirmation: false }); setOpen({ inactiveMaker: true, confirmation: false });
} else { } else {
setOpen({ inactiveMaker: false, confirmation: true }); setOpen({ inactiveMaker: false, confirmation: true });
@ -179,18 +176,18 @@ const TakeButton = ({
}; };
const invalidTakeAmount = useMemo(() => { const invalidTakeAmount = useMemo(() => {
const order = garage.getOrder(); const amount =
const amount = order?.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount); currentOrder?.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount);
return ( return (
amount < Number(order?.min_amount) || amount < Number(currentOrder?.min_amount) ||
amount > Number(order?.max_amount) || amount > Number(currentOrder?.max_amount) ||
takeAmount === '' || takeAmount === '' ||
takeAmount == null takeAmount == null
); );
}, [takeAmount, orderUpdatedAt]); }, [takeAmount, orderUpdatedAt]);
const takeOrderButton = function (): JSX.Element { const takeOrderButton = function (): JSX.Element {
if (garage.getOrder()?.has_range === true) { if (currentOrder?.has_range === true) {
return ( return (
<Box <Box
sx={{ sx={{
@ -229,8 +226,8 @@ const TakeButton = ({
required={true} required={true}
value={takeAmount} value={takeAmount}
inputProps={{ inputProps={{
min: garage.getOrder()?.min_amount, min: currentOrder?.min_amount,
max: garage.getOrder()?.max_amount, max: currentOrder?.max_amount,
style: { textAlign: 'center' }, style: { textAlign: 'center' },
}} }}
onChange={handleTakeAmountChange} onChange={handleTakeAmountChange}
@ -283,7 +280,7 @@ const TakeButton = ({
{satoshis !== '0' && satoshis !== '' && !invalidTakeAmount ? ( {satoshis !== '0' && satoshis !== '' && !invalidTakeAmount ? (
<Grid item> <Grid item>
<FormHelperText sx={{ position: 'relative', top: '0.15em' }}> <FormHelperText sx={{ position: 'relative', top: '0.15em' }}>
{garage.getOrder()?.type === 1 {currentOrder?.type === 1
? t('You will receive {{satoshis}} Sats (Approx)', { satoshis }) ? t('You will receive {{satoshis}} Sats (Approx)', { satoshis })
: t('You will send {{satoshis}} Sats (Approx)', { satoshis })} : t('You will send {{satoshis}} Sats (Approx)', { satoshis })}
</FormHelperText> </FormHelperText>
@ -317,19 +314,19 @@ const TakeButton = ({
}; };
const takeOrder = function (): void { const takeOrder = function (): void {
if (!(focusedCoordinator != null)) return; if (currentOrder === null) return;
setLoadingTake(true); setLoadingTake(true);
const { url } = federation const { url, basePath } = federation
.getCoordinator(focusedCoordinator) .getCoordinator(currentOrder.shortAlias)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl); .getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
apiClient apiClient
.post( .post(
url, url + basePath,
`/api/order/?order_id=${String(garage.getOrder()?.id)}`, `/api/order/?order_id=${String(currentOrder?.id)}`,
{ {
action: 'take', action: 'take',
amount: garage.getOrder()?.currency === 1000 ? takeAmount / 100000000 : takeAmount, amount: currentOrder?.currency === 1000 ? takeAmount / 100000000 : takeAmount,
}, },
{ tokenSHA256: garage.getRobot().tokenSHA256 }, { tokenSHA256: garage.getRobot().tokenSHA256 },
) )
@ -350,7 +347,7 @@ const TakeButton = ({
return ( return (
<Box> <Box>
<Countdown <Countdown
date={new Date(garage.getOrder()?.penalty ?? '')} date={new Date(currentOrder?.penalty ?? '')}
renderer={countdownTakeOrderRenderer} renderer={countdownTakeOrderRenderer}
/> />
{badRequest !== '' ? ( {badRequest !== '' ? (

View File

@ -43,52 +43,52 @@ import { F2fMapDialog } from '../Dialogs';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext'; import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext'; import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext'; import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
import { type Order } from '../../models';
interface OrderDetailsProps { interface OrderDetailsProps {
coordinator: Coordinator; shortAlias: string;
currentOrder: Order;
onClickCoordinator?: () => void; onClickCoordinator?: () => void;
baseUrl: string; baseUrl: string;
onClickGenerateRobot?: () => void; onClickGenerateRobot?: () => void;
} }
const OrderDetails = ({ const OrderDetails = ({
coordinator, shortAlias,
currentOrder,
onClickCoordinator = () => null, onClickCoordinator = () => null,
baseUrl, baseUrl,
onClickGenerateRobot = () => null, onClickGenerateRobot = () => null,
}: OrderDetailsProps): JSX.Element => { }: OrderDetailsProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const { hostUrl } = useContext<UseAppStoreType>(AppContext);
const theme = useTheme(); const theme = useTheme();
const { hostUrl } = useContext<UseAppStoreType>(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext); const { orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
const { currentOrder } = useContext<UseFederationStoreType>(FederationContext); const [coordinator] = useState<Coordinator | null>(federation.getCoordinator(shortAlias));
const currencyCode: string = currencies[(currentOrder?.currency ?? 1).toString()];
const currencyCode: string = currencies[(currentOrder.order?.currency ?? 1).toString()];
const [showSatsDetails, setShowSatsDetails] = useState<boolean>(false); const [showSatsDetails, setShowSatsDetails] = useState<boolean>(false);
const [openWorldmap, setOpenWorldmap] = useState<boolean>(false); const [openWorldmap, setOpenWorldmap] = useState<boolean>(false);
const amountString = useMemo(() => { const amountString = useMemo(() => {
// precision to 8 decimal if currency is BTC otherwise 4 decimals if (currentOrder === null || currentOrder.amount === null) return;
const order = currentOrder.order;
if (order === null) return; if (currentOrder.currency === 1000) {
if (order.currency === 1000) {
return ( return (
amountToString( amountToString(
(order.amount * 100000000).toString(), (currentOrder.amount * 100000000).toString(),
order.amount > 0 ? false : order.has_range, currentOrder.amount > 0 ? false : currentOrder.has_range,
order.min_amount * 100000000, currentOrder.min_amount * 100000000,
order.max_amount * 100000000, currentOrder.max_amount * 100000000,
) + ' Sats' ) + ' Sats'
); );
} else { } else {
return ( return (
amountToString( amountToString(
order.amount.toString(), currentOrder.amount.toString(),
order.amount > 0 ? false : order.has_range, currentOrder.amount > 0 ? false : currentOrder.has_range,
order.min_amount, currentOrder.min_amount,
order.max_amount, currentOrder.max_amount,
) + ` ${currencyCode}` ) + ` ${currencyCode}`
); );
} }
@ -107,7 +107,7 @@ const OrderDetails = ({
return <span> {t('The order has expired')}</span>; return <span> {t('The order has expired')}</span>;
} else { } else {
let color = 'inherit'; let color = 'inherit';
const fraction_left = total / 1000 / (currentOrder.order?.total_secs_exp ?? 1); const fraction_left = total / 1000 / (currentOrder?.total_secs_exp ?? 1);
// Make orange at 25% of time left // Make orange at 25% of time left
if (fraction_left < 0.25) { if (fraction_left < 0.25) {
color = theme.palette.warning.main; color = theme.palette.warning.main;
@ -167,7 +167,7 @@ const OrderDetails = ({
let send: string = ''; let send: string = '';
let receive: string = ''; let receive: string = '';
let sats: string = ''; let sats: string = '';
const order = currentOrder.order; const order = currentOrder;
if (order === null) return {}; if (order === null) return {};
@ -242,10 +242,10 @@ const OrderDetails = ({
return ( return (
<Grid container spacing={0}> <Grid container spacing={0}>
<F2fMapDialog <F2fMapDialog
latitude={currentOrder.order?.latitude} latitude={currentOrder?.latitude}
longitude={currentOrder.order?.longitude} longitude={currentOrder?.longitude}
open={openWorldmap} open={openWorldmap}
orderType={currentOrder.order?.type ?? 0} orderType={currentOrder?.type ?? 0}
zoom={6} zoom={6}
message={t( message={t(
'The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.', 'The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.',
@ -282,51 +282,44 @@ const OrderDetails = ({
<ListItem> <ListItem>
<ListItemAvatar sx={{ width: '4em', height: '4em' }}> <ListItemAvatar sx={{ width: '4em', height: '4em' }}>
<RobotAvatar <RobotAvatar
statusColor={statusBadgeColor(currentOrder.order?.maker_status ?? '')} statusColor={statusBadgeColor(currentOrder?.maker_status ?? '')}
nickname={currentOrder.order?.maker_nick} nickname={currentOrder?.maker_nick}
tooltip={t(currentOrder.order?.maker_status ?? '')} tooltip={t(currentOrder?.maker_status ?? '')}
orderType={currentOrder.order?.type} orderType={currentOrder?.type}
baseUrl={baseUrl} baseUrl={baseUrl}
small={true} small={true}
/> />
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={`${String(currentOrder.order?.maker_nick)} (${ primary={`${String(currentOrder?.maker_nick)} (${
currentOrder.order?.type === 1 currentOrder?.type === 1
? t(currentOrder.order?.currency === 1000 ? 'Swapping Out' : 'Seller') ? t(currentOrder?.currency === 1000 ? 'Swapping Out' : 'Seller')
: t(currentOrder.order?.currency === 1000 ? 'Swapping In' : 'Buyer') : t(currentOrder?.currency === 1000 ? 'Swapping In' : 'Buyer')
})`} })`}
secondary={t('Order maker')} secondary={t('Order maker')}
/> />
</ListItem> </ListItem>
<Collapse <Collapse in={currentOrder?.is_participant && currentOrder?.taker_nick !== 'None'}>
in={
currentOrder.order?.is_participant === true &&
currentOrder.order?.taker_nick !== 'None'
}
>
<Divider /> <Divider />
<ListItem> <ListItem>
<ListItemText <ListItemText
primary={`${String(currentOrder.order?.taker_nick)} (${ primary={`${String(currentOrder?.taker_nick)} (${
currentOrder.order?.type === 1 currentOrder?.type === 1
? t(currentOrder.order?.currency === 1000 ? 'Swapping In' : 'Buyer') ? t(currentOrder?.currency === 1000 ? 'Swapping In' : 'Buyer')
: t(currentOrder.order?.currency === 1000 ? 'Swapping Out' : 'Seller') : t(currentOrder?.currency === 1000 ? 'Swapping Out' : 'Seller')
})`} })`}
secondary={t('Order taker')} secondary={t('Order taker')}
/> />
<ListItemAvatar> <ListItemAvatar>
<RobotAvatar <RobotAvatar
avatarClass='smallAvatar' avatarClass='smallAvatar'
statusColor={statusBadgeColor(currentOrder.order?.taker_status ?? '')} statusColor={statusBadgeColor(currentOrder?.taker_status ?? '')}
nickname={ nickname={
currentOrder.order?.taker_nick === 'None' currentOrder?.taker_nick === 'None' ? undefined : currentOrder?.taker_nick
? undefined
: currentOrder.order?.taker_nick
} }
tooltip={t(currentOrder.order?.taker_status ?? '')} tooltip={t(currentOrder?.taker_status ?? '')}
orderType={currentOrder.order?.type === 0 ? 1 : 0} orderType={currentOrder?.type === 0 ? 1 : 0}
baseUrl={baseUrl} baseUrl={baseUrl}
small={true} small={true}
/> />
@ -337,13 +330,13 @@ const OrderDetails = ({
<Chip label={t('Order Details')} /> <Chip label={t('Order Details')} />
</Divider> </Divider>
<Collapse in={currentOrder.order?.is_participant}> <Collapse in={currentOrder?.is_participant}>
<ListItem> <ListItem>
<ListItemIcon> <ListItemIcon>
<Article /> <Article />
</ListItemIcon> </ListItemIcon>
<ListItemText <ListItemText
primary={t(currentOrder.order?.status_message ?? '')} primary={t(currentOrder?.status_message ?? '')}
secondary={t('Order status')} secondary={t('Order status')}
/> />
</ListItem> </ListItem>
@ -367,7 +360,7 @@ const OrderDetails = ({
</ListItemIcon> </ListItemIcon>
<ListItemText <ListItemText
primary={amountString} primary={amountString}
secondary={(currentOrder.order?.amount ?? 0) > 0 ? 'Amount' : 'Amount Range'} secondary={(currentOrder?.amount ?? 0) > 0 ? 'Amount' : 'Amount Range'}
/> />
<ListItemIcon> <ListItemIcon>
<IconButton <IconButton
@ -416,16 +409,16 @@ const OrderDetails = ({
size={1.42 * theme.typography.fontSize} size={1.42 * theme.typography.fontSize}
othersText={t('Others')} othersText={t('Others')}
verbose={true} verbose={true}
text={currentOrder.order?.payment_method} text={currentOrder?.payment_method}
/> />
} }
secondary={ secondary={
currentOrder.order?.currency === 1000 currentOrder?.currency === 1000
? t('Swap destination') ? t('Swap destination')
: t('Accepted payment methods') : t('Accepted payment methods')
} }
/> />
{currentOrder.order?.payment_method.includes('Cash F2F') === true && ( {currentOrder?.payment_method.includes('Cash F2F') && (
<ListItemIcon> <ListItemIcon>
<Tooltip enterTouchDelay={0} title={t('F2F location')}> <Tooltip enterTouchDelay={0} title={t('F2F location')}>
<div> <div>
@ -449,29 +442,27 @@ const OrderDetails = ({
<PriceChange /> <PriceChange />
</ListItemIcon> </ListItemIcon>
{currentOrder.order?.price_now !== undefined ? ( {currentOrder?.price_now !== undefined ? (
<ListItemText <ListItemText
primary={t('{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%', { primary={t('{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%', {
price: pn(currentOrder.order?.price_now), price: pn(currentOrder?.price_now),
currencyCode, currencyCode,
premium: currentOrder.order?.premium_now, premium: currentOrder?.premium_now,
})} })}
secondary={t('Price and Premium')} secondary={t('Price and Premium')}
/> />
) : null} ) : null}
{currentOrder.order?.price_now === undefined && {currentOrder?.price_now === undefined && currentOrder?.is_explicit ? (
currentOrder.order?.is_explicit === true ? (
<ListItemText <ListItemText
primary={pn(currentOrder.order?.satoshis)} primary={pn(currentOrder?.satoshis)}
secondary={t('Amount of Satoshis')} secondary={t('Amount of Satoshis')}
/> />
) : null} ) : null}
{currentOrder.order?.price_now === undefined && {currentOrder?.price_now === undefined && !currentOrder?.is_explicit ? (
!(currentOrder.order?.is_explicit === true) ? (
<ListItemText <ListItemText
primary={`${parseFloat(Number(currentOrder.order?.premium).toFixed(2))}%`} primary={`${parseFloat(Number(currentOrder?.premium).toFixed(2))}%`}
secondary={t('Premium over market price')} secondary={t('Premium over market price')}
/> />
) : null} ) : null}
@ -485,7 +476,7 @@ const OrderDetails = ({
</ListItemIcon> </ListItemIcon>
<Grid container> <Grid container>
<Grid item xs={4.5}> <Grid item xs={4.5}>
<ListItemText primary={currentOrder.order?.id} secondary={t('Order ID')} /> <ListItemText primary={currentOrder?.id} secondary={t('Order ID')} />
</Grid> </Grid>
<Grid item xs={7.5}> <Grid item xs={7.5}>
<Grid container> <Grid container>
@ -496,7 +487,7 @@ const OrderDetails = ({
</Grid> </Grid>
<Grid item xs={10}> <Grid item xs={10}>
<ListItemText <ListItemText
primary={timerRenderer(currentOrder.order?.escrow_duration)} primary={timerRenderer(currentOrder?.escrow_duration)}
secondary={t('Deposit timer')} secondary={t('Deposit timer')}
></ListItemText> ></ListItemText>
</Grid> </Grid>
@ -506,9 +497,7 @@ const OrderDetails = ({
</ListItem> </ListItem>
{/* if order is in a status that does not expire, do not show countdown */} {/* if order is in a status that does not expire, do not show countdown */}
<Collapse <Collapse in={![4, 5, 12, 13, 14, 15, 16, 17, 18].includes(currentOrder?.status ?? 0)}>
in={![4, 5, 12, 13, 14, 15, 16, 17, 18].includes(currentOrder.order?.status ?? 0)}
>
<Divider /> <Divider />
<ListItem> <ListItem>
<ListItemIcon> <ListItemIcon>
@ -516,24 +505,24 @@ const OrderDetails = ({
</ListItemIcon> </ListItemIcon>
<ListItemText secondary={t('Expires in')}> <ListItemText secondary={t('Expires in')}>
<Countdown <Countdown
date={new Date(currentOrder.order?.expires_at ?? '')} date={new Date(currentOrder?.expires_at ?? '')}
renderer={countdownRenderer} renderer={countdownRenderer}
/> />
</ListItemText> </ListItemText>
</ListItem> </ListItem>
<LinearDeterminate <LinearDeterminate
totalSecsExp={currentOrder.order?.total_secs_exp ?? 0} totalSecsExp={currentOrder?.total_secs_exp ?? 0}
expiresAt={currentOrder.order?.expires_at ?? ''} expiresAt={currentOrder?.expires_at ?? ''}
/> />
</Collapse> </Collapse>
</List> </List>
{/* If the user has a penalty/limit */} {/* If the user has a penalty/limit */}
{currentOrder.order?.penalty !== undefined ? ( {currentOrder?.penalty !== undefined ? (
<Grid item xs={12}> <Grid item xs={12}>
<Alert severity='warning' sx={{ borderRadius: '0' }}> <Alert severity='warning' sx={{ borderRadius: '0' }}>
<Countdown <Countdown
date={new Date(currentOrder.order?.penalty ?? '')} date={new Date(currentOrder?.penalty ?? '')}
renderer={countdownPenaltyRenderer} renderer={countdownPenaltyRenderer}
/> />
</Alert> </Alert>
@ -542,10 +531,10 @@ const OrderDetails = ({
<></> <></>
)} )}
{!(currentOrder.order?.is_participant === true) ? ( {!currentOrder?.is_participant ? (
<Grid item xs={12}> <Grid item xs={12}>
<TakeButton <TakeButton
baseUrl={baseUrl} currentOrder={currentOrder}
info={coordinator.info} info={coordinator.info}
onClickGenerateRobot={onClickGenerateRobot} onClickGenerateRobot={onClickGenerateRobot}
/> />

View File

@ -102,6 +102,7 @@ const closeAll: OpenDialogProps = {
interface TradeBoxProps { interface TradeBoxProps {
robot: Robot; robot: Robot;
currentOrder: Order;
setBadOrder: (state: string | undefined) => void; setBadOrder: (state: string | undefined) => void;
onRenewOrder: () => void; onRenewOrder: () => void;
onStartAgain: () => void; onStartAgain: () => void;
@ -111,16 +112,17 @@ interface TradeBoxProps {
const TradeBox = ({ const TradeBox = ({
robot, robot,
currentOrder,
settings, settings,
baseUrl, baseUrl,
setBadOrder, setBadOrder,
onRenewOrder, onRenewOrder,
onStartAgain, onStartAgain,
}: TradeBoxProps): JSX.Element => { }: TradeBoxProps): JSX.Element => {
const { currentOrder, setCurrentOrder, federation, focusedCoordinator } = const { federation } = useContext<UseFederationStoreType>(FederationContext);
useContext<UseFederationStoreType>(FederationContext);
const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext); const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
const { origin, hostUrl } = useContext<UseAppStoreType>(AppContext); const { origin, hostUrl } = useContext<UseAppStoreType>(AppContext);
// Buttons and Dialogs // Buttons and Dialogs
const [loadingButtons, setLoadingButtons] = useState<loadingButtonsProps>(noLoadingButtons); const [loadingButtons, setLoadingButtons] = useState<loadingButtonsProps>(noLoadingButtons);
const [open, setOpen] = useState<OpenDialogProps>(closeAll); const [open, setOpen] = useState<OpenDialogProps>(closeAll);
@ -164,7 +166,7 @@ const TradeBox = ({
rating, rating,
}: SubmitActionProps): void { }: SubmitActionProps): void {
const { url } = federation const { url } = federation
.getCoordinator(focusedCoordinator) .getCoordinator(currentOrder.shortAlias)
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl); .getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
void apiClient void apiClient
.post( .post(
@ -193,12 +195,7 @@ const TradeBox = ({
} else if (data.bad_statement !== undefined) { } else if (data.bad_statement !== undefined) {
setDispute({ ...dispute, badStatement: data.bad_statement }); setDispute({ ...dispute, badStatement: data.bad_statement });
} else { } else {
setCurrentOrder((prev) => { garage.updateOrder(data);
return {
...prev,
order: { ...prev.order, ...data },
};
});
setBadOrder(undefined); setBadOrder(undefined);
} }
}) })
@ -325,9 +322,9 @@ const TradeBox = ({
// Effect on Order Status change (used for WebLN) // Effect on Order Status change (used for WebLN)
useEffect(() => { useEffect(() => {
if (currentOrder.order !== null && currentOrder.order.status !== lastOrderStatus) { if (currentOrder !== null && currentOrder.status !== lastOrderStatus) {
setLastOrderStatus(currentOrder.order.status); setLastOrderStatus(currentOrder.status);
void handleWebln(currentOrder.order); void handleWebln(currentOrder);
} }
// FIXME this should trigger with current order, not garage order // FIXME this should trigger with current order, not garage order
}, [orderUpdatedAt]); }, [orderUpdatedAt]);
@ -698,7 +695,7 @@ const TradeBox = ({
return { title, titleVariables, titleColor, prompt, bondStatus, titleIcon }; return { title, titleVariables, titleColor, prompt, bondStatus, titleIcon };
}; };
const contract = currentOrder.order != null ? statusToContract(currentOrder.order) : null; const contract = currentOrder != null ? statusToContract(currentOrder) : null;
return ( return (
<Box> <Box>
@ -708,7 +705,7 @@ const TradeBox = ({
setOpen(closeAll); setOpen(closeAll);
}} }}
waitingWebln={waitingWebln} waitingWebln={waitingWebln}
isBuyer={currentOrder.order?.is_buyer ?? false} isBuyer={currentOrder?.is_buyer ?? false}
/> />
<ConfirmDisputeDialog <ConfirmDisputeDialog
open={open.confirmDispute} open={open.confirmDispute}
@ -731,11 +728,11 @@ const TradeBox = ({
}} }}
onCollabCancelClick={cancel} onCollabCancelClick={cancel}
loading={loadingButtons.cancel} loading={loadingButtons.cancel}
peerAskedCancel={currentOrder.order?.pending_cancel ?? false} peerAskedCancel={currentOrder?.pending_cancel ?? false}
/> />
<ConfirmFiatSentDialog <ConfirmFiatSentDialog
open={open.confirmFiatSent} open={open.confirmFiatSent}
order={currentOrder.order} order={currentOrder}
loadingButton={loadingButtons.fiatSent} loadingButton={loadingButtons.fiatSent}
onClose={() => { onClose={() => {
setOpen(closeAll); setOpen(closeAll);
@ -752,14 +749,14 @@ const TradeBox = ({
/> />
<ConfirmFiatReceivedDialog <ConfirmFiatReceivedDialog
open={open.confirmFiatReceived} open={open.confirmFiatReceived}
order={currentOrder.order} order={currentOrder}
loadingButton={loadingButtons.fiatReceived} loadingButton={loadingButtons.fiatReceived}
onClose={() => { onClose={() => {
setOpen(closeAll); setOpen(closeAll);
}} }}
onConfirmClick={confirmFiatReceived} onConfirmClick={confirmFiatReceived}
/> />
<CollabCancelAlert order={currentOrder.order} /> <CollabCancelAlert order={currentOrder} />
<Grid <Grid
container container
padding={1} padding={1}
@ -770,7 +767,7 @@ const TradeBox = ({
> >
<Grid item> <Grid item>
<Title <Title
order={currentOrder.order} order={currentOrder}
text={contract?.title} text={contract?.title}
color={contract?.titleColor} color={contract?.titleColor}
icon={contract?.titleIcon} icon={contract?.titleIcon}
@ -784,10 +781,7 @@ const TradeBox = ({
{contract?.bondStatus !== 'hide' ? ( {contract?.bondStatus !== 'hide' ? (
<Grid item sx={{ width: '100%' }}> <Grid item sx={{ width: '100%' }}>
<Divider /> <Divider />
<BondStatus <BondStatus status={contract?.bondStatus} isMaker={currentOrder?.is_maker ?? false} />
status={contract?.bondStatus}
isMaker={currentOrder.order?.is_maker ?? false}
/>
</Grid> </Grid>
) : ( ) : (
<></> <></>
@ -795,7 +789,7 @@ const TradeBox = ({
<Grid item> <Grid item>
<CancelButton <CancelButton
order={currentOrder.order} order={currentOrder}
onClickCancel={cancel} onClickCancel={cancel}
openCancelDialog={() => { openCancelDialog={() => {
setOpen({ ...closeAll, confirmCancel: true }); setOpen({ ...closeAll, confirmCancel: true });

View File

@ -48,19 +48,13 @@ export interface fetchRobotProps {
isRefresh?: boolean; isRefresh?: boolean;
} }
export interface CurrentOrder {
shortAlias: string | null;
id: number | null;
order: Order | null;
}
export interface UseFederationStoreType { export interface UseFederationStoreType {
federation: Federation; federation: Federation;
sortedCoordinators: string[]; sortedCoordinators: string[];
focusedCoordinator: string | null; focusedCoordinator: string | null;
setFocusedCoordinator: Dispatch<SetStateAction<string>>; setFocusedCoordinator: Dispatch<SetStateAction<string>>;
currentOrder: CurrentOrder; currentOrder: Order | null;
setCurrentOrder: Dispatch<SetStateAction<CurrentOrder>>; setCurrentOrder: Dispatch<SetStateAction<Order | null>>;
setDelay: Dispatch<SetStateAction<number>>; setDelay: Dispatch<SetStateAction<number>>;
coordinatorUpdatedAt: string; coordinatorUpdatedAt: string;
federationUpdatedAt: string; federationUpdatedAt: string;
@ -70,8 +64,8 @@ export const initialFederationContext: UseFederationStoreType = {
federation: new Federation(), federation: new Federation(),
sortedCoordinators: [], sortedCoordinators: [],
focusedCoordinator: '', focusedCoordinator: '',
currentOrder: null,
setFocusedCoordinator: () => {}, setFocusedCoordinator: () => {},
currentOrder: { shortAlias: null, id: null, order: null },
setCurrentOrder: () => {}, setCurrentOrder: () => {},
setDelay: () => {}, setDelay: () => {},
coordinatorUpdatedAt: '', coordinatorUpdatedAt: '',
@ -83,7 +77,7 @@ export const FederationContext = createContext<UseFederationStoreType>(initialFe
export const useFederationStore = (): UseFederationStoreType => { export const useFederationStore = (): UseFederationStoreType => {
const { settings, page, origin, hostUrl, open, torStatus } = const { settings, page, origin, hostUrl, open, torStatus } =
useContext<UseAppStoreType>(AppContext); useContext<UseAppStoreType>(AppContext);
const { setMaker, garage, robotUpdatedAt, badOrder } = const { setMaker, garage, orderUpdatedAt, robotUpdatedAt, badOrder } =
useContext<UseGarageStoreType>(GarageContext); useContext<UseGarageStoreType>(GarageContext);
const [federation, setFederation] = useState(initialFederationContext.federation); const [federation, setFederation] = useState(initialFederationContext.federation);
const sortedCoordinators = useMemo(() => { const sortedCoordinators = useMemo(() => {
@ -104,9 +98,7 @@ export const useFederationStore = (): UseFederationStoreType => {
const [timer, setTimer] = useState<NodeJS.Timer | undefined>(() => const [timer, setTimer] = useState<NodeJS.Timer | undefined>(() =>
setInterval(() => null, delay), setInterval(() => null, delay),
); );
const [currentOrder, setCurrentOrder] = useState<CurrentOrder>( const [currentOrder, setCurrentOrder] = useState<Order | null>(null);
initialFederationContext.currentOrder,
);
useEffect(() => { useEffect(() => {
// On bitcoin network change we reset book, limits and federation info and fetch everything again // On bitcoin network change we reset book, limits and federation info and fetch everything again
@ -124,8 +116,8 @@ export const useFederationStore = (): UseFederationStoreType => {
}, [settings.network, torStatus]); }, [settings.network, torStatus]);
const fetchCurrentOrder = (): void => { const fetchCurrentOrder = (): void => {
if (currentOrder.id != null && (page === 'order' || badOrder === undefined)) { if (currentOrder?.id != null && (page === 'order' || badOrder === undefined)) {
void federation.fetchOrder(currentOrder, garage.getRobot()); void federation.fetchOrder(currentOrder, garage);
} }
}; };
@ -134,6 +126,10 @@ export const useFederationStore = (): UseFederationStoreType => {
fetchCurrentOrder(); fetchCurrentOrder();
}, [currentOrder, page]); }, [currentOrder, page]);
useEffect(() => {
setCurrentOrder(garage.getOrder());
}, [orderUpdatedAt]);
useEffect(() => { useEffect(() => {
clearInterval(timer); clearInterval(timer);
setTimer(setInterval(fetchCurrentOrder, delay)); setTimer(setInterval(fetchCurrentOrder, delay));

View File

@ -10,7 +10,6 @@ export interface UseGarageStoreType {
setBadOrder: Dispatch<SetStateAction<string | undefined>>; setBadOrder: Dispatch<SetStateAction<string | undefined>>;
robotUpdatedAt: string; robotUpdatedAt: string;
orderUpdatedAt: string; orderUpdatedAt: string;
clearOrder: () => void;
} }
export const initialGarageContext: UseGarageStoreType = { export const initialGarageContext: UseGarageStoreType = {
@ -21,7 +20,6 @@ export const initialGarageContext: UseGarageStoreType = {
setBadOrder: () => {}, setBadOrder: () => {},
robotUpdatedAt: '', robotUpdatedAt: '',
orderUpdatedAt: '', orderUpdatedAt: '',
clearOrder: () => {},
}; };
export const GarageContext = createContext<UseGarageStoreType>(initialGarageContext); export const GarageContext = createContext<UseGarageStoreType>(initialGarageContext);
@ -47,11 +45,6 @@ export const useGarageStore = (): UseGarageStoreType => {
garage.registerHook('onOrderUpdate', onOrderUpdate); garage.registerHook('onOrderUpdate', onOrderUpdate);
}, []); }, []);
const clearOrder = function (): void {
garage.updateOrder(null);
setBadOrder(undefined);
};
return { return {
garage, garage,
maker, maker,
@ -60,6 +53,5 @@ export const useGarageStore = (): UseGarageStoreType => {
setBadOrder, setBadOrder,
robotUpdatedAt, robotUpdatedAt,
orderUpdatedAt, orderUpdatedAt,
clearOrder,
}; };
}; };

View File

@ -9,6 +9,7 @@ import {
import { apiClient } from '../services/api'; import { apiClient } from '../services/api';
import { validateTokenEntropy } from '../utils'; import { validateTokenEntropy } from '../utils';
import { compareUpdateLimit } from './Limit.model'; import { compareUpdateLimit } from './Limit.model';
import { defaultOrder } from './Order.model';
export interface Contact { export interface Contact {
nostr?: string | undefined; nostr?: string | undefined;
@ -122,7 +123,6 @@ export class Coordinator {
public loadingInfo: boolean = false; public loadingInfo: boolean = false;
public limits: LimitList = {}; public limits: LimitList = {};
public loadingLimits: boolean = false; public loadingLimits: boolean = false;
public robot?: Robot | undefined = undefined;
public loadingRobot: boolean = true; public loadingRobot: boolean = true;
start = async ( start = async (
@ -297,10 +297,6 @@ export class Coordinator {
console.log(e); console.log(e);
}); });
if (
newAttributes?.activeOrderId !== null ||
(garage.getRobot(index).activeOrderId === null && newAttributes?.lastOrderId !== null)
) {
garage.updateRobot( garage.updateRobot(
{ {
...newAttributes, ...newAttributes,
@ -308,10 +304,10 @@ export class Coordinator {
loading: false, loading: false,
bitsEntropy, bitsEntropy,
shannonEntropy, shannonEntropy,
shortAlias: this.shortAlias,
}, },
index, index,
); );
}
return garage.getRobot(index); return garage.getRobot(index);
}; };
@ -326,7 +322,12 @@ export class Coordinator {
return await apiClient return await apiClient
.get(this.url, `${this.basePath}/api/order/?order_id=${orderId}`, authHeaders) .get(this.url, `${this.basePath}/api/order/?order_id=${orderId}`, authHeaders)
.then((data) => { .then((data) => {
return data as Order; const order: Order = {
...defaultOrder,
...data,
shortAlias: this.shortAlias,
};
return order;
}) })
.catch((e) => { .catch((e) => {
console.log(e); console.log(e);

View File

@ -4,12 +4,11 @@ import {
type Garage, type Garage,
type Origin, type Origin,
type PublicOrder, type PublicOrder,
type Robot,
type Settings, type Settings,
defaultExchange, defaultExchange,
type Order,
} from '.'; } from '.';
import defaultFederation from '../../static/federation.json'; import defaultFederation from '../../static/federation.json';
import { type CurrentOrder } from '../contexts/FederationContext';
import { updateExchangeInfo } from './Exchange.model'; import { updateExchangeInfo } from './Exchange.model';
type FederationHooks = 'onCoordinatorUpdate' | 'onFederationReady'; type FederationHooks = 'onCoordinatorUpdate' | 'onFederationReady';
@ -97,20 +96,18 @@ export class Federation {
}); });
}; };
fetchOrder = async (currentOrder: CurrentOrder, robot: Robot): Promise<CurrentOrder | null> => { fetchOrder = async (order: Order, garage: Garage): Promise<Order | null> => {
if (currentOrder.shortAlias !== null) { if (order.shortAlias !== null) {
const coordinator = this.coordinators[currentOrder.shortAlias]; const coordinator = this.coordinators[order.shortAlias];
if (coordinator != null && currentOrder.id !== null) { if (coordinator != null && order.id !== null) {
const newOrder = await coordinator.fetchOrder(currentOrder.id, robot); const newOrder = await coordinator.fetchOrder(order.id, garage.getRobot());
if (newOrder) { if (newOrder != null) {
return { garage.updateOrder(newOrder);
...currentOrder, return newOrder;
order: newOrder,
};
} }
} }
} }
return currentOrder; return order;
}; };
// Coordinators // Coordinators

View File

@ -3,9 +3,22 @@ import { systemClient } from '../services/System';
import { saveAsJson } from '../utils'; import { saveAsJson } from '../utils';
export interface Slot { export interface Slot {
robot: Robot; robot: Robot;
lastOrderId: number | null;
lastOrderShortAlias: string | null;
activeOrderId: number | null;
activeOrderShortAlias: string | null;
order: Order | null; order: Order | null;
} }
const defaultSlot = {
robot: new Robot(),
lastOrderId: null,
lastOrderShortAlias: null,
activeOrderId: null,
activeOrderShortAlias: null,
order: null,
};
type GarageHooks = 'onRobotUpdate' | 'onOrderUpdate'; type GarageHooks = 'onRobotUpdate' | 'onOrderUpdate';
class Garage { class Garage {
@ -19,13 +32,17 @@ class Garage {
.map((raw: any) => { .map((raw: any) => {
const robot = new Robot(raw.robot); const robot = new Robot(raw.robot);
robot.update(raw.robot); robot.update(raw.robot);
return { robot, order: raw.order as Order }; return {
...defaultSlot,
...raw,
robot,
};
}); });
console.log('Robot Garage was loaded from local storage'); console.log('Robot Garage was loaded from local storage');
} }
if (this.slots.length < 1) { if (this.slots.length < 1) {
this.slots = [{ robot: new Robot(), order: null }]; this.slots = [defaultSlot];
} }
this.currentSlot = 0; this.currentSlot = 0;
@ -64,7 +81,7 @@ class Garage {
// Slots // Slots
delete = (): void => { delete = (): void => {
this.slots = [{ robot: new Robot(), order: null }]; this.slots = [defaultSlot];
systemClient.deleteItem('garage'); systemClient.deleteItem('garage');
this.triggerHook('onRobotUpdate'); this.triggerHook('onRobotUpdate');
this.triggerHook('onOrderUpdate'); this.triggerHook('onOrderUpdate');
@ -79,9 +96,9 @@ class Garage {
this.save(); this.save();
}; };
getSlot: (index: number) => Slot = (index) => { getSlot: (index?: number) => Slot = (index = this.currentSlot) => {
if (this.slots[index] === undefined) { if (this.slots[index] === undefined) {
this.slots[index] = { robot: new Robot(), order: null }; this.slots[index] = defaultSlot;
} }
return this.slots[index]; return this.slots[index];
@ -95,6 +112,14 @@ class Garage {
const robot = this.getSlot(index).robot; const robot = this.getSlot(index).robot;
if (robot != null) { if (robot != null) {
robot.update(attributes); robot.update(attributes);
if (attributes.activeOrderId != null) {
this.slots[index].activeOrderId = attributes.activeOrderId;
this.slots[index].activeOrderShortAlias = attributes.shortAlias;
}
if (attributes.lastOrderId != null) {
this.slots[index].lastOrderId = attributes.lastOrderId;
this.slots[index].lastOrderShortAlias = attributes.shortAlias;
}
this.triggerHook('onRobotUpdate'); this.triggerHook('onRobotUpdate');
this.save(); this.save();
} }
@ -105,29 +130,32 @@ class Garage {
}; };
createRobot = (attributes: Record<any, any>): void => { createRobot = (attributes: Record<any, any>): void => {
const newSlot = { robot: new Robot(), order: null }; const newSlot = defaultSlot;
newSlot.robot.update(attributes); newSlot.robot.update(attributes);
this.slots.push(newSlot); this.slots.push(newSlot);
this.currentSlot = this.slots.length - 1; this.currentSlot = this.slots.length - 1;
this.save(); this.save();
}; };
// Orders getActiveOrderId: (index?: number) => number | null = (index = this.currentSlot) => {
updateOrder: (order: Order | null, index?: number) => void = ( return this.getSlot(index)?.robot?.activeOrderId ?? null;
order,
index = this.currentSlot,
) => {
const slot = this.getSlot(index);
this.slots[index] = {
...slot,
order,
}; };
// Orders
updateOrder: (order: Order, index?: number) => void = (order, index = this.currentSlot) => {
this.slots[index].order = order;
this.triggerHook('onOrderUpdate'); this.triggerHook('onOrderUpdate');
this.save(); this.save();
}; };
getOrder = (): Order | null => { deleteOrder: (index?: number) => void = (index = this.currentSlot) => {
return this.getSlot(this.currentSlot).order; this.slots[index].order = null;
this.triggerHook('onOrderUpdate');
this.save();
};
getOrder: (index?: number) => Order | null = (index = this.currentSlot) => {
return this.getSlot(index)?.order;
}; };
} }

View File

@ -104,6 +104,120 @@ export interface Order {
tx_queued: boolean; tx_queued: boolean;
address: string; address: string;
network: 'mainnet' | 'testnet'; network: 'mainnet' | 'testnet';
shortAlias: string;
} }
export const defaultOrder: Order = {
shortAlias: '',
id: 0,
status: 0,
created_at: new Date(),
expires_at: new Date(),
type: 0,
currency: 0,
amount: 0,
has_range: false,
min_amount: 0,
max_amount: 0,
payment_method: '',
is_explicit: false,
premium: 0,
satoshis: 0,
maker: 0,
taker: 0,
escrow_duration: 0,
total_secs_exp: 0,
penalty: undefined,
is_maker: false,
is_taker: false,
is_participant: false,
maker_status: 'Active',
taker_status: 'Active',
price_now: undefined,
satoshis_now: 0,
latitude: 0,
longitude: 0,
premium_now: undefined,
premium_percentile: 0,
num_similar_orders: 0,
tg_enabled: false,
tg_token: '',
tg_bot_name: '',
is_buyer: false,
is_seller: false,
maker_nick: '',
taker_nick: '',
status_message: '',
is_fiat_sent: false,
is_disputed: false,
ur_nick: '',
maker_locked: false,
taker_locked: false,
escrow_locked: false,
trade_satoshis: 0,
bond_invoice: '',
bond_satoshis: 0,
escrow_invoice: '',
escrow_satoshis: 0,
invoice_amount: 0,
swap_allowed: false,
swap_failure_reason: '',
suggested_mining_fee_rate: 0,
swap_fee_rate: 0,
pending_cancel: false,
asked_for_cancel: false,
statement_submitted: false,
retries: 0,
next_retry_time: new Date(),
failure_reason: '',
invoice_expired: false,
public_duration: 0,
bond_size: '',
trade_fee_percent: 0,
bond_size_sats: 0,
bond_size_percent: 0,
chat_last_index: 0,
maker_summary: {
is_buyer: false,
sent_fiat: 0,
received_sats: 0,
is_swap: false,
received_onchain_sats: 0,
mining_fee_sats: 0,
swap_fee_sats: 0,
swap_fee_percent: 0,
sent_sats: 0,
received_fiat: 0,
trade_fee_sats: 0,
},
taker_summary: {
is_buyer: false,
sent_fiat: 0,
received_sats: 0,
is_swap: false,
received_onchain_sats: 0,
mining_fee_sats: 0,
swap_fee_sats: 0,
swap_fee_percent: 0,
sent_sats: 0,
received_fiat: 0,
trade_fee_sats: 0,
},
platform_summary: {
contract_timestamp: new Date(),
contract_total_time: 0,
contract_exchange_rate: 0,
routing_budget_sats: 0,
trade_revenue_sats: 0,
},
expiry_reason: 0,
expiry_message: '',
num_satoshis: 0,
sent_satoshis: 0,
txid: '',
tx_queued: false,
address: '',
network: 'mainnet',
};
export default Order; export default Order;

View File

@ -39,6 +39,7 @@ class Robot {
public last_login: string = ''; public last_login: string = '';
public copiedToken: boolean = false; public copiedToken: boolean = false;
public avatarLoaded: boolean = false; public avatarLoaded: boolean = false;
public shortAlias: string = '';
update = (attributes: Record<string, any>): void => { update = (attributes: Record<string, any>): void => {
Object.assign(this, attributes); Object.assign(this, attributes);