diff --git a/frontend/src/basic/MainDialogs/index.tsx b/frontend/src/basic/MainDialogs/index.tsx index 7967ef8c..a86610f3 100644 --- a/frontend/src/basic/MainDialogs/index.tsx +++ b/frontend/src/basic/MainDialogs/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useContext, useEffect } from 'react'; +import React, { useContext } from 'react'; import { CommunityDialog, ExchangeDialog, @@ -9,16 +9,16 @@ import { ClientDialog, UpdateDialog, } from '../../components/Dialogs'; -import { pn } from '../../utils'; import { AppContext, type UseAppStoreType, closeAll } from '../../contexts/AppContext'; import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext'; +import { UseGarageStoreType, GarageContext } from '../../contexts/GarageContext'; export interface OpenDialogs { more: boolean; learn: boolean; community: boolean; info: boolean; - coordinator: boolean; + coordinator: string; exchange: boolean; client: boolean; update: boolean; @@ -29,19 +29,8 @@ export interface OpenDialogs { const MainDialogs = (): JSX.Element => { const { open, setOpen, settings, clientVersion, hostUrl } = useContext(AppContext); - const { federation, focusedCoordinator, coordinatorUpdatedAt } = - useContext(FederationContext); - - const [maxAmount, setMaxAmount] = useState('...loading...'); - - useEffect(() => { - if (focusedCoordinator !== null && focusedCoordinator !== '') { - const limits = federation.getCoordinator(focusedCoordinator).limits; - if (limits[1000] !== undefined) { - setMaxAmount(pn(limits[1000].max_amount * 100000000)); - } - } - }, [coordinatorUpdatedAt]); + const { federation } = useContext(FederationContext); + const { garage } = useContext(GarageContext); return ( <> @@ -56,7 +45,7 @@ const MainDialogs = (): JSX.Element => { /> { setOpen((open) => { return { ...open, info: false }; @@ -103,16 +92,12 @@ const MainDialogs = (): JSX.Element => { }} /> { setOpen(closeAll); }} - coordinator={ - focusedCoordinator !== null && focusedCoordinator !== '' - ? federation.getCoordinator(focusedCoordinator) - : null - } + shortAlias={open.coordinator} /> ); diff --git a/frontend/src/basic/OrderPage/index.tsx b/frontend/src/basic/OrderPage/index.tsx index f8bd4b25..89e7f11d 100644 --- a/frontend/src/basic/OrderPage/index.tsx +++ b/frontend/src/basic/OrderPage/index.tsx @@ -6,7 +6,6 @@ import { useNavigate, useParams } from 'react-router-dom'; import TradeBox from '../../components/TradeBox'; import OrderDetails from '../../components/OrderDetails'; -import { apiClient } from '../../services/api'; import { AppContext, type UseAppStoreType } from '../../contexts/AppContext'; import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext'; import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext'; @@ -15,8 +14,7 @@ import { type Order } from '../../models'; const OrderPage = (): JSX.Element => { const { windowSize, setOpen, settings, navbarHeight, hostUrl, origin } = useContext(AppContext); - const { setFocusedCoordinator, federation, focusedCoordinator } = - useContext(FederationContext); + const { federation } = useContext(FederationContext); const { garage, badOrder, setBadOrder } = useContext(GarageContext); const { t } = useTranslation(); const navigate = useNavigate(); @@ -40,32 +38,18 @@ const OrderPage = (): JSX.Element => { setBaseUrl(`${url}${basePath}`); - if (garage.getSlot().activeOrderId === Number(params.orderId)) { - if (garage.getSlot().order != null) { - setCurrentOrder(garage.getSlot().order); - } else { - coordinator - .fetchOrder(Number(params.orderId) ?? null, garage.getSlot().robot) - .then((order) => { - if (order?.bad_request !== undefined) { - setBadOrder(order.bad_request); - } else { - setCurrentOrder(order); - garage.updateOrder(order as Order); - } - }) - .catch((e) => { - console.log(e); - }); - } - } else { + if (currentOrder?.id !== Number(params.orderId)) { + const coordinator = federation.getCoordinator(params.shortAlias ?? ''); coordinator .fetchOrder(Number(params.orderId) ?? null, garage.getSlot().robot) .then((order) => { if (order?.bad_request !== undefined) { setBadOrder(order.bad_request); - } else { + } else if (order !== null && order?.id !== null) { setCurrentOrder(order); + if (order.is_participant) { + garage.updateOrder(order as Order); + } } }) .catch((e) => { @@ -76,50 +60,9 @@ const OrderPage = (): JSX.Element => { const onClickCoordinator = function (): void { if (currentOrder?.shortAlias != null) { - setFocusedCoordinator(currentOrder.shortAlias); - } - setOpen((open) => { - return { ...open, coordinator: true }; - }); - }; - - const renewOrder = function (): void { - const order = currentOrder; - if (order !== null && focusedCoordinator != null) { - const body = { - type: order.type, - currency: order.currency, - amount: order.has_range ? null : order.amount, - has_range: order.has_range, - min_amount: order.min_amount, - max_amount: order.max_amount, - payment_method: order.payment_method, - is_explicit: order.is_explicit, - premium: order.is_explicit ? null : order.premium, - satoshis: order.is_explicit ? order.satoshis : null, - public_duration: order.public_duration, - escrow_duration: order.escrow_duration, - bond_size: order.bond_size, - latitude: order.latitude, - longitude: order.longitude, - }; - const { url, basePath } = federation - .getCoordinator(order.shortAlias) - .getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl); - apiClient - .post(url + basePath, '/api/make/', body, { - tokenSHA256: garage.getSlot().robot.tokenSHA256, - }) - .then((data: any) => { - if (data.bad_request !== undefined) { - setBadOrder(data.bad_request); - } else if (data.id !== undefined) { - navigate(`/order/${String(currentOrder?.shortAlias)}/${String(data.id)}`); - } - }) - .catch(() => { - setBadOrder('Request error'); - }); + setOpen((open) => { + return { ...open, coordinator: shortAlias }; + }); } }; @@ -127,6 +70,26 @@ const OrderPage = (): JSX.Element => { navigate('/robot'); }; + const orderDetailsSpace = currentOrder ? ( + { + navigate('/robot'); + }} + /> + ) : ( + <> + ); + + const tradeBoxSpace = currentOrder ? ( + + ) : ( + <> + ); + return ( {currentOrder === null && badOrder === undefined && } @@ -156,15 +119,7 @@ const OrderPage = (): JSX.Element => { overflow: 'auto', }} > - { - navigate('/robot'); - }} - /> + {orderDetailsSpace} @@ -176,15 +131,7 @@ const OrderPage = (): JSX.Element => { overflow: 'auto', }} > - + {tradeBoxSpace} @@ -194,7 +141,7 @@ const OrderPage = (): JSX.Element => { { + onChange={(_mouseEvent, value) => { setTab(value); }} variant='fullWidth' @@ -211,28 +158,8 @@ const OrderPage = (): JSX.Element => { overflow: 'auto', }} > -
- { - navigate('/robot'); - }} - /> -
-
- -
+
{orderDetailsSpace}
+
{tradeBoxSpace}
) @@ -245,15 +172,7 @@ const OrderPage = (): JSX.Element => { overflow: 'auto', }} > - { - navigate('/robot'); - }} - /> + {orderDetailsSpace} ) ) : ( diff --git a/frontend/src/basic/SettingsPage/index.tsx b/frontend/src/basic/SettingsPage/index.tsx index f9204f3f..2645ab08 100644 --- a/frontend/src/basic/SettingsPage/index.tsx +++ b/frontend/src/basic/SettingsPage/index.tsx @@ -25,14 +25,7 @@ const SettingsPage = (): JSX.Element => { - { - setOpen({ ...open, coordinator: true }); - }} - baseUrl={hostUrl} - maxHeight={14} - network={settings.network} - /> + diff --git a/frontend/src/components/BookTable/index.tsx b/frontend/src/components/BookTable/index.tsx index 9a65a9c1..aa0fddb2 100644 --- a/frontend/src/components/BookTable/index.tsx +++ b/frontend/src/components/BookTable/index.tsx @@ -108,7 +108,7 @@ const BookTable = ({ }: BookTableProps): JSX.Element => { const { fav, setFav, settings, setOpen, hostUrl, origin } = useContext(AppContext); - const { federation, setFocusedCoordinator, coordinatorUpdatedAt } = + const { federation, coordinatorUpdatedAt } = useContext(FederationContext); const { t } = useTranslation(); @@ -273,9 +273,8 @@ const BookTable = ({ }, []); const onClickCoordinator = function (shortAlias: string): void { - setFocusedCoordinator(shortAlias); setOpen((open) => { - return { ...open, coordinator: true }; + return { ...open, coordinator: shortAlias }; }); }; diff --git a/frontend/src/components/Dialogs/Coordinator.tsx b/frontend/src/components/Dialogs/Coordinator.tsx index 9981dbea..6d422ca3 100644 --- a/frontend/src/components/Dialogs/Coordinator.tsx +++ b/frontend/src/components/Dialogs/Coordinator.tsx @@ -62,11 +62,12 @@ import { import { AppContext } from '../../contexts/AppContext'; import { systemClient } from '../../services/System'; import { type Badges } from '../../models/Coordinator.model'; +import { UseFederationStoreType, FederationContext } from '../../contexts/FederationContext'; interface Props { open: boolean; onClose: () => void; - coordinator: Coordinator | null; + shortAlias: string | null; network: 'mainnet' | 'testnet' | undefined; } @@ -335,9 +336,11 @@ const BadgesHall = ({ badges }: BadgesProps): JSX.Element => { ); }; -const CoordinatorDialog = ({ open = false, onClose, coordinator, network }: Props): JSX.Element => { +const CoordinatorDialog = ({ open = false, onClose, network, shortAlias }: Props): JSX.Element => { const { t } = useTranslation(); const { clientVersion, page, hostUrl } = useContext(AppContext); + const { federation } = useContext(FederationContext); + const coordinator = federation.getCoordinator(shortAlias); const [expanded, setExpanded] = useState<'summary' | 'stats' | 'policies' | undefined>(undefined); diff --git a/frontend/src/components/FederationTable/index.tsx b/frontend/src/components/FederationTable/index.tsx index ce809cbe..8958afc9 100644 --- a/frontend/src/components/FederationTable/index.tsx +++ b/frontend/src/components/FederationTable/index.tsx @@ -9,22 +9,20 @@ import { AppContext, type UseAppStoreType } from '../../contexts/AppContext'; import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext'; interface FederationTableProps { - openCoordinator: () => void; maxWidth?: number; maxHeight?: number; fillContainer?: boolean; } const FederationTable = ({ - openCoordinator, maxWidth = 90, maxHeight = 50, fillContainer = false, }: FederationTableProps): JSX.Element => { const { t } = useTranslation(); - const { federation, sortedCoordinators, setFocusedCoordinator, coordinatorUpdatedAt } = + const { federation, sortedCoordinators, coordinatorUpdatedAt } = useContext(FederationContext); - const { hostUrl } = useContext(AppContext); + const { hostUrl, setOpen } = useContext(AppContext); const theme = useTheme(); const [pageSize, setPageSize] = useState(0); @@ -52,8 +50,9 @@ const FederationTable = ({ }; const onClickCoordinator = function (shortAlias: string): void { - setFocusedCoordinator(shortAlias); - openCoordinator(); + setOpen((open) => { + return { ...open, coordinator: shortAlias }; + }); }; const aliasObj = useCallback((width: number) => { diff --git a/frontend/src/components/MakerForm/MakerForm.tsx b/frontend/src/components/MakerForm/MakerForm.tsx index c01d24e7..1f49b361 100644 --- a/frontend/src/components/MakerForm/MakerForm.tsx +++ b/frontend/src/components/MakerForm/MakerForm.tsx @@ -68,7 +68,7 @@ const MakerForm = ({ onClickGenerateRobot = () => null, }: MakerFormProps): JSX.Element => { const { fav, setFav, settings, hostUrl, origin } = useContext(AppContext); - const { federation, focusedCoordinator, coordinatorUpdatedAt, federationUpdatedAt } = + const { federation, coordinatorUpdatedAt, federationUpdatedAt } = useContext(FederationContext); const { maker, setMaker, garage } = useContext(GarageContext); @@ -93,8 +93,8 @@ const MakerForm = ({ useEffect(() => { setCurrencyCode(currencyDict[fav.currency === 0 ? 1 : fav.currency]); - if (focusedCoordinator != null) { - const newLimits = federation.getCoordinator(focusedCoordinator).limits; + if (maker.coordinator != null) { + const newLimits = federation.getCoordinator(maker.coordinator).limits; if (Object.keys(newLimits).length !== 0) { updateAmountLimits(newLimits, fav.currency, maker.premium); updateCurrentPrice(newLimits, fav.currency, maker.premium); @@ -285,15 +285,9 @@ const MakerForm = ({ .getCoordinator(maker.coordinator) ?.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl) ?? {}; - const auth = { - tokenSHA256: garage.getSlot().robot.tokenSHA256, - keys: { - pubKey: garage.getSlot().robot.pubKey?.split('\n').join('\\'), - encPrivKey: garage.getSlot().robot.encPrivKey?.split('\n').join('\\'), - }, - }; + const auth = garage.getSlot().robot.getAuthHeaders(); - if (!disableRequest && focusedCoordinator != null) { + if (!disableRequest && maker.coordinator != null && auth !== null) { setSubmittingRequest(true); const body = { type: fav.type === 0 ? 1 : 0, @@ -320,6 +314,7 @@ const MakerForm = ({ setBadRequest(data.bad_request); if (data.id !== undefined) { onOrderCreated(maker.coordinator, data.id); + garage.updateOrder(data); } setSubmittingRequest(false); }) @@ -446,9 +441,9 @@ const MakerForm = ({ }; const amountLabel = useMemo(() => { - if (!(focusedCoordinator != null)) return; + if (!(maker.coordinator != null)) return; - const info = federation.getCoordinator(focusedCoordinator)?.info; + const info = federation.getCoordinator(maker.coordinator)?.info; const defaultRoutingBudget = 0.001; let label = t('Amount'); let helper = ''; diff --git a/frontend/src/components/MakerForm/SelectCoordinator.tsx b/frontend/src/components/MakerForm/SelectCoordinator.tsx index 16136d40..3152f281 100644 --- a/frontend/src/components/MakerForm/SelectCoordinator.tsx +++ b/frontend/src/components/MakerForm/SelectCoordinator.tsx @@ -22,15 +22,13 @@ interface SelectCoordinatorProps { const SelectCoordinator: React.FC = ({ coordinator, setCoordinator }) => { const { setOpen, hostUrl } = useContext(AppContext); - const { federation, setFocusedCoordinator, sortedCoordinators } = - useContext(FederationContext); + const { federation, sortedCoordinators } = useContext(FederationContext); const theme = useTheme(); const { t } = useTranslation(); const onClickCurrentCoordinator = function (shortAlias: string): void { - setFocusedCoordinator(shortAlias); setOpen((open) => { - return { ...open, coordinator: true }; + return { ...open, coordinator: shortAlias }; }); }; diff --git a/frontend/src/components/RobotAvatar/index.tsx b/frontend/src/components/RobotAvatar/index.tsx index d49d8bda..7d4b89ae 100644 --- a/frontend/src/components/RobotAvatar/index.tsx +++ b/frontend/src/components/RobotAvatar/index.tsx @@ -47,9 +47,6 @@ const RobotAvatar: React.FC = ({ coordinator = false, baseUrl, }) => { - const { settings, origin, hostUrl } = useContext(AppContext); - const { federation, focusedCoordinator } = useContext(FederationContext); - const [avatarSrc, setAvatarSrc] = useState(); const [nicknameReady, setNicknameReady] = useState(false); const [activeBackground, setActiveBackground] = useState(true); @@ -66,13 +63,10 @@ const RobotAvatar: React.FC = ({ if (window.NativeRobosats === undefined) { setAvatarSrc(`${baseUrl}${path}${nickname}${small ? '.small' : ''}.webp`); setNicknameReady(true); - } else if (focusedCoordinator != null) { + } else if (baseUrl != null && apiClient.fileImageUrl !== undefined) { setNicknameReady(true); - const { url, basePath } = federation - .getCoordinator(focusedCoordinator) - .getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl); void apiClient - .fileImageUrl(url + basePath, `${path}${nickname}${small ? '.small' : ''}.webp`) + .fileImageUrl(baseUrl, `${path}${nickname}${small ? '.small' : ''}.webp`) .then(setAvatarSrc); } } else { diff --git a/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx index 4c695b50..07a47db9 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/EncryptedSocketChat/index.tsx @@ -25,7 +25,6 @@ const audioPath = interface Props { orderId: number; status: number; - robot: Robot; userNick: string; takerNick: string; messages: EncryptedChatMessage[]; @@ -38,7 +37,6 @@ interface Props { const EncryptedSocketChat: React.FC = ({ orderId, status, - robot, userNick, takerNick, messages, @@ -110,7 +108,7 @@ const EncryptedSocketChat: React.FC = ({ setConnected(true); connection.send({ - message: robot.pubKey, + message: garage.getSlot().robot.pubKey, nick: userNick, }); @@ -132,10 +130,10 @@ const EncryptedSocketChat: React.FC = ({ const createJsonFile: () => object = () => { return { credentials: { - own_public_key: robot.pubKey, + own_public_key: garage.getSlot().robot.pubKey, peer_public_key: peerPubKey, - encrypted_private_key: robot.encPrivKey, - passphrase: robot.token, + encrypted_private_key: garage.getSlot().robot.encPrivKey, + passphrase: garage.getSlot().robot.token, }, messages, }; @@ -143,7 +141,7 @@ const EncryptedSocketChat: React.FC = ({ const onMessage: (message: any) => void = (message) => { const dataFromServer = JSON.parse(message.data); - + const robot = garage.getSlot().robot; if (dataFromServer != null && !receivedIndexes.includes(dataFromServer.index)) { setReceivedIndexes((prev) => [...prev, dataFromServer.index]); setPeerConnected(dataFromServer.peer_connected); @@ -213,6 +211,7 @@ const EncryptedSocketChat: React.FC = ({ }; const onButtonClicked = (e: React.FormEvent): void => { + const robot = garage.getSlot().robot; if (robot.token !== undefined && value.includes(robot.token)) { alert( `Aye! You just sent your own robot robot.token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`, @@ -264,10 +263,10 @@ const EncryptedSocketChat: React.FC = ({ }} orderId={Number(orderId)} messages={messages} - ownPubKey={robot.pubKey ?? ''} - ownEncPrivKey={robot.encPrivKey ?? ''} + ownPubKey={garage.getSlot().robot.pubKey ?? ''} + ownEncPrivKey={garage.getSlot().robot.encPrivKey ?? ''} peerPubKey={peerPubKey ?? 'Not received yet'} - passphrase={robot.token ?? ''} + passphrase={garage.getSlot().robot.token ?? ''} onClickBack={() => { setAudit(false); }} diff --git a/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx index 8f5c9991..7ef7c08a 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, TextField, Grid, Paper, Typography } from '@mui/material'; import { encryptMessage, decryptMessage } from '../../../../pgp'; @@ -14,10 +14,12 @@ import ChatHeader from '../ChatHeader'; import { type EncryptedChatMessage, type ServerMessage } from '..'; import { apiClient } from '../../../../services/api'; import ChatBottom from '../ChatBottom'; +import { UseAppStoreType, AppContext } from '../../../../contexts/AppContext'; +import { UseFederationStoreType, FederationContext } from '../../../../contexts/FederationContext'; +import { UseGarageStoreType, GarageContext } from '../../../../contexts/GarageContext'; interface Props { orderId: number; - robot: Robot; userNick: string; takerNick: string; chatOffset: number; @@ -35,7 +37,6 @@ const audioPath = const EncryptedTurtleChat: React.FC = ({ orderId, - robot, userNick, takerNick, chatOffset, @@ -48,7 +49,8 @@ const EncryptedTurtleChat: React.FC = ({ const { t } = useTranslation(); const theme = useTheme(); const { origin, hostUrl, settings } = useContext(AppContext); - const { federation, focusedCoordinator } = useContext(FederationContext); + const { federation } = useContext(FederationContext); + const { garage } = useContext(GarageContext); const [audio] = useState(() => new Audio(`${audioPath}/chat-open.mp3`)); const [peerConnected, setPeerConnected] = useState(false); @@ -83,11 +85,11 @@ const EncryptedTurtleChat: React.FC = ({ const loadMessages: () => void = () => { const { url, basePath } = federation - .getCoordinator(focusedCoordinator) + .getCoordinator(garage.getSlot().activeOrderShortAlias) .getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl); apiClient .get(url + basePath, `/api/chat/?order_id=${orderId}&offset=${lastIndex}`, { - tokenSHA256: robot.tokenSHA256, + tokenSHA256: garage.getSlot().robot.tokenSHA256, }) .then((results: any) => { if (results != null) { @@ -104,16 +106,17 @@ const EncryptedTurtleChat: React.FC = ({ const createJsonFile = (): object => { return { credentials: { - own_public_key: robot.pubKey, + own_public_key: garage.getSlot().robot.pubKey, peer_public_key: peerPubKey, - encrypted_private_key: robot.encPrivKey, - passphrase: robot.token, + encrypted_private_key: garage.getSlot().robot.encPrivKey, + passphrase: garage.getSlot().robot.token, }, messages, }; }; const onMessage = (dataFromServer: ServerMessage): void => { + const robot = garage.getSlot().robot; if (dataFromServer != null) { // If we receive an encrypted message if (dataFromServer.message.substring(0, 27) === `-----BEGIN PGP MESSAGE-----`) { @@ -169,6 +172,7 @@ const EncryptedTurtleChat: React.FC = ({ }; const onButtonClicked = (e: React.FormEvent): void => { + const robot = garage.getSlot().robot; if (robot.token !== undefined && value.includes(robot.token)) { alert( `Aye! You just sent your own robot robot.token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`, @@ -178,7 +182,7 @@ const EncryptedTurtleChat: React.FC = ({ // If input string contains '#' send unencrypted and unlogged message else if (value.substring(0, 1) === '#') { const { url, basePath } = federation - .getCoordinator(focusedCoordinator) + .getCoordinator(garage.getSlot().activeOrderShortAlias ?? '') .getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl); apiClient .post( @@ -211,7 +215,7 @@ const EncryptedTurtleChat: React.FC = ({ encryptMessage(value, robot.pubKey, peerPubKey, robot.encPrivKey, robot.token) .then((encryptedMessage) => { const { url, basePath } = federation - .getCoordinator(focusedCoordinator) + .getCoordinator(garage.getSlot().activeOrderShortAlias ?? '') .getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl); apiClient .post( @@ -259,10 +263,10 @@ const EncryptedTurtleChat: React.FC = ({ }} orderId={Number(orderId)} messages={messages} - ownPubKey={robot.pubKey ?? ''} - ownEncPrivKey={robot.encPrivKey ?? ''} + ownPubKey={garage.getSlot().robot.pubKey ?? ''} + ownEncPrivKey={garage.getSlot().robot.encPrivKey ?? ''} peerPubKey={peerPubKey ?? 'Not received yet'} - passphrase={robot.token ?? ''} + passphrase={garage.getSlot().robot.token ?? ''} onClickBack={() => { setAudit(false); }} diff --git a/frontend/src/components/TradeBox/EncryptedChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/index.tsx index 5970d043..9b2a0106 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/index.tsx @@ -35,7 +35,6 @@ export interface ServerMessage { const EncryptedChat: React.FC = ({ orderId, takerNick, - robot, userNick, chatOffset, baseUrl, @@ -48,7 +47,6 @@ const EncryptedChat: React.FC = ({ return turtleMode ? ( = ({ void; onClickUndoConfirmSent: () => void; loadingSent: boolean; @@ -27,7 +27,6 @@ interface ChatPromptProps { export const ChatPrompt = ({ order, - robot, onClickConfirmSent, onClickUndoConfirmSent, onClickConfirmReceived, @@ -41,6 +40,7 @@ export const ChatPrompt = ({ setMessages, }: ChatPromptProps): JSX.Element => { const { t } = useTranslation(); + const { garage } = useContext(GarageContext); const [sentButton, setSentButton] = useState(false); const [receivedButton, setReceivedButton] = useState(false); @@ -49,9 +49,9 @@ export const ChatPrompt = ({ const [enableDisputeTime, setEnableDisputeTime] = useState(new Date(order.expires_at)); const [text, setText] = useState(''); - const currencyCode: string = currencies[`${order.currency}`]; + const currencyCode: string = currencies[`${garage.getSlot().order.currency}`]; const amount: string = pn( - parseFloat(parseFloat(order.amount).toFixed(order.currency === 1000 ? 8 : 4)), + parseFloat(parseFloat(garage.getSlot().order.amount).toFixed(order.currency === 1000 ? 8 : 4)), ); const disputeCountdownRenderer = function ({ @@ -133,7 +133,6 @@ export const ChatPrompt = ({ void; - onRenewOrder: () => void; - onStartAgain: () => void; - settings: Settings; baseUrl: string; + onStartAgain: () => void; } -const TradeBox = ({ - robot, - currentOrder, - settings, - baseUrl, - setBadOrder, - onRenewOrder, - onStartAgain, -}: TradeBoxProps): JSX.Element => { +interface Contract { + title: string; + titleVariables: object; + titleColor: string; + prompt: () => JSX.Element; + bondStatus: 'hide' | 'locked' | 'unlocked' | 'settled'; + titleIcon: () => JSX.Element; +} + +const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => { + const { garage, orderUpdatedAt, setBadOrder } = useContext(GarageContext); + const { settings, hostUrl, origin } = useContext(AppContext); const { federation } = useContext(FederationContext); - const { garage, orderUpdatedAt } = useContext(GarageContext); - const { origin, hostUrl } = useContext(AppContext); + const navigate = useNavigate(); // Buttons and Dialogs const [loadingButtons, setLoadingButtons] = useState(noLoadingButtons); @@ -156,6 +154,46 @@ const TradeBox = ({ rating?: number; } + const renewOrder = function (): void { + const currentOrder = garage.getSlot().order; + if (currentOrder !== null) { + const body = { + type: currentOrder.type, + currency: currentOrder.currency, + amount: currentOrder.has_range ? null : currentOrder.amount, + has_range: currentOrder.has_range, + min_amount: currentOrder.min_amount, + max_amount: currentOrder.max_amount, + payment_method: currentOrder.payment_method, + is_explicit: currentOrder.is_explicit, + premium: currentOrder.is_explicit ? null : currentOrder.premium, + satoshis: currentOrder.is_explicit ? currentOrder.satoshis : null, + public_duration: currentOrder.public_duration, + escrow_duration: currentOrder.escrow_duration, + bond_size: currentOrder.bond_size, + latitude: currentOrder.latitude, + longitude: currentOrder.longitude, + }; + const { url, basePath } = federation + .getCoordinator(currentOrder.shortAlias) + .getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl); + apiClient + .post(url + basePath, '/api/make/', body, { + tokenSHA256: garage.getSlot().robot.tokenSHA256, + }) + .then((data: any) => { + if (data.bad_request !== undefined) { + setBadOrder(data.bad_request); + } else if (data.id !== undefined) { + navigate(`/order/${String(currentOrder?.shortAlias)}/${String(data.id)}`); + } + }) + .catch(() => { + setBadOrder('Request error'); + }); + } + }; + const submitAction = function ({ action, invoice, @@ -165,13 +203,13 @@ const TradeBox = ({ statement, rating, }: SubmitActionProps): void { - const { url, basePath } = federation - .getCoordinator(currentOrder.shortAlias) - .getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl); + const robot = garage.getSlot().robot; + const currentOrder = garage.getSlot().order; + void apiClient .post( - url + basePath, - `/api/order/?order_id=${Number(currentOrder.id)}`, + baseUrl, + `/api/order/?order_id=${Number(currentOrder?.id)}`, { action, invoice, @@ -268,6 +306,7 @@ const TradeBox = ({ }; const submitStatement = function (): void { + const robot = garage.getSlot().robot; let statement = dispute.statement; if (dispute.attachLogs) { const payload = { statement, messages, token: robot.token }; @@ -322,44 +361,48 @@ const TradeBox = ({ // Effect on Order Status change (used for WebLN) useEffect(() => { + const currentOrder = garage.getSlot().order; if (currentOrder !== null && currentOrder.status !== lastOrderStatus) { setLastOrderStatus(currentOrder.status); void handleWebln(currentOrder); } - // FIXME this should trigger with current order, not garage order }, [orderUpdatedAt]); - const statusToContract = function (order: Order): JSX.Element { + const statusToContract = function (): Contract { + const order = garage.getSlot().order; + + const baseContract: Contract = { + title: 'Unknown Order Status', + titleVariables: {}, + titleColor: 'primary', + prompt: () => Wops!, + bondStatus: 'hide', + titleIcon: () => <>, + }; + + if (order === null) return baseContract; + const status = order.status; const isBuyer = order.is_buyer; const isMaker = order.is_maker; - let title: string = 'Unknown Order Status'; - let titleVariables: object = {}; - let titleColor: string = 'primary'; - let titleIcon: () => JSX.Element = function () { - return <>; - }; - let prompt = (): JSX.Element => Wops!; - let bondStatus: 'hide' | 'locked' | 'unlocked' | 'settled' = 'hide'; - switch (status) { // 0: 'Waiting for maker bond' case 0: if (isMaker) { - title = 'Lock {{amountSats}} Sats to PUBLISH order'; - titleVariables = { amountSats: pn(order.bond_satoshis) }; - prompt = () => { + baseContract.title = 'Lock {{amountSats}} Sats to PUBLISH order'; + baseContract.titleVariables = { amountSats: pn(order.bond_satoshis) }; + baseContract.prompt = () => { return ; }; - bondStatus = 'hide'; + baseContract.bondStatus = 'hide'; } break; // 1: 'Public' case 1: if (isMaker) { - title = 'Your order is public'; - prompt = () => { + baseContract.title = 'Your order is public'; + baseContract.prompt = () => { return ( ); }; - bondStatus = 'locked'; + baseContract.bondStatus = 'locked'; } break; // 2: 'Paused' case 2: if (isMaker) { - title = 'Your order is paused'; - prompt = () => { + baseContract.title = 'Your order is paused'; + baseContract.prompt = () => { return ( ); }; - bondStatus = 'locked'; + baseContract.bondStatus = 'locked'; } break; // 3: 'Waiting for taker bond' case 3: if (isMaker) { - title = 'A taker has been found!'; - prompt = () => { + baseContract.title = 'A taker has been found!'; + baseContract.prompt = () => { return ; }; - bondStatus = 'locked'; + baseContract.bondStatus = 'locked'; } else { - title = 'Lock {{amountSats}} Sats to TAKE order'; - titleVariables = { amountSats: pn(order.bond_satoshis) }; - prompt = () => { + baseContract.title = 'Lock {{amountSats}} Sats to TAKE order'; + baseContract.titleVariables = { amountSats: pn(order.bond_satoshis) }; + baseContract.prompt = () => { return ; }; - bondStatus = 'hide'; + baseContract.bondStatus = 'hide'; } break; // 5: 'Expired' case 5: - title = 'The order has expired'; - prompt = () => { + baseContract.title = 'The order has expired'; + baseContract.prompt = () => { return ( { - onRenewOrder(); + renewOrder(); setLoadingButtons({ ...noLoadingButtons, renewOrder: true }); }} /> ); }; - bondStatus = 'hide'; // To do: show bond status according to expiry message. + baseContract.bondStatus = 'hide'; // To do: show bond status according to expiry message. break; // 6: 'Waiting for trade collateral and buyer invoice' case 6: - bondStatus = 'locked'; + baseContract.bondStatus = 'locked'; if (isBuyer) { - title = 'Submit payout info'; - titleVariables = { amountSats: pn(order.invoice_amount) }; - prompt = function () { + baseContract.title = 'Submit payout info'; + baseContract.titleVariables = { amountSats: pn(order.invoice_amount) }; + baseContract.prompt = function () { return ( { + baseContract.title = 'Lock {{amountSats}} Sats as collateral'; + baseContract.titleVariables = { amountSats: pn(order.escrow_satoshis) }; + baseContract.titleColor = 'warning'; + baseContract.prompt = () => { return ; }; } @@ -457,17 +500,17 @@ const TradeBox = ({ // 7: 'Waiting only for seller trade collateral' case 7: - bondStatus = 'locked'; + baseContract.bondStatus = 'locked'; if (isBuyer) { - title = 'Your info looks good!'; - prompt = () => { + baseContract.title = 'Your info looks good!'; + baseContract.prompt = () => { return ; }; } else { - title = 'Lock {{amountSats}} Sats as collateral'; - titleVariables = { amountSats: pn(order.escrow_satoshis) }; - titleColor = 'warning'; - prompt = () => { + baseContract.title = 'Lock {{amountSats}} Sats as collateral'; + baseContract.titleVariables = { amountSats: pn(order.escrow_satoshis) }; + baseContract.titleColor = 'warning'; + baseContract.prompt = () => { return ; }; } @@ -475,11 +518,11 @@ const TradeBox = ({ // 8: 'Waiting only for buyer invoice' case 8: - bondStatus = 'locked'; + baseContract.bondStatus = 'locked'; if (isBuyer) { - title = 'Submit payout info'; - titleVariables = { amountSats: pn(order.invoice_amount) }; - prompt = () => { + baseContract.title = 'Submit payout info'; + baseContract.titleVariables = { amountSats: pn(order.invoice_amount) }; + baseContract.prompt = () => { return ( { + baseContract.title = 'The trade collateral is locked!'; + baseContract.prompt = () => { return ; }; } @@ -507,12 +550,11 @@ const TradeBox = ({ // 10: 'Fiat sent - In chatroom' case 9: case 10: - title = isBuyer ? 'Chat with the seller' : 'Chat with the buyer'; - prompt = function () { + baseContract.title = isBuyer ? 'Chat with the seller' : 'Chat with the buyer'; + baseContract.prompt = function () { return ( { setOpen({ ...open, confirmFiatSent: true }); }} @@ -535,20 +577,20 @@ const TradeBox = ({ /> ); }; - bondStatus = 'locked'; + baseContract.bondStatus = 'locked'; break; // 11: 'In dispute' case 11: - bondStatus = 'settled'; + baseContract.bondStatus = 'settled'; if (order.statement_submitted) { - title = 'We have received your statement'; - prompt = function () { + baseContract.title = 'We have received your statement'; + baseContract.prompt = function () { return ; }; } else { - title = 'A dispute has been opened'; - prompt = function () { + baseContract.title = 'A dispute has been opened'; + baseContract.prompt = function () { return ( ; }; } else { - title = 'Trade finished!'; - titleColor = 'success'; - titleIcon = function () { + baseContract.title = 'Trade finished!'; + baseContract.titleColor = 'success'; + baseContract.titleIcon = function () { return ; }; - prompt = function () { + baseContract.prompt = function () { return ( { - onRenewOrder(); + renewOrder(); setLoadingButtons({ ...noLoadingButtons, renewOrder: true }); }} /> @@ -598,12 +640,12 @@ const TradeBox = ({ // 14: 'Sucessful trade' case 14: - title = 'Trade finished!'; - titleColor = 'success'; - titleIcon = function () { + baseContract.title = 'Trade finished!'; + baseContract.titleColor = 'success'; + baseContract.titleIcon = function () { return ; }; - prompt = function () { + baseContract.prompt = function () { return ( { - onRenewOrder(); + renewOrder(); setLoadingButtons({ ...noLoadingButtons, renewOrder: true }); }} /> @@ -623,9 +665,9 @@ const TradeBox = ({ // 15: 'Failed lightning network routing' case 15: if (isBuyer) { - bondStatus = 'unlocked'; - title = 'Lightning Routing Failed'; - prompt = function () { + baseContract.bondStatus = 'unlocked'; + baseContract.title = 'Lightning Routing Failed'; + baseContract.prompt = function () { return ( ; }; - prompt = function () { + baseContract.prompt = function () { return ( { - onRenewOrder(); + renewOrder(); setLoadingButtons({ ...noLoadingButtons, renewOrder: true }); }} /> @@ -663,9 +705,9 @@ const TradeBox = ({ // 16: 'Wait for dispute resolution' case 16: - bondStatus = 'settled'; - title = 'We have the statements'; - prompt = function () { + baseContract.bondStatus = 'settled'; + baseContract.title = 'We have the statements'; + baseContract.prompt = function () { return ; }; break; @@ -675,14 +717,14 @@ const TradeBox = ({ case 17: case 18: if ((status === 17 && isMaker) || (status === 18 && !isMaker)) { - title = 'You have lost the dispute'; - prompt = function () { + baseContract.title = 'You have lost the dispute'; + baseContract.prompt = function () { return ; }; - bondStatus = 'settled'; + baseContract.bondStatus = 'settled'; } else if ((status === 17 && !isMaker) || (status === 18 && isMaker)) { - title = 'You have won the dispute'; - prompt = function () { + baseContract.title = 'You have won the dispute'; + baseContract.prompt = function () { return ; }; } @@ -692,10 +734,10 @@ const TradeBox = ({ break; } - return { title, titleVariables, titleColor, prompt, bondStatus, titleIcon }; + return baseContract; }; - const contract = currentOrder != null ? statusToContract(currentOrder) : null; + const contract = statusToContract(); return ( @@ -705,7 +747,7 @@ const TradeBox = ({ setOpen(closeAll); }} waitingWebln={waitingWebln} - isBuyer={currentOrder?.is_buyer ?? false} + isBuyer={garage.getSlot().order?.is_buyer ?? false} /> { setOpen(closeAll); @@ -749,14 +791,14 @@ const TradeBox = ({ /> { setOpen(closeAll); }} onConfirmClick={confirmFiatReceived} /> - + <Divider /> - <BondStatus status={contract?.bondStatus} isMaker={currentOrder?.is_maker ?? false} /> + <BondStatus + status={contract?.bondStatus} + isMaker={garage.getSlot().order?.is_maker ?? false} + /> </Grid> ) : ( <></> @@ -789,7 +834,7 @@ const TradeBox = ({ <Grid item> <CancelButton - order={currentOrder} + order={garage.getSlot().order} onClickCancel={cancel} openCancelDialog={() => { setOpen({ ...closeAll, confirmCancel: true }); diff --git a/frontend/src/contexts/FederationContext.ts b/frontend/src/contexts/FederationContext.ts index 2f51ba79..465030cf 100644 --- a/frontend/src/contexts/FederationContext.ts +++ b/frontend/src/contexts/FederationContext.ts @@ -51,8 +51,6 @@ export interface fetchRobotProps { export interface UseFederationStoreType { federation: Federation; sortedCoordinators: string[]; - focusedCoordinator: string | null; - setFocusedCoordinator: Dispatch<SetStateAction<string>>; setDelay: Dispatch<SetStateAction<number>>; coordinatorUpdatedAt: string; federationUpdatedAt: string; @@ -61,8 +59,6 @@ export interface UseFederationStoreType { export const initialFederationContext: UseFederationStoreType = { federation: new Federation(), sortedCoordinators: [], - focusedCoordinator: '', - setFocusedCoordinator: () => {}, setDelay: () => {}, coordinatorUpdatedAt: '', federationUpdatedAt: '', @@ -73,7 +69,7 @@ export const FederationContext = createContext<UseFederationStoreType>(initialFe export const useFederationStore = (): UseFederationStoreType => { const { settings, page, origin, hostUrl, open, torStatus } = useContext<UseAppStoreType>(AppContext); - const { setMaker, garage, orderUpdatedAt, robotUpdatedAt, badOrder } = + const { setMaker, garage, setBadOrder, robotUpdatedAt, badOrder, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext); const [federation, setFederation] = useState(initialFederationContext.federation); const sortedCoordinators = useMemo(() => { @@ -88,13 +84,10 @@ export const useFederationStore = (): UseFederationStoreType => { ); const [federationUpdatedAt, setFederationUpdatedAt] = useState<string>(new Date().toISOString()); - const [focusedCoordinator, setFocusedCoordinator] = useState<string>(sortedCoordinators[0]); - - const [delay, setDelay] = useState<number>(60000); + const [delay, setDelay] = useState<number>(5000); const [timer, setTimer] = useState<NodeJS.Timer | undefined>(() => setInterval(() => null, delay), ); - const [currentOrder, setCurrentOrder] = useState<Order | null>(null); useEffect(() => { // On bitcoin network change we reset book, limits and federation info and fetch everything again @@ -112,27 +105,34 @@ export const useFederationStore = (): UseFederationStoreType => { }, [settings.network, torStatus]); const fetchCurrentOrder = (): void => { - if (currentOrder?.id != null && (page === 'order' || badOrder === undefined)) { - void federation.fetchOrder(currentOrder, garage); + const activeSlot = garage.getSlot(); + if (activeSlot.activeOrderShortAlias !== null && activeSlot.activeOrderId !== null) { + const coordinator = federation.getCoordinator(activeSlot.activeOrderShortAlias); + coordinator + .fetchOrder(activeSlot.activeOrderId, activeSlot.robot) + .then((order) => { + if (order?.bad_request !== undefined) { + setBadOrder(order.bad_request); + } + if (order?.id !== null) { + garage.updateOrder(order as Order); + } + }) + .finally(() => { + setTimer(setTimeout(fetchCurrentOrder, delay)); + }); + } else { + setTimer(setTimeout(fetchCurrentOrder, delay)); } }; - // Fetch current order at load and in a loop - useEffect(() => { - fetchCurrentOrder(); - }, [currentOrder, page]); - - useEffect(() => { - setCurrentOrder(garage.getOrder()); - }, [orderUpdatedAt]); - useEffect(() => { clearInterval(timer); - setTimer(setInterval(fetchCurrentOrder, delay)); + fetchCurrentOrder(); return () => { clearInterval(timer); }; - }, [delay, currentOrder, page, badOrder]); + }, []); useEffect(() => { const robot = garage.getSlot().robot; @@ -154,8 +154,6 @@ export const useFederationStore = (): UseFederationStoreType => { return { federation, sortedCoordinators, - focusedCoordinator, - setFocusedCoordinator, setDelay, coordinatorUpdatedAt, federationUpdatedAt, diff --git a/frontend/src/models/Coordinator.model.ts b/frontend/src/models/Coordinator.model.ts index 48763941..82d2d838 100644 --- a/frontend/src/models/Coordinator.model.ts +++ b/frontend/src/models/Coordinator.model.ts @@ -188,7 +188,7 @@ export class Coordinator { apiClient .get(this.url, `${this.basePath}/api/limits/`) .then((data) => { - if (data != null) { + if (data !== null) { const newLimits = data as LimitList; for (const currency in this.limits) { @@ -215,8 +215,10 @@ export class Coordinator { apiClient .get(this.url, `${this.basePath}/api/info/`) .then((data) => { - this.info = data as Info; - onDataLoad(); + if (data !== null) { + this.info = data as Info; + onDataLoad(); + } }) .catch((e) => { console.log(e); diff --git a/frontend/src/models/Federation.model.ts b/frontend/src/models/Federation.model.ts index 6cb6a260..2224e198 100644 --- a/frontend/src/models/Federation.model.ts +++ b/frontend/src/models/Federation.model.ts @@ -96,20 +96,6 @@ export class Federation { }); }; - fetchOrder = async (order: Order, garage: Garage): Promise<Order | null> => { - if (order.shortAlias !== null) { - const coordinator = this.coordinators[order.shortAlias]; - if (coordinator != null && order.id !== null) { - const newOrder = await coordinator.fetchOrder(order.id, garage.getSlot().robot); - if (newOrder != null) { - garage.updateOrder(newOrder); - return newOrder; - } - } - } - return order; - }; - // Coordinators getCoordinator = (shortAlias: string): Coordinator => { return this.coordinators[shortAlias]; diff --git a/frontend/src/models/Garage.model.ts b/frontend/src/models/Garage.model.ts index 875f6664..1fc40e2e 100644 --- a/frontend/src/models/Garage.model.ts +++ b/frontend/src/models/Garage.model.ts @@ -146,7 +146,17 @@ class Garage { // Orders updateOrder: (order: Order, index?: number) => void = (order, index = this.currentSlot) => { - this.slots[index].order = order; + const updatedOrder = this.slots[index].order; + if (updatedOrder !== null && updatedOrder.id === order.id) { + Object.assign(updatedOrder, order); + this.slots[index].order = updatedOrder; + } else { + this.slots[index].order = order; + } + if (this.slots[index].order?.is_participant) { + this.slots[index].activeOrderId = this.slots[index].order?.id ?? null; + this.slots[index].activeOrderShortAlias = this.slots[index].order?.shortAlias ?? null; + } this.triggerHook('onOrderUpdate'); this.save(); }; diff --git a/frontend/src/models/Order.model.ts b/frontend/src/models/Order.model.ts index 06371026..78b9e927 100644 --- a/frontend/src/models/Order.model.ts +++ b/frontend/src/models/Order.model.ts @@ -106,6 +106,9 @@ export interface Order { network: 'mainnet' | 'testnet'; shortAlias: string; bad_request?: string; + bad_address?: string; + bad_invoice?: string; + bad_statement?: string; } export const defaultOrder: Order = { diff --git a/frontend/src/pro/Widgets/Federation.tsx b/frontend/src/pro/Widgets/Federation.tsx index 6f9a44d1..88684e70 100644 --- a/frontend/src/pro/Widgets/Federation.tsx +++ b/frontend/src/pro/Widgets/Federation.tsx @@ -35,7 +35,6 @@ const FederationWidget = React.forwardRef(function Component( return ( <Paper elevation={3} style={{ width: '100%', height: '100%' }}> <FederationTable - openCoordinator={() => setOpen({ ...open, coordinator: true })} maxWidth={layout.w * gridCellSize} // EM units maxHeight={layout.h * gridCellSize} // EM units />