Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions frontend/app/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,30 @@ export class ApiError extends Error {

// ─── Fetch helpers ────────────────────────────────────────────────────────────

const REQUEST_TIMEOUT_MS = 30_000;

async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
const res = await fetch(`/api${path}`, {
headers: { "Content-Type": "application/json", ...options.headers },
...options,
});
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);

let res: Response;
try {
res = await fetch(`/api${path}`, {
headers: { "Content-Type": "application/json", ...options.headers },
signal: controller.signal,
...options,
});
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") {
throw new ApiError(
0,
`Request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`,
);
}
throw err;
} finally {
clearTimeout(timer);
}
Comment thread
hulxv marked this conversation as resolved.
Comment on lines +194 to +217
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf 'Shared timeout implementation:\n'
sed -n '194,217p' frontend/app/api.ts
printf '\nWrappers and affected mutating endpoints:\n'
sed -n '250,381p' frontend/app/api.ts | rg -n 'function (post|put|del)|=>\s*(post|put|del)\('

Repository: citadel-tech/maker-dasboard

Length of output: 1450


🏁 Script executed:

wc -l frontend/app/api.ts

Repository: citadel-tech/maker-dasboard

Length of output: 94


🏁 Script executed:

cat -n frontend/app/api.ts | head -250

Repository: citadel-tech/maker-dasboard

Length of output: 7443


🏁 Script executed:

cat -n frontend/app/api.ts | tail -144

Repository: citadel-tech/maker-dasboard

Length of output: 6359


🏁 Script executed:

cd frontend && npx prettier --write . && npm run build

Repository: citadel-tech/maker-dasboard

Length of output: 1477


🏁 Script executed:

# Check if there are any retry mechanisms, idempotency keys, or status polling patterns elsewhere in the codebase
rg -i "retry|idempotent|idempotency|polling|debounce" frontend/app --type ts --type tsx -A 2 -B 2

Repository: citadel-tech/maker-dasboard

Length of output: 98


🏁 Script executed:

rg -i "retry|idempotent|idempotency|polling|debounce" frontend/app --type ts -A 2 -B 2

Repository: citadel-tech/maker-dasboard

Length of output: 1041


🏁 Script executed:

# Check how mutating endpoints are called in components - look for error handling patterns
rg "wallet\.send|wallet\.sync|makers\.start|makers\.stop|makers\.delete|bitcoind\.start|bitcoind\.stop" frontend/app --type ts -B 3 -A 5

Repository: citadel-tech/maker-dasboard

Length of output: 6601


A blanket 30s abort makes write requests ambiguous.

request() backs all post/put/del operations, so timeout affects wallet.send(), makers.start(), bitcoind.start(), and other non-idempotent writes. After 30s the browser stops waiting, but the backend may still complete the operation. The error handler shows a timeout message, but there is no status polling or idempotency mechanism to confirm the operation failed. Users can manually retry, creating duplicate sends or state inconsistency.

Make timeouts configurable per endpoint. For long-running or non-idempotent operations, either implement idempotency tokens and client-side deduplication, or use status polling to confirm operation completion before retrying.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/api.ts` around lines 194 - 217, The current global
REQUEST_TIMEOUT_MS used by request() causes ambiguous failures for
non-idempotent endpoints (e.g., wallet.send(), makers.start(),
bitcoind.start()); change request(path, options) to accept a per-call timeout
(e.g., options.timeoutMs) that overrides REQUEST_TIMEOUT_MS, use that value to
set the AbortController timer, and preserve existing behavior when not provided;
for known long-running or write endpoints update their callers to pass longer
timeouts or implement idempotency tokens / client-side deduplication or
status-polling (e.g., add idempotency header and/or a polling flow) so retries
don’t create duplicate operations.


const raw = await res.text();
let body: ApiResponse<T> | null = null;
Expand Down
32 changes: 32 additions & 0 deletions frontend/app/components/LoadingState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Loader2 } from "lucide-react";

interface LoadingStateProps {
/** Primary message, e.g. "Loading maker data" */
message: string;
/** Optional secondary detail, e.g. "Fetching wallet balance…" */
detail?: string;
/** Show as full-page centered (default) or inline */
inline?: boolean;
}

export default function LoadingState({
message,
detail,
inline,
}: LoadingStateProps) {
const content = (
<div className="flex flex-col items-center gap-3">
<Loader2 className="w-6 h-6 text-orange-500 animate-spin" />
<div className="text-center">
<p className="text-gray-300 text-sm font-medium">{message}</p>
{detail && (
<p className="text-gray-500 text-xs mt-1 animate-pulse">{detail}</p>
)}
</div>
</div>
);

if (inline) return content;

return <div className="flex items-center justify-center h-64">{content}</div>;
Comment on lines +17 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Expose the loader as a live status region.

The staged detail text is a key part of this PR, but it is only visual right now. Without role="status" and aria-live="polite", screen readers will miss these loading-state updates.

♿ Suggested fix
   const content = (
-    <div className="flex flex-col items-center gap-3">
-      <Loader2 className="w-6 h-6 text-orange-500 animate-spin" />
+    <div
+      className="flex flex-col items-center gap-3"
+      role="status"
+      aria-live="polite"
+      aria-atomic="true"
+    >
+      <Loader2
+        className="w-6 h-6 text-orange-500 animate-spin"
+        aria-hidden="true"
+      />
       <div className="text-center">
         <p className="text-gray-300 text-sm font-medium">{message}</p>
         {detail && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/components/LoadingState.tsx` around lines 17 - 31, The
