Blocks · @jengacore/dispute-case
Dispute case
Disputes case page: header with SLA, timeline, evidence and a decision that requires a reason.
Disputes
DSP-2026-00412Goods not receivedEvidence due22:00until SLA
Horn Commercial Bank v. Sahan Mobile Money
12,400.00USDTimeline
- Horn Commercial Bank02 Oct 11:40completedRaised a dispute: goods not receivedtx_01J9…H3V
- Hub operations02 Oct 12:05completedAccepted the case and notified Sahan Mobile Money
- Sahan Mobile Moneydue 04 Oct 12:05current stepEvidence requested: merchant delivery record
- Hub operationsdue 06 Oct 12:05upcomingDecision
Evidence
- Customer statement.pdf02 Oct
Decision
Install
terminal
npx shadcn@latest add @jengacore/dispute-caseWithout the namespace configured: npx shadcn@latest add https://ui.jengacore.com/r/<new-york-v4|base-nova>/dispute-case.json
Source
components/blocks/dispute-case.tsx
"use client"
import * as React from "react"
import { Paperclip } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Amount } from "@/components/jc/amount"
import { CaseTimeline, type TimelineStep } from "@/components/jc/case-timeline"
import { ConfirmDialog } from "@/components/jc/confirm-dialog"
import { IdChip } from "@/components/jc/id-chip"
import { SlaChip } from "@/components/jc/sla-chip"
import { StatusBadge } from "@/components/jc/status-badge"
const STEPS: TimelineStep[] = [
{ id: "1", actor: "Horn Commercial Bank", action: "Raised a dispute: goods not received", reference: "tx_01J9ZK4Q7MXH3V", at: "02 Oct 11:40", state: "done" },
{ id: "2", actor: "Hub operations", action: "Accepted the case and notified Sahan Mobile Money", at: "02 Oct 12:05", state: "done" },
{ id: "3", actor: "Sahan Mobile Money", action: "Evidence requested: merchant delivery record", at: "04 Oct 12:05", due: true, state: "current" },
{ id: "4", actor: "Hub operations", action: "Decision", at: "06 Oct 12:05", due: true, state: "future" },
]
/** Disputes case page: header → timeline → evidence → decision. Decisions need a reason. */
export function DisputeCase() {
const [deciding, setDeciding] = React.useState<null | "payer" | "payee">(null)
return (
<div className="flex flex-col gap-6">
<header className="flex flex-wrap items-start justify-between gap-4">
<div className="grid gap-2">
<div className="flex flex-wrap items-center gap-2">
<IdChip id="DSP-2026-00412" />
<Badge tone="outline">Goods not received</Badge>
<StatusBadge domain="dispute" status="evidence_due" />
<SlaChip minutes={22 * 60} warnBelow={24 * 60} />
</div>
<h1 className="text-2xl leading-8 font-semibold">Horn Commercial Bank v. Sahan Mobile Money</h1>
<Amount value="12400" currency="USD" size="lg" />
</div>
</header>
<div className="grid gap-6 lg:grid-cols-[1fr_340px]">
<Card>
<CardHeader>
<CardTitle>Timeline</CardTitle>
</CardHeader>
<CardContent>
<CaseTimeline steps={STEPS} />
</CardContent>
</Card>
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle>Evidence</CardTitle>
</CardHeader>
<CardContent>
<ul className="grid gap-2 text-[13.5px]">
<li className="flex items-center gap-2">
<Paperclip className="size-4 stroke-[1.75] text-muted-foreground" />
Customer statement.pdf
<span className="ml-auto font-mono text-xs text-muted-foreground">02 Oct</span>
</li>
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Decision</CardTitle>
</CardHeader>
<CardContent className="grid gap-2">
<Button variant="outline" onClick={() => setDeciding("payee")}>Decide for Sahan Mobile Money</Button>
<Button variant="destructive" onClick={() => setDeciding("payer")}>Decide for Horn Commercial Bank</Button>
</CardContent>
</Card>
</div>
</div>
<ConfirmDialog
open={deciding !== null}
onOpenChange={(o) => !o && setDeciding(null)}
title={deciding === "payer" ? "Reverse 12,400.00 USD to Horn Commercial Bank?" : "Close the case for Sahan Mobile Money?"}
details={[
{ label: "Case", value: "DSP-2026-00412" },
{ label: "Transfer", value: "tx_01J9ZK4Q7MXH3V" },
{ label: "Amount", value: "12,400.00 USD" },
]}
confirmLabel={deciding === "payer" ? "Reverse transfer" : "Close case"}
destructive={deciding === "payer"}
requireReason
onConfirm={() => new Promise((r) => setTimeout(r, 600))}
/>
</div>
)
}