jengacore.shadcn registry · Tailwind v4 · Radix and Base UI
Components
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.00USD
Timeline
  1. Horn Commercial Bank02 Oct 11:40completed
    Raised a dispute: goods not received
    tx_01J9…H3V
  2. Hub operations02 Oct 12:05completed
    Accepted the case and notified Sahan Mobile Money
  3. Sahan Mobile Moneydue 04 Oct 12:05current step
    Evidence requested: merchant delivery record
  4. Hub operationsdue 06 Oct 12:05upcoming
    Decision
Evidence
  • Customer statement.pdf02 Oct
Decision

Install

terminal
npx shadcn@latest add @jengacore/dispute-case

Without 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>
  )
}