LoadingState component currently renders the visual loader and the optional
detail string but does not expose these updates to assistive tech; update the
rendered DOM to include an accessible live region by adding role="status" and
aria-live="polite" to the container that wraps the textual content (e.g., the
div that contains the <p> elements or the parent "content" wrapper used when
inline is true), ensuring the detail text updates are announced by screen
readers while preserving the existing structure and inline behavior.

}
45 changes: 36 additions & 9 deletions frontend/app/routes/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Link } from "react-router-dom";
import { X } from "lucide-react";
import Nav from "../components/Nav";
import LoadingState from "../components/LoadingState";
import OnboardingWizard from "./onboarding";
import {
makers,
Expand Down Expand Up @@ -44,20 +45,35 @@
export default function Home() {
const [makerRows, setMakerRows] = useState<MakerRow[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [loadingDetail, setLoadingDetail] = useState<string>("");
const [error, setError] = useState<string | null>(null);
const [pending, setPending] = useState<Set<string>>(new Set());
const swapHistoryCache = useRef<Record<string, UtxoInfo[]>>({});
const swapReportCache = useRef<Record<string, SwapReportDto[]>>({});
const lastSwapRefreshAt = useRef(0);

async function loadMakers(forceSwapRefresh = false) {
const isInitialLoad = useRef(true);

Check failure on line 57 in frontend/app/routes/home.tsx

View workflow job for this annotation

GitHub Actions / frontend

React Hook "useRef" is called in function "loadMakers" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"

const initial = isInitialLoad.current;
if (!initial) setRefreshing(true);
Comment on lines 56 to +60
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '56,60p' frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 249


🏁 Script executed:

#!/bin/bash
# Get the full structure of the component to understand context
head -80 frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 2639


🏁 Script executed:

#!/bin/bash
# Search for where loadMakers is called
rg "loadMakers" frontend/app/routes/home.tsx -n -A 2 -B 2

Repository: citadel-tech/maker-dasboard

Length of output: 659


🏁 Script executed:

#!/bin/bash
# Verify the suggested fix location by checking the component state declarations
sed -n '42,56p' frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 777


🏁 Script executed:

#!/bin/bash
# Check if there are any other hook-related issues in the file
# Look for useEffect, useState, useRef, useCallback, useMemo, useContext, useReducer
rg "use[A-Z]\w+\(" frontend/app/routes/home.tsx -n | head -30

Repository: citadel-tech/maker-dasboard

Length of output: 288


🏁 Script executed:

#!/bin/bash
# Verify the exact behavior by checking what happens with isInitialLoad
# Look at how isInitialLoad is used in loadMakers
sed -n '56,75p' frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 828


🏁 Script executed:

#!/bin/bash
# Check if isInitialLoad is referenced anywhere else in the file
rg "isInitialLoad" frontend/app/routes/home.tsx -n

Repository: citadel-tech/maker-dasboard

Length of output: 200


🏁 Script executed:

#!/bin/bash
# Check context around where isInitialLoad is set to false (line 140)
sed -n '135,145p' frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 327


🏁 Script executed:

#!/bin/bash
# Let's verify the complete context of the useEffect and understand the lifecycle
sed -n '144,160p' frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 607


🏁 Script executed:

#!/bin/bash
# Final verification: ensure the entire loadMakers function is properly scoped
# and confirm the hook violation by checking ESLint or similar would flag this
wc -l frontend/app/routes/home.tsx && echo "---" && sed -n '52,142p' frontend/app/routes/home.tsx | head -50

Repository: citadel-tech/maker-dasboard

Length of output: 2104


Move useRef(true) to component scope.

Line 57 violates React's Rules of Hooks: useRef is called inside the async loadMakers helper function instead of at component level. This breaks the persistent state—each call recreates the ref with true, so isInitialLoad.current = false at line 140 is lost on the next invocation, and the setRefreshing(true) path never executes correctly.

Declare the ref once at component scope (after line 54) and read/update it inside loadMakers.

🔧 Suggested fix
 export default function Home() {
   const [makerRows, setMakerRows] = useState<MakerRow[]>([]);
   const [loading, setLoading] = useState(true);
   const [refreshing, setRefreshing] = useState(false);
   const [loadingDetail, setLoadingDetail] = useState<string>("");
   const [error, setError] = useState<string | null>(null);
   const [pending, setPending] = useState<Set<string>>(new Set());
   const swapHistoryCache = useRef<Record<string, UtxoInfo[]>>({});
   const swapReportCache = useRef<Record<string, SwapReportDto[]>>({});
   const lastSwapRefreshAt = useRef(0);
+  const isInitialLoad = useRef(true);

   async function loadMakers(forceSwapRefresh = false) {
-    const isInitialLoad = useRef(true);
-
     const initial = isInitialLoad.current;
🧰 Tools
🪛 GitHub Actions: Lint

[error] 57-57: ESLint (react-hooks/rules-of-hooks): React Hook "useRef" is called in function "loadMakers" that is neither a React function component nor a custom React Hook. Function/component names must start uppercase; Hook names must start with "use".

🪛 GitHub Check: frontend

[failure] 57-57:
React Hook "useRef" is called in function "loadMakers" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/routes/home.tsx` around lines 56 - 60, The useRef call must be
moved out of the async helper to component scope: remove the `const
isInitialLoad = useRef(true)` from inside the `loadMakers` function and instead
declare `const isInitialLoad = useRef(true)` once at the component level
(immediately after line 54), then inside `loadMakers` read `const initial =
isInitialLoad.current` and later set `isInitialLoad.current = false` where the
code currently updates it (around the existing line ~140); this preserves the
persistent ref across invocations so `setRefreshing(true)` executes correctly.

try {
setError(null);
if (initial) setLoadingDetail("Fetching maker list…");
const list = await makers.list();
if (initial)
setLoadingDetail(
`Loading details for ${list.length} maker${list.length !== 1 ? "s" : ""}…`,
);
const includeSwaps =
forceSwapRefresh ||
lastSwapRefreshAt.current === 0 ||
Date.now() - lastSwapRefreshAt.current >= SWAP_HISTORY_REFRESH_MS;
if (initial)
setLoadingDetail(
`Loading details for ${list.length} maker${list.length !== 1 ? "s" : ""}…`,
);
const rows = await Promise.all(
list.map(async ({ id }): Promise<MakerRow> => {
const requests = [
Expand Down Expand Up @@ -120,6 +136,8 @@
setError(err instanceof Error ? err.message : "Failed to load makers");
} finally {
setLoading(false);
setRefreshing(false);
isInitialLoad.current = false;
}
}

Expand Down Expand Up @@ -152,9 +170,10 @@
return (
<div className="min-h-screen bg-gray-950 text-gray-100">
<Nav />
<div className="flex items-center justify-center h-64">
<div className="text-gray-400 animate-pulse">Loading makers…</div>
</div>
<LoadingState
message="Loading makers"
detail={loadingDetail || "Connecting to dashboard…"}
/>
</div>
);
}
Expand Down Expand Up @@ -253,12 +272,20 @@
<div className="mb-6 sm:mb-8">
<div className="flex flex-col sm:flex-row sm:items-center justify-between mb-4 sm:mb-5 gap-3">
<h2 className="text-lg sm:text-xl font-semibold">Your Makers</h2>
<Link
to="/addMaker"
className="px-4 sm:px-5 py-2 sm:py-2.5 bg-orange-600 text-white rounded-lg hover:bg-orange-700 active:scale-[0.97] transition-all duration-150 font-semibold text-sm w-full sm:w-auto text-center"
>
+ Add New Maker
</Link>
<div className="flex items-center gap-3 w-full sm:w-auto">
{refreshing && (
<span className="text-xs text-gray-500 flex items-center gap-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-orange-500 animate-pulse" />
Refreshing...
</span>
)}
<Link
to="/addMaker"
className="px-4 sm:px-5 py-2 sm:py-2.5 bg-orange-600 text-white rounded-lg hover:bg-orange-700 active:scale-[0.97] transition-all duration-150 font-semibold text-sm w-full sm:w-auto text-center"
>
+ Add New Maker
</Link>
</div>
</div>

<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-5">
Expand Down
26 changes: 23 additions & 3 deletions frontend/app/routes/makerDetails/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, useRef } from "react";
import { useParams } from "react-router-dom";
import { Zap, Moon } from "lucide-react";
import Nav from "../../components/Nav";
import LoadingState from "../../components/LoadingState";
import {
makers,
wallet,
Expand Down Expand Up @@ -41,17 +42,21 @@ export default function MakerDetails() {
const [torAddress, setTorAddress] = useState<string | null>(null);
const [dataDir, setDataDir] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [loadingDetail, setLoadingDetail] = useState("");
const [error, setError] = useState<string | null>(null);
const [actionLoading, setActionLoading] = useState(false);
const [syncLoading, setSyncLoading] = useState(false);
const [syncMsg, setSyncMsg] = useState<string | null>(null);
const isInitialLoad = useRef(true);
const [walletRefreshToken, setWalletRefreshToken] = useState(0);

const loadCore = useCallback(async () => {
if (!id) return;
setLoading(true);
const initial = isInitialLoad.current;
if (initial) setLoading(true);
setError(null);
try {
if (initial) setLoadingDetail("Fetching maker config and status…");
const [infoData, statusData, balanceData, reportsData] =
await Promise.allSettled([
makers.get(id),
Expand All @@ -75,6 +80,7 @@ export default function MakerDetails() {
setEarningsSats(0);
}

if (initial) setLoadingDetail("Resolving Tor address…");
monitoring
.torAddress(id)
.then(setTorAddress)
Expand All @@ -87,6 +93,7 @@ export default function MakerDetails() {
setError(e instanceof Error ? e.message : "Failed to load maker data");
} finally {
setLoading(false);
isInitialLoad.current = false;
}
}, [id]);

Expand Down Expand Up @@ -137,6 +144,19 @@ export default function MakerDetails() {
};

const swapLiquidity = balances ? `${satsToBtc(balances.swap)} BTC` : "—";

if (loading) {
return (
<div className="min-h-screen bg-gray-950 text-gray-100">
<Nav />
<LoadingState
message={`Loading maker ${id}`}
detail={loadingDetail || "Connecting…"}
/>
</div>
);
}

return (
<div className="min-h-screen bg-gray-950 text-gray-100">
<Nav />
Expand Down Expand Up @@ -229,7 +249,7 @@ export default function MakerDetails() {
<div>
<div className="text-sm text-orange-100 mb-1">Status</div>
<div className="text-xl sm:text-2xl font-bold text-white">
{loading ? "Loading…" : isRunning ? "Running" : "Stopped"}
{isRunning ? "Running" : "Stopped"}
</div>
</div>
</div>
Expand Down
15 changes: 11 additions & 4 deletions frontend/app/routes/makerDetails/wallet.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from "react";
import { Loader2 } from "lucide-react";
import { wallet, satsToBtc, btcToSats, type UtxoInfo } from "../../api";

interface Props {
Expand Down Expand Up @@ -232,10 +233,16 @@ export default function Wallet({ id, onBalanceRefresh, refreshToken }: Props) {
)}

{utxosLoading ? (
<div className="animate-pulse space-y-2">
{[...Array(3)].map((_, i) => (
<div key={i} className="h-10 bg-gray-800 rounded-lg" />
))}
<div className="space-y-3">
<div className="flex items-center gap-2 text-sm text-gray-400">
<Loader2 className="w-4 h-4 text-orange-500 animate-spin" />
Fetching UTXOs from wallet…
</div>
<div className="animate-pulse space-y-2">
{[...Array(3)].map((_, i) => (
<div key={i} className="h-10 bg-gray-800 rounded-lg" />
))}
</div>
</div>
) : utxos && utxos.length > 0 ? (
<div className="overflow-x-auto">
Expand Down
53 changes: 47 additions & 6 deletions frontend/app/routes/makersetup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,31 @@
l.includes("Coinselection"),
);
}
/** Pattern → human-readable progress mappings */
const progressPatterns: [string, string][] = [
["maker server listening on port", "Maker server started"],
["Successfully created fidelity bond", "Fidelity bond confirmed"],
["No spendable UTXOs available", "No funds yet — waiting for deposit"],
[
"Insufficient fund to create fidelity bond",
"Insufficient funds for bond — waiting for deposit",
],
["Next sync in", "Waiting for next wallet sync cycle…"],
["Synced & Saved", "Wallet synced and saved"],
["Scanning completed", "Blockchain scan completed"],
["Re-scanning Blockchain", "Re-scanning blockchain…"],
["Sync Started for", "Syncing wallet with Bitcoin Core…"],
["Sync at:----setup_fidelity_bond----", "Setting up fidelity bond…"],
["Fidelity timelock", "Configuring fidelity bond timelock…"],
["Fidelity value chosen", "Fidelity bond amount selected"],
["No active Fidelity Bonds found", "No fidelity bonds found — creating one"],
["Generated new Tor Hidden Service", "Generated new Tor hidden service"],
["Generated existing Tor Hidden Service", "Tor hidden service restored"],
["Starting Maker Server", "Starting maker server…"],
["Selected 1 regular UTXOs", "Funds found — selecting UTXOs for bond"],
["Coinselection", "Selecting coins for bond transaction…"],
["Transaction seen in mempool", "Incoming transaction detected"],
];

const LOG_LEVEL_RE = /(?:^|\s|\[)(INFO|WARN|ERROR|DEBUG|TRACE)(?=$|\s|:|\])/i;

Expand All @@ -78,6 +103,16 @@
return levelMatch[1].toUpperCase() === "INFO";
}

/** Extract the latest meaningful progress detail from logs for user display */
function latestProgressDetail(logs: string[]): string | null {

Check failure on line 107 in frontend/app/routes/makersetup.tsx

View workflow job for this annotation

GitHub Actions / frontend

'latestProgressDetail' is defined but never used
for (const line of [...logs].reverse()) {
for (const [needle, detail] of progressPatterns) {
if (line.includes(needle)) return detail;
}
}
return null;
}
Comment on lines +106 to +114
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

progressDetail is never populated.

latestProgressDetail() and setProgressDetail() are both introduced here, but nothing calls them in this file. That means progressDetail stays null, so all of the new progressDetail ?? ... branches keep rendering the fallback copy and the log-derived status text never appears.

💡 Minimal wiring
setProgressDetail(latestProgressDetail(existingLogs));

...

const detail = latestProgressDetail(next);
setProgressDetail(detail);

Also applies to: 142-142

🧰 Tools
🪛 GitHub Check: frontend

[failure] 107-107:
'latestProgressDetail' is defined but never used

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/routes/makersetup.tsx` around lines 106 - 114,
latestProgressDetail is never used so progressDetail remains null; call
setProgressDetail(latestProgressDetail(...)) when logs are initialized and
whenever logs change. Specifically, after computing or loading existingLogs (or
the initial logs state) invoke
setProgressDetail(latestProgressDetail(existingLogs)), and inside the logs
update/append handler (where you receive next logs) compute const detail =
latestProgressDetail(next) and call setProgressDetail(detail) so progressDetail
is populated whenever logs change; refer to the functions latestProgressDetail
and setProgressDetail and the log variables existingLogs/next in your updates.


function inferStage(
logs: string[],
fidelityConfirmations: number[],
Expand All @@ -104,6 +139,7 @@
const [minAmount, setMinAmount] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [progressDetail, setProgressDetail] = useState<string | null>(null);

Check failure on line 142 in frontend/app/routes/makersetup.tsx

View workflow job for this annotation

GitHub Actions / frontend

'setProgressDetail' is assigned a value but never used

const logsEndRef = useRef<HTMLDivElement>(null);
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
Expand Down Expand Up @@ -320,7 +356,8 @@
</div>
),
title: "Starting Maker",
subtitle: "Initializing wallet and connecting to Bitcoin Core…",
subtitle:
progressDetail ?? "Initializing wallet and connecting to Bitcoin Core…",
color: "orange",
},
awaiting_funds: {
Expand All @@ -330,7 +367,9 @@
</div>
),
title: "Deposit Required",
subtitle: "Send Bitcoin to this address to create your fidelity bond",
subtitle:
progressDetail ??
"Send Bitcoin to this address to create your fidelity bond",
color: "orange",
},
creating_bond: {
Expand Down Expand Up @@ -358,7 +397,9 @@
</div>
),
title: "Creating Fidelity Bond",
subtitle: "Funds detected — waiting for confirmation and bond creation…",
subtitle:
progressDetail ??
"Funds detected — waiting for confirmation and bond creation…",
color: "blue",
},
live: {
Expand Down Expand Up @@ -612,8 +653,8 @@
d="M4 12a8 8 0 018-8v8z"
/>
</svg>
Watching for incoming funds — this page will update
automatically
{progressDetail ??
"Watching for incoming funds — this page will update automatically"}
</div>
</div>
)}
Expand Down Expand Up @@ -659,7 +700,7 @@
d="M4 12a8 8 0 018-8v8z"
/>
</svg>
Waiting for block confirmation…
{progressDetail ?? "Waiting for block confirmation…"}
</div>
</div>
)}
Expand Down
Loading