From 1a1994ce8a7a0acf5e18e52f89a2665a8382ca2b Mon Sep 17 00:00:00 2001 From: Obiajulu-gif Date: Fri, 27 Mar 2026 13:36:37 +0100 Subject: [PATCH 1/7] Add Playwright E2E mock wallet and Axum backend --- apps/web/app/disputes/[id]/page.tsx | 225 ++++++++++++++++++--- apps/web/app/globals.css | 146 ++------------ apps/web/app/jobs/new/page.tsx | 265 ++++++++++++++----------- apps/web/app/jobs/page.tsx | 261 +++++++++---------------- apps/web/app/layout.tsx | 32 +-- apps/web/app/page.tsx | 13 +- backend/src/bin/mock_e2e.rs | 271 ++++++++++++++++++++++++++ package.json | 5 +- playwright.config.ts | 45 ++++- test-results/.last-run.json | 2 +- tests/e2e/fixtures.ts | 51 +++++ tests/e2e/wallet-and-disputes.spec.ts | 38 ++++ 12 files changed, 878 insertions(+), 476 deletions(-) create mode 100644 backend/src/bin/mock_e2e.rs create mode 100644 tests/e2e/fixtures.ts create mode 100644 tests/e2e/wallet-and-disputes.spec.ts diff --git a/apps/web/app/disputes/[id]/page.tsx b/apps/web/app/disputes/[id]/page.tsx index 1fd4ee8..1145767 100644 --- a/apps/web/app/disputes/[id]/page.tsx +++ b/apps/web/app/disputes/[id]/page.tsx @@ -1,39 +1,218 @@ "use client"; -import { useEffect } from "react"; -import { useParams, useRouter } from "next/navigation"; -import { SiteShell } from "@/components/site-shell"; -import { api } from "@/lib/api"; +import Link from "next/link"; +import { useParams } from "next/navigation"; +import { FormEvent, useEffect, useState } from "react"; + +import { api, type Dispute, type Evidence, type Verdict } from "@/lib/api"; +import { connectWallet, signTransaction } from "@/lib/stellar"; export default function DisputePage() { - const { id } = useParams<{ id: string }>(); - const router = useRouter(); + const params = useParams<{ id: string }>(); + const disputeId = params.id; + + const [dispute, setDispute] = useState(null); + const [verdict, setVerdict] = useState(null); + const [walletAddress, setWalletAddress] = useState(""); + const [signature, setSignature] = useState(""); + const [evidence, setEvidence] = useState(null); + const [content, setContent] = useState( + "Freelancer uploaded timestamped delivery proof and reviewer notes.", + ); + const [status, setStatus] = useState("Loading dispute state..."); + const [isSubmitting, setIsSubmitting] = useState(false); useEffect(() => { let active = true; - void api.disputes - .get(id) - .then((dispute) => { - if (!active) return; - router.replace(`/jobs/${dispute.job_id}/dispute?disputeId=${dispute.id}`); - }) - .catch(() => { - if (!active) return; - }); + async function loadDispute() { + try { + const [nextDispute, nextVerdict] = await Promise.all([ + api.disputes.get(disputeId), + api.disputes.verdict(disputeId), + ]); + + if (!active) { + return; + } + + setDispute(nextDispute); + setVerdict(nextVerdict); + setStatus("Dispute loaded. Evidence can now be signed and submitted."); + } catch (error) { + if (!active) { + return; + } + + setStatus( + error instanceof Error ? error.message : "Unable to load dispute.", + ); + } + } + + void loadDispute(); return () => { active = false; }; - }, [id, router]); + }, [disputeId]); + + async function ensureWallet(): Promise { + if (walletAddress) { + return walletAddress; + } + + const address = await connectWallet(); + setWalletAddress(address); + return address; + } + + async function onSubmitEvidence(event: FormEvent) { + event.preventDefault(); + setIsSubmitting(true); + setStatus("Connecting wallet for evidence approval..."); + + try { + const address = await ensureWallet(); + setStatus("Signing evidence receipt..."); + + const signed = await signTransaction( + JSON.stringify({ + action: "submit_evidence", + dispute_id: disputeId, + submitted_by: address, + content, + }), + ); + + setSignature(signed); + setStatus("Signature approved. Sending evidence to mocked backend..."); + + const savedEvidence = await api.disputes.submitEvidence(disputeId, { + submitted_by: address, + content, + file_hash: "bafybeigdyrdeterministicevidence", + }); + + setEvidence(savedEvidence); + setStatus("Evidence stored. UI reflects the signed submission."); + } catch (error) { + setStatus( + error instanceof Error ? error.message : "Evidence submission failed.", + ); + } finally { + setIsSubmitting(false); + } + } return ( - -
- +
+
+
+ + Back to jobs + +

+ Dispute Workspace +

+

Dispute Verdict

+

{status}

+
+ +
+
+

Case Snapshot

+
+
+
+ Dispute ID +
+
{dispute?.id ?? disputeId}
+
+
+
+ Opened by +
+
{dispute?.opened_by ?? "Loading..."}
+
+
+
+ Verdict +
+
+ {verdict + ? `${verdict.winner} ยท ${verdict.freelancer_share_bps} bps` + : "Pending"} +
+
+
+
+ Reasoning +
+
{verdict?.reasoning ?? "Loading..."}
+
+
+
+ Wallet +
+
{walletAddress || "Not connected"}
+
+
+
+ Signed payload +
+
+ {signature || "Awaiting wallet approval"} +
+
+
+
+ +
+

Submit Evidence

+

+ The test suite injects a mock Freighter-compatible wallet and signs + this evidence payload in-browser before the request is accepted. +

+ +
+