Skip to content

Commit 97c15b1

Browse files
committed
fix: stabilize rebased frontend ci
1 parent a92288e commit 97c15b1

12 files changed

Lines changed: 13182 additions & 196 deletions

File tree

.github/workflows/CI.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ jobs:
6666
with:
6767
node-version: 20
6868
cache: 'npm'
69-
cache-dependency-path: package-lock.json
69+
cache-dependency-path: apps/web/package-lock.json
7070
- name: Install dependencies
71-
run: npm ci
71+
run: npm install --prefix apps/web
7272
- name: Lint
73-
run: npm run lint -w web
73+
run: npm run lint --prefix apps/web
7474
- name: Build
75-
run: npm run build -w web
75+
run: npm run build --prefix apps/web
7676

7777
playwright-e2e:
7878
name: E2E Tests (Playwright)
@@ -84,9 +84,11 @@ jobs:
8484
with:
8585
node-version: 20
8686
cache: 'npm'
87-
cache-dependency-path: package-lock.json
87+
cache-dependency-path: apps/web/package-lock.json
8888
- name: Install dependencies
89-
run: npm ci
89+
run: npm install
90+
- name: Install web dependencies
91+
run: npm install --prefix apps/web
9092
- name: Install Playwright Browsers
9193
run: npx playwright install --with-deps chromium
9294
- name: Run E2E tests
Lines changed: 208 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,224 @@
11
"use client";
22

3-
import { useEffect } from "react";
4-
import { useParams, useRouter } from "next/navigation";
5-
import { SiteShell } from "@/components/site-shell";
6-
import { api } from "@/lib/api";
3+
import Link from "next/link";
4+
import { useParams } from "next/navigation";
5+
import { FormEvent, useEffect, useState } from "react";
6+
7+
import { api, type Dispute, type Evidence, type Verdict } from "@/lib/api";
8+
import { connectWallet, signTransaction } from "@/lib/stellar";
79

810
export default function DisputePage() {
9-
const { id } = useParams<{ id: string }>();
10-
const router = useRouter();
11+
const params = useParams<{ id: string }>();
12+
const disputeId = params.id;
13+
14+
const [dispute, setDispute] = useState<Dispute | null>(null);
15+
const [verdict, setVerdict] = useState<Verdict | null>(null);
16+
const [walletAddress, setWalletAddress] = useState("");
17+
const [signature, setSignature] = useState("");
18+
const [evidence, setEvidence] = useState<Evidence | null>(null);
19+
const [content, setContent] = useState(
20+
"Freelancer uploaded timestamped delivery proof and reviewer notes.",
21+
);
22+
const [status, setStatus] = useState("Loading dispute state...");
23+
const [isSubmitting, setIsSubmitting] = useState(false);
1124

1225
useEffect(() => {
1326
let active = true;
1427

15-
void api.disputes
16-
.get(id)
17-
.then((dispute) => {
18-
if (!active) return;
19-
router.replace(`/jobs/${dispute.job_id}/dispute?disputeId=${dispute.id}`);
20-
})
21-
.catch(() => {
22-
if (!active) return;
23-
});
28+
async function loadDispute() {
29+
try {
30+
const [nextDispute, nextVerdict] = await Promise.all([
31+
api.disputes.get(disputeId),
32+
api.disputes.verdict(disputeId),
33+
]);
34+
35+
if (!active) {
36+
return;
37+
}
38+
39+
setDispute(nextDispute);
40+
setVerdict(nextVerdict);
41+
setStatus("Dispute loaded. Evidence can now be signed and submitted.");
42+
} catch (error) {
43+
if (!active) {
44+
return;
45+
}
46+
47+
setStatus(
48+
error instanceof Error ? error.message : "Unable to load dispute.",
49+
);
50+
}
51+
}
52+
53+
void loadDispute();
2454

2555
return () => {
2656
active = false;
2757
};
28-
}, [id, router]);
58+
}, [disputeId]);
59+
60+
async function ensureWallet(): Promise<string> {
61+
if (walletAddress) {
62+
return walletAddress;
63+
}
64+
65+
const address = await connectWallet();
66+
setWalletAddress(address);
67+
return address;
68+
}
69+
70+
async function onSubmitEvidence(event: FormEvent<HTMLFormElement>) {
71+
event.preventDefault();
72+
setIsSubmitting(true);
73+
setStatus("Connecting wallet for evidence approval...");
74+
75+
try {
76+
const address = await ensureWallet();
77+
setStatus("Signing evidence receipt...");
78+
79+
const signed = await signTransaction(
80+
JSON.stringify({
81+
action: "submit_evidence",
82+
dispute_id: disputeId,
83+
submitted_by: address,
84+
content,
85+
}),
86+
);
87+
88+
setSignature(signed);
89+
setStatus("Signature approved. Sending evidence to mocked backend...");
90+
91+
const savedEvidence = await api.disputes.evidence.submit(disputeId, {
92+
submitted_by: address,
93+
content,
94+
file_hash: "bafybeigdyrdeterministicevidence",
95+
});
96+
97+
setEvidence(savedEvidence);
98+
setStatus("Evidence stored. UI reflects the signed submission.");
99+
} catch (error) {
100+
setStatus(
101+
error instanceof Error ? error.message : "Evidence submission failed.",
102+
);
103+
} finally {
104+
setIsSubmitting(false);
105+
}
106+
}
29107

