Skip to content

Commit 3662c3f

Browse files
committed
UX improvements: offline balance warning, duplicate detection, info icons, import clarity
1 parent 8bed7f5 commit 3662c3f

5 files changed

Lines changed: 63 additions & 15 deletions

File tree

web/src/app/add/components/UtxoWizard.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ type ParsedOutput = {
2626

2727
type UtxoWizardProps = {
2828
buckets: Bucket[];
29+
existingOutpoints: Set<string>;
2930
};
3031

31-
export function UtxoWizard({ buckets }: UtxoWizardProps) {
32+
export function UtxoWizard({ buckets, existingOutpoints }: UtxoWizardProps) {
3233
const { save: saveDossier } = useDossiers();
3334
const { save: saveBeef } = useBeefStore();
3435
const { mode, requestOnline } = useNetworkMode();
@@ -291,6 +292,7 @@ export function UtxoWizard({ buckets }: UtxoWizardProps) {
291292
setLabels={setLabels}
292293
onStore={handleStore}
293294
onBack={() => setStep("select-output")}
295+
existingOutpoints={existingOutpoints}
294296
/>
295297
)}
296298

web/src/app/add/components/WizardStepStore.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type WizardStepStoreProps = {
2727
setLabels: (labels: string) => void;
2828
onStore: () => void;
2929
onBack: () => void;
30+
existingOutpoints: Set<string>;
3031
};
3132

3233
export function WizardStepStore({
@@ -42,30 +43,57 @@ export function WizardStepStore({
4243
setLabels,
4344
onStore,
4445
onBack,
46+
existingOutpoints,
4547
}: WizardStepStoreProps) {
4648
const output = outputs.find((o) => o.vout === selectedVout);
49+
const outpoint = `${txid}:${selectedVout}`;
50+
const isDuplicate = existingOutpoints.has(outpoint);
4751

4852
return (
4953
<Card>
5054
<CardHeader>
5155
<CardTitle>Store UTXO</CardTitle>
5256
</CardHeader>
5357
<CardContent className="space-y-4">
54-
<div className="rounded-md border p-3 text-sm">
58+
<div className="rounded-md border p-3 text-sm space-y-1">
5559
<p>
5660
<strong>Outpoint:</strong> {txid}:{selectedVout}
5761
</p>
58-
{output && (
62+
{output ? (
5963
<p>
6064
<strong>Value:</strong> {(output.satoshis / 1e8).toFixed(8)} BSV
6165
</p>
66+
) : (
67+
<p className="text-yellow-600 dark:text-yellow-400">
68+
<strong>Value:</strong> Unknown (offline entry)
69+
</p>
6270
)}
63-
{beefBase64 && (
71+
{beefBase64 ? (
6472
<p style={{ color: "#16a34a" }}>
6573
<strong>BEEF:</strong> ✓ Available (height {height})
6674
</p>
75+
) : (
76+
<p className="text-yellow-600 dark:text-yellow-400">
77+
<strong>BEEF:</strong> Not available (can fetch later)
78+
</p>
6779
)}
6880
</div>
81+
{!output && (
82+
<p className="text-xs text-yellow-600 dark:text-yellow-400">
83+
⚠ Offline entry: BSV balance shown as 0 until you fetch the transaction online.
84+
</p>
85+
)}
86+
{isDuplicate && (
87+
<div className="rounded-md border border-orange-500/30 bg-orange-500/10 p-3">
88+
<p className="text-sm font-medium text-orange-700 dark:text-orange-400">
89+
⚠ Duplicate UTXO Detected
90+
</p>
91+
<p className="mt-1 text-xs text-muted-foreground">
92+
This outpoint already exists in your inventory. Storing will update the existing entry.
93+
If you meant to add a different output from the same transaction, go back and select a different vout.
94+
</p>
95+
</div>
96+
)}
6997

7098
<div className="space-y-2">
7199
<Label>Bucket</Label>

web/src/app/add/page.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ import { useDossiers } from "@/contexts/dossier-context";
88
import { UtxoWizard, BulkCsvImport } from "./components";
99

1010
export default function AddUtxoPage() {
11-
const { buckets, save: saveDossier } = useDossiers();
11+
const { buckets, dossiers, save: saveDossier } = useDossiers();
12+
13+
// Create a set of existing outpoints for duplicate detection
14+
const existingOutpoints = React.useMemo(
15+
() => new Set(dossiers.map((d) => d.outpoint)),
16+
[dossiers]
17+
);
1218

1319
return (
1420
<div className="space-y-6">
@@ -20,7 +26,7 @@ export default function AddUtxoPage() {
2026
</div>
2127

2228
{/* Main Wizard */}
23-
<UtxoWizard buckets={buckets} />
29+
<UtxoWizard buckets={buckets} existingOutpoints={existingOutpoints} />
2430

2531
<Separator />
2632

web/src/app/export/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,12 @@ export default function ExportPage() {
408408
className="hidden"
409409
/>
410410

411-
<Button onClick={handleFileSelect} disabled={importing}>
412-
{importing ? "Importing..." : "Select & Import Archive"}
411+
<Button onClick={handleFileSelect} disabled={importing} className="w-full sm:w-auto">
412+
{importing ? "Importing..." : "📁 Select & Import Archive"}
413413
</Button>
414414

415415
<p className="text-xs text-muted-foreground">
416-
Click the button above to select a file. Import starts automatically after selection.
416+
Opens a file picker. Import begins automatically once you select a .json or .enc file.
417417
</p>
418418
<p className="text-xs text-muted-foreground">
419419
Note: Importing merges with existing data. Duplicate UTXOs (same outpoint) will be updated, not duplicated.

web/src/app/page.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,14 @@ export default function HomePage() {
131131
</CardContent>
132132
</Card>
133133

134-
<Card title="BEEF (Background Evaluation Extended Format) proofs contain the Merkle path linking your UTXO's transaction to a block header, enabling offline SPV verification.">
134+
<Card>
135135
<CardHeader className="pb-2">
136-
<CardTitle className="text-sm font-medium text-muted-foreground cursor-help">
136+
<CardTitle
137+
className="text-sm font-medium text-muted-foreground cursor-help flex items-center gap-1"
138+
title="BEEF (Background Evaluation Extended Format) proofs contain the Merkle path linking your UTXO's transaction to a block header, enabling offline SPV verification."
139+
>
137140
BEEF Coverage
141+
<span className="inline-flex items-center justify-center w-4 h-4 text-[10px] rounded-full border border-muted-foreground/50"></span>
138142
</CardTitle>
139143
</CardHeader>
140144
<CardContent className="space-y-2">
@@ -157,10 +161,14 @@ export default function HomePage() {
157161
</CardContent>
158162
</Card>
159163

160-
<Card title="Verified proofs have been checked against locally-stored block headers. This confirms the transaction was included in a mined block, but does NOT prove the UTXO is currently unspent.">
164+
<Card>
161165
<CardHeader className="pb-2">
162-
<CardTitle className="text-sm font-medium text-muted-foreground cursor-help">
166+
<CardTitle
167+
className="text-sm font-medium text-muted-foreground cursor-help flex items-center gap-1"
168+
title="Verified proofs have been checked against locally-stored block headers. This confirms the transaction was included in a mined block, but does NOT prove the UTXO is currently unspent."
169+
>
163170
Verified Proofs
171+
<span className="inline-flex items-center justify-center w-4 h-4 text-[10px] rounded-full border border-muted-foreground/50"></span>
164172
</CardTitle>
165173
</CardHeader>
166174
<CardContent className="space-y-2">
@@ -190,10 +198,14 @@ export default function HomePage() {
190198
</CardContent>
191199
</Card>
192200

193-
<Card title="Block headers are 80-byte summaries of each block. They contain the Merkle root needed to verify BEEF proofs locally without trusting a third party.">
201+
<Card>
194202
<CardHeader className="pb-2">
195-
<CardTitle className="text-sm font-medium text-muted-foreground cursor-help">
203+
<CardTitle
204+
className="text-sm font-medium text-muted-foreground cursor-help flex items-center gap-1"
205+
title="Block headers are 80-byte summaries of each block. They contain the Merkle root needed to verify BEEF proofs locally without trusting a third party."
206+
>
196207
Headers
208+
<span className="inline-flex items-center justify-center w-4 h-4 text-[10px] rounded-full border border-muted-foreground/50"></span>
197209
</CardTitle>
198210
</CardHeader>
199211
<CardContent className="space-y-2">

0 commit comments

Comments
 (0)