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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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