30108
return (
31-
<SiteShell
32-
eyebrow="Dispute Center"
33-
title="Redirecting to job-linked dispute center"
34-
description="Legacy dispute URLs now resolve to the canonical job-based route."
35-
>
36-
<div className="h-64 animate-pulse rounded-[2rem] border border-slate-200 bg-white/70" />
37-
</SiteShell>
109+
<main className="min-h-screen bg-stone-950 px-6 py-10 text-stone-50">
110+
<div className="mx-auto flex max-w-5xl flex-col gap-8">
111+
<header className="space-y-3">
112+
<Link href="/jobs" className="text-sm text-amber-300 hover:text-amber-200">
113+
Back to jobs
114+
</Link>
115+
<p className="text-xs uppercase tracking-[0.35em] text-amber-300">
116+
Dispute Workspace
117+
</p>
118+
<h1 className="text-4xl font-semibold">Dispute Verdict</h1>
119+
<p className="max-w-3xl text-sm text-stone-300">{status}</p>
120+
</header>
121+
122+
<div className="grid gap-6 lg:grid-cols-[0.9fr_1.1fr]">
123+
<section className="space-y-4 rounded-3xl border border-stone-800 bg-stone-900/70 p-6">
124+
<h2 className="text-xl font-semibold">Case Snapshot</h2>
125+
<dl className="space-y-3 text-sm text-stone-300">
126+
<div>
127+
<dt className="text-xs uppercase tracking-[0.3em] text-stone-500">
128+
Dispute ID
129+
</dt>
130+
<dd className="break-all">{dispute?.id ?? disputeId}</dd>
131+
</div>
132+
<div>
133+
<dt className="text-xs uppercase tracking-[0.3em] text-stone-500">
134+
Opened by
135+
</dt>
136+
<dd className="break-all">{dispute?.opened_by ?? "Loading..."}</dd>
137+
</div>
138+
<div>
139+
<dt className="text-xs uppercase tracking-[0.3em] text-stone-500">
140+
Verdict
141+
</dt>
142+
<dd>
143+
{verdict
144+
? `${verdict.winner} · ${verdict.freelancer_share_bps} bps`
145+
: "Pending"}
146+
</dd>
147+
</div>
148+
<div>
149+
<dt className="text-xs uppercase tracking-[0.3em] text-stone-500">
150+
Reasoning
151+
</dt>
152+
<dd>{verdict?.reasoning ?? "Loading..."}</dd>
153+
</div>
154+
<div>
155+
<dt className="text-xs uppercase tracking-[0.3em] text-stone-500">
156+
Settlement Tx
157+
</dt>
158+
<dd className="break-all">{verdict?.on_chain_tx ?? "Pending"}</dd>
159+
</div>
160+
<div>
161+
<dt className="text-xs uppercase tracking-[0.3em] text-stone-500">
162+
Wallet
163+
</dt>
164+
<dd className="break-all">{walletAddress || "Not connected"}</dd>
165+
</div>
166+
<div>
167+
<dt className="text-xs uppercase tracking-[0.3em] text-stone-500">
168+
Signed payload
169+
</dt>
170+
<dd className="break-all font-mono text-xs text-stone-400">
171+
{signature || "Awaiting wallet approval"}
172+
</dd>
173+
</div>
174+
</dl>
175+
</section>
176+
177+
<section className="rounded-3xl border border-amber-400/20 bg-amber-400/5 p-6">
178+
<h2 className="text-xl font-semibold">Submit Evidence</h2>
179+
<p className="mt-2 text-sm text-stone-300">
180+
The test suite injects a mock Freighter-compatible wallet and signs
181+
this evidence payload in-browser before the request is accepted.
182+
</p>
183+
184+
<form onSubmit={onSubmitEvidence} className="mt-6 space-y-4">
185+
<label className="block space-y-2">
186+
<span className="text-sm font-medium text-stone-200">
187+
Evidence summary
188+
</span>
189+
<textarea
190+
rows={6}
191+
value={content}
192+
onChange={(event) => setContent(event.target.value)}
193+
className="w-full rounded-2xl border border-stone-700 bg-stone-950 px-4 py-3 outline-none transition focus:border-amber-400"
194+
/>
195+
</label>
196+
197+
<button
198+
type="submit"
199+
disabled={isSubmitting}
200+
className="inline-flex min-h-12 items-center justify-center rounded-full bg-amber-300 px-6 py-3 font-semibold text-stone-950 transition hover:bg-amber-200 disabled:cursor-not-allowed disabled:bg-stone-700 disabled:text-stone-300"
201+
>
202+
{isSubmitting ? "Submitting..." : "Sign and Submit Evidence"}
203+
</button>
204+
</form>
205+
206+
{evidence ? (
207+
<div className="mt-6 rounded-2xl border border-emerald-400/30 bg-emerald-400/10 p-4">
208+
<p className="text-xs uppercase tracking-[0.3em] text-emerald-300">
209+
Evidence Recorded
210+
</p>
211+
<p className="mt-2 break-all text-sm text-stone-100">
212+
{evidence.content}
213+
</p>
214+
<p className="mt-2 break-all font-mono text-xs text-stone-400">
215+
{evidence.id}
216+
</p>
217+
</div>
218+
) : null}
219+
</section>
220+
</div>
221+
</div>
222+
</main>
38223
);
39224
}

0 commit comments

Comments
 (0)