diff --git a/app/api/reset/route.ts b/app/api/reset/route.ts new file mode 100644 index 0000000..9779822 --- /dev/null +++ b/app/api/reset/route.ts @@ -0,0 +1,84 @@ +import { prisma } from "@/lib/store/prisma"; +import { BUYER_AUTH_PENDING } from "@/lib/store/types"; +import { safeJson, errorJson } from "@/lib/api/responses"; +import { NextResponse } from "next/server"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function POST() { + // Production protection: prevent accidental wipes on production environment + if (process.env.NODE_ENV === "production" && process.env.ALLOW_DEMO_RESET !== "true") { + return new NextResponse("Forbidden: Database reset is disabled in production environment.", { status: 403 }); + } + + try { + // Idempotent reseed: clear children first (FK order), then parents. + await prisma.auditEntry.deleteMany(); + await prisma.billOfLading.deleteMany(); + await prisma.escrowTransfer.deleteMany(); + await prisma.contractTerms.deleteMany(); + await prisma.letterOfCredit.deleteMany(); + + // LC-1 — VALID: required port == target port, value $25,000 within $50,000 cap. + await prisma.letterOfCredit.create({ + data: { + buyerRef: BUYER_AUTH_PENDING, + exporterRef: "exporter-ref:acme-textiles-001", + valueCents: 2_500_000, + currency: "AUD", + targetPort: "Port of Rotterdam (NLRTM)", + state: "INITIATED", + terms: { + create: { + requiredPort: "Port of Rotterdam (NLRTM)", + requiredBolStatus: "DELIVERED", + maxValueCents: 5_000_000, + }, + }, + }, + }); + + // LC-2 — PORT MISMATCH: cargo targets Rotterdam, but terms require Hamburg. + await prisma.letterOfCredit.create({ + data: { + buyerRef: BUYER_AUTH_PENDING, + exporterRef: "exporter-ref:north-sea-foods-002", + valueCents: 1_800_000, + currency: "AUD", + targetPort: "Port of Rotterdam (NLRTM)", + state: "INITIATED", + terms: { + create: { + requiredPort: "Port of Hamburg (DEHAM)", + requiredBolStatus: "DELIVERED", + maxValueCents: 5_000_000, + }, + }, + }, + }); + + // LC-3 — OVER VALUE: value $95,000 exceeds the $50,000 contract cap. + await prisma.letterOfCredit.create({ + data: { + buyerRef: BUYER_AUTH_PENDING, + exporterRef: "exporter-ref:pacific-machinery-003", + valueCents: 9_500_000, + currency: "AUD", + targetPort: "Port of Singapore (SGSIN)", + state: "INITIATED", + terms: { + create: { + requiredPort: "Port of Singapore (SGSIN)", + requiredBolStatus: "DELIVERED", + maxValueCents: 5_000_000, + }, + }, + }, + }); + + return safeJson({ ok: true, message: "Database reset and seeded successfully." }); + } catch (err) { + return errorJson(err, 500); + } +} diff --git a/app/page.tsx b/app/page.tsx index a9220ec..7cbeed6 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -510,6 +510,11 @@ export default function Home() { const [formRequiredPort, setFormRequiredPort] = useState("Rotterdam"); const [formMaxLimit, setFormMaxLimit] = useState("25000"); + // Reset DB State + const [isResetOpen, setIsResetOpen] = useState(false); + const [confirmResetText, setConfirmResetText] = useState(""); + const [isResetting, setIsResetting] = useState(false); + const handlePortChange = (val: string) => { setFormPort(val); if (!customizePolicy) { @@ -566,6 +571,28 @@ export default function Home() { } } + async function handleResetDb() { + setIsResetting(true); + setError(null); + try { + const response = await fetch("/api/reset", { + method: "POST", + }); + if (!response.ok) { + const errData = await response.json(); + throw new Error(errData.error || "Failed to reset database"); + } + setIsResetOpen(false); + setConfirmResetText(""); + setSelectedId(null); + await refresh(); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setIsResetting(false); + } + } + const selectedLc = useMemo( () => lcs.find((lc) => lc.id === selectedId) ?? lcs[0], [lcs, selectedId], @@ -666,7 +693,16 @@ export default function Home() {
-

Autonomous trade finance node

+

+ Autonomous trade finance node + +

Letter of Credit Agent Console

@@ -1093,6 +1129,82 @@ export default function Home() {
)} + {isResetOpen && ( + <> +
{ + if (!isResetting) { + setIsResetOpen(false); + setConfirmResetText(""); + } + }} + className="fixed inset-0 bg-black/85 backdrop-blur-sm z-[60] transition-opacity duration-300 animate-fade-in" + /> +
+
+ ⚠️ +
+

Dangerous Administrative Action

+

Reset Demo Database

+
+
+ +
+
+ Warning & Disclaimer: + This action is intended strictly for the demo version of the application. It will permanently delete: +
    +
  • All Letters of Credit contracts
  • +
  • All simulated Bills of Lading
  • +
  • All escrow hold & payout records
  • +
  • All cryptographic audit ledger logs
  • +
+
+ +

+ Once executed, the database will be seeded back to its original 3 demo contracts. There is no undo. +

+ +
+ + setConfirmResetText(e.target.value)} + placeholder="RESET" + disabled={isResetting} + className="w-full bg-black/60 border border-neutral-900 rounded-sm px-3 py-2 text-neutral-200 placeholder-neutral-800 focus:outline-none focus:border-red-500/50 transition font-mono uppercase tracking-wider text-center" + /> +
+ +
+ + +
+
+
+ + )} ); }