diff --git a/src/components/App.tsx b/src/components/App.tsx index 1edbd5e6..5c1cd49c 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -87,8 +87,6 @@ export default function App() { [reloadCurrentWalletInfo], ) - debugger - const router = createBrowserRouter( createRoutesFromElements( { - {item.bondValue.displayValue} + + {item.bondValue.value > 0 ? ( + ( + + +
+ {item.bondValue.displayLocktime} ({item.bondValue.displayExpiresIn}) +
+
+ )} + > + {item.bondValue.displayValue} +
+ ) : ( + <>{item.bondValue.displayValue} + )} +
) } @@ -290,9 +322,13 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { ) } -const offerToTableEntry = (offer: ObwatchApi.Offer, t: TFunction): OrderTableEntry => { +const offerToTableEntry = ( + offer: ObwatchApi.Offer, + fidelityBond: ObwatchApi.FidelityBond | undefined, + i18n: i18n, +): OrderTableEntry => { return { - type: orderTypeProps(offer, t), + type: orderTypeProps(offer, i18n.t), counterparty: offer.counterparty, orderId: String(offer.oid), fee: @@ -314,6 +350,17 @@ const offerToTableEntry = (offer: ObwatchApi.Offer, t: TFunction): OrderTableEnt bondValue: { value: offer.fidelity_bond_value, displayValue: String(offer.fidelity_bond_value.toFixed(0)), + locktime: fidelityBond?.locktime, + displayLocktime: + fidelityBond?.locktime !== undefined ? new Date(fidelityBond.locktime * 1_000).toDateString() : undefined, + displayExpiresIn: + fidelityBond?.locktime !== undefined + ? fb.time.humanReadableDuration({ + to: fidelityBond.locktime * 1_000, + locale: i18n.resolvedLanguage || i18n.language, + }) + : undefined, + amount: fidelityBond?.amount, }, } } @@ -321,14 +368,14 @@ const offerToTableEntry = (offer: ObwatchApi.Offer, t: TFunction): OrderTableEnt interface OrderbookProps { entries: OrderTableEntry[] refresh: (signal: AbortSignal) => Promise + isLoading: boolean nickname?: string } -export function Orderbook({ entries, refresh, nickname }: OrderbookProps) { +export function Orderbook({ entries, refresh, isLoading: isLoadingRefresh, nickname }: OrderbookProps) { const { t } = useTranslation() const settings = useSettings() const [search, setSearch] = useState('') - const [isLoadingRefresh, setIsLoadingRefresh] = useState(false) const [isHighlightOwnOffers, setIsHighlightOwnOffers] = useState(false) const [isPinToTopOwnOffers, setIsPinToTopOwnOffers] = useState(false) const [highlightedOrders, setHighlightedOrders] = useState([]) @@ -389,12 +436,9 @@ export function Orderbook({ entries, refresh, nickname }: OrderbookProps) { onClick={() => { if (isLoadingRefresh) return - setIsLoadingRefresh(true) - const abortCtrl = new AbortController() refresh(abortCtrl.signal).finally(() => { - // as refreshing is fast most of the time, add a short delay to avoid flickering - setTimeout(() => setIsLoadingRefresh(false), 250) + console.log('Finished reloading orderbook.') }) }} > @@ -482,13 +526,19 @@ type OrderbookOverlayProps = rb.OffcanvasProps & { } export function OrderbookOverlay({ nickname, show, onHide }: OrderbookOverlayProps) { - const { t } = useTranslation() + const { t, i18n } = useTranslation() const [alert, setAlert] = useState() const [isInitialized, setIsInitialized] = useState(false) const [isLoading, setIsLoading] = useState(true) const [offers, setOffers] = useState() - const tableEntries = useMemo(() => offers && offers.map((offer) => offerToTableEntry(offer, t)), [offers, t]) + const [fidelityBonds, setFidelityBonds] = useState>() const [__dev_showGenerateDemoOfferButton] = useState(isDebugFeatureEnabled('enableDemoOrderbook')) + const tableEntries = useMemo(() => { + return ( + offers && + offers.map((offer) => offerToTableEntry(offer, fidelityBonds && fidelityBonds.get(offer.counterparty), i18n)) + ) + }, [offers, fidelityBonds, i18n]) const __dev_generateDemoReportEntryButton = () => { const randomMinsize = pseudoRandomNumber(JM_DUST_THRESHOLD, JM_DUST_THRESHOLD + 100_000) @@ -511,9 +561,10 @@ export function OrderbookOverlay({ nickname, show, onHide }: OrderbookOverlayPro const refresh = useCallback( (signal: AbortSignal) => { - return ObwatchApi.refreshOrderbook({ signal }) + setIsLoading(true) + return ObwatchApi.refreshOrderbook({ signal, redirect: 'manual' }) .then((res) => { - if (!res.ok) { + if (!res.ok && res.type !== 'opaqueredirect') { // e.g. error is raised if ob-watcher is not running return ApiHelper.throwError(res) } @@ -523,8 +574,10 @@ export function OrderbookOverlay({ nickname, show, onHide }: OrderbookOverlayPro .then((orderbook) => { if (signal.aborted) return + setIsLoading(false) setAlert(undefined) setOffers(orderbook.offers || []) + setFidelityBonds(new Map((orderbook.fidelitybonds || []).map((it) => [it.counterparty, it]))) if (isDevMode()) { console.table(orderbook.offers) @@ -532,6 +585,7 @@ export function OrderbookOverlay({ nickname, show, onHide }: OrderbookOverlayPro }) .catch((e) => { if (signal.aborted) return + setIsLoading(false) const message = t('orderbook.error_loading_orderbook_failed', { reason: e.message || t('global.errors.reason_unknown'), }) @@ -546,10 +600,8 @@ export function OrderbookOverlay({ nickname, show, onHide }: OrderbookOverlayPro const abortCtrl = new AbortController() - setIsLoading(true) refresh(abortCtrl.signal).finally(() => { if (abortCtrl.signal.aborted) return - setIsLoading(false) setIsInitialized(true) }) @@ -617,7 +669,7 @@ export function OrderbookOverlay({ nickname, show, onHide }: OrderbookOverlayPro {tableEntries && ( - + )} diff --git a/src/libs/JmObwatchApi.ts b/src/libs/JmObwatchApi.ts index 23d00987..35e18348 100644 --- a/src/libs/JmObwatchApi.ts +++ b/src/libs/JmObwatchApi.ts @@ -13,8 +13,21 @@ export interface Offer { fidelity_bond_value: number // example: 0 (no fb) or 114557102085.28133 } +export interface FidelityBond { + counterparty: string // example: "J5Bv3JSxPFWm2Yjb" + bond_value: number // example: 82681607.26848702, + locktime: number // example: 1725148800 + amount: AmountSats // example: 312497098 + script: string // example: 002059e6f4a2afbb87c9967530955d091d7954f9364cd829b6012bdbcb38fab5e383 + utxo_confirmations: number // example: 10 + utxo_confirmation_timestamp: number // example: 1716876653 + utxo_pub: string // example: 02d46a9001a5430c0aa1e3ad0c004b409a932d3ae99b19617f0ab013b12076c082 + cert_expiry: number // example: 1 +} + export interface OrderbookJson { offers?: Offer[] + fidelitybonds?: FidelityBond[] } const orderbookJson = async ({ signal }: { signal: AbortSignal }) => { @@ -27,9 +40,10 @@ const fetchOrderbook = async (options: { signal: AbortSignal }): Promise (res.ok ? res.json() : ApiHelper.throwError(res))) } -const refreshOrderbook = async ({ signal }: { signal: AbortSignal }) => { +const refreshOrderbook = async ({ signal, redirect }: { signal: AbortSignal; redirect: RequestRedirect }) => { return await fetch(`${basePath()}/refreshorderbook`, { method: 'POST', + redirect, signal, }) }