Spaces:
Sleeping
Sleeping
| import { useEffect, useRef } from "react"; | |
| import { ArrowRight, X } from "lucide-react"; | |
| const STEPS: { n: number; title: string; body: string }[] = [ | |
| { | |
| n: 1, | |
| title: "Pick a campaign", | |
| body: "Choose a submission from the review queue on the left.", | |
| }, | |
| { | |
| n: 2, | |
| title: "Run AI triage", | |
| body: "The agent investigates and recommends — citing the exact policy rules, surfacing risk signals, and showing the deterministic policy gate, which enforces safety rules in code and can only route a case to a human (never auto-approve or auto-reject).", | |
| }, | |
| { | |
| n: 3, | |
| title: "You decide", | |
| body: "Approve, Reject, or Request info. Overriding the AI requires a written reason — every decision is recorded against your name.", | |
| }, | |
| ]; | |
| // camp id -> one-line reason it's worth trying. Clicking selects the campaign and closes the modal. | |
| const SHOWCASE: { id: string; note: string }[] = [ | |
| { id: "camp-017", note: "approves clearing a debt's principal (hardship relief)" }, | |
| { id: "camp-005", note: "rejects an interest-bearing investment — reads policy, not keywords" }, | |
| { id: "camp-015", note: "prompt injection: flagged and escalated, never obeyed" }, | |
| ]; | |
| export function WelcomeModal({ | |
| onClose, | |
| onPick, | |
| }: { | |
| onClose: () => void; | |
| onPick: (id: string) => void; | |
| }) { | |
| const startRef = useRef<HTMLButtonElement>(null); | |
| useEffect(() => { | |
| startRef.current?.focus(); | |
| const onKey = (e: KeyboardEvent) => { | |
| if (e.key === "Escape") onClose(); | |
| }; | |
| window.addEventListener("keydown", onKey); | |
| return () => window.removeEventListener("keydown", onKey); | |
| }, [onClose]); | |
| return ( | |
| <div | |
| className="fixed inset-0 z-50 grid place-items-center bg-black/40 p-4 anim-fade" | |
| onClick={onClose} | |
| > | |
| <div | |
| role="dialog" | |
| aria-modal="true" | |
| aria-labelledby="welcome-title" | |
| onClick={(e) => e.stopPropagation()} | |
| className="anim-pop w-full max-w-lg max-h-[90vh] overflow-y-auto rounded-2xl border border-line bg-surface shadow-card p-6 sm:p-7" | |
| > | |
| <div className="flex items-start gap-3"> | |
| <div className="grid place-items-center w-10 h-10 rounded-full bg-pine text-gold font-display font-semibold text-xl seal-ring shrink-0"> | |
| ا | |
| </div> | |
| <div className="flex-1"> | |
| <h2 id="welcome-title" className="font-display text-2xl font-semibold text-heading leading-tight"> | |
| Amana — Campaign Trust & Safety triage | |
| </h2> | |
| <p className="text-xs text-muted mt-0.5">A moderator's bench for fundraising review</p> | |
| </div> | |
| <button | |
| onClick={onClose} | |
| aria-label="Close" | |
| className="text-faint hover:text-heading shrink-0" | |
| > | |
| <X size={18} /> | |
| </button> | |
| </div> | |
| <p className="text-sm text-body leading-relaxed mt-4"> | |
| An AI agent reviews each incoming fundraising campaign against policy and recommends{" "} | |
| <b className="text-heading">APPROVE / REJECT / ESCALATE</b> — with cited rule IDs and surfaced | |
| risk signals. <b className="text-heading">A human moderator makes the final decision.</b> | |
| </p> | |
| <div className="mt-5 space-y-3"> | |
| {STEPS.map((s) => ( | |
| <div key={s.n} className="flex gap-3"> | |
| <span className="grid place-items-center w-6 h-6 rounded-full bg-accent-soft text-accent-on text-xs font-semibold shrink-0 mt-0.5"> | |
| {s.n} | |
| </span> | |
| <div> | |
| <div className="text-sm font-semibold text-heading">{s.title}</div> | |
| <p className="text-sm text-muted leading-relaxed">{s.body}</p> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| <div className="mt-5"> | |
| <div className="text-xs font-semibold uppercase tracking-wide text-faint mb-2">Try these</div> | |
| <div className="space-y-1.5"> | |
| {SHOWCASE.map((c) => ( | |
| <button | |
| key={c.id} | |
| onClick={() => onPick(c.id)} | |
| className="w-full text-left flex items-baseline gap-2 rounded-lg border border-line hover:border-accent hover:bg-accent-soft/50 px-3 py-2 transition" | |
| > | |
| <span className="font-mono text-xs font-semibold text-accent shrink-0">{c.id}</span> | |
| <span className="text-sm text-muted">{c.note}</span> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| <button | |
| ref={startRef} | |
| onClick={onClose} | |
| className="mt-6 w-full flex items-center justify-center gap-2 bg-pine hover:bg-pine-deep text-white rounded-lg py-2.5 text-sm font-medium shadow-sm transition" | |
| > | |
| Start reviewing <ArrowRight size={16} /> | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| } | |