Add registration status badges and auto-dismissing dashboard messages

Two consistency fixes requested after the payment-method work:

1. Registration status (pending/confirmed/partial_paid/paid/cancelled)
   was printed as a raw string on the user dashboard. Added
   RegistrationStatusBadge mirroring the existing EventStatusBadge
   pattern, using the same status colors already established on
   dashboard/admin/registrations.

2. Inline success/error banners across dashboard pages persisted
   indefinitely. Added a shared useDismissingState hook (drop-in
   useState replacement that auto-clears a truthy value after 7s,
   resetting the timer on each update) and swapped it in across ~24
   dashboard files. Excluded: message-only modal dialogs (ticket-
   scanning's success/error confirmations) and two states that mix
   live form-validation feedback with async results inside actively-
   open forms (the registration-edit modal's editError, the event
   create/edit modal's error) - those keep persisting until the user
   acts, since auto-hiding a "fix this field" message mid-edit would
   be a regression. Also fixed at-the-door's existing bespoke
   auto-dismiss timers (10s/15s, one mislabeled as "5s") to the same
   consistent 7s, and removed admin/settings' manual x dismiss button
   in favor of the same auto-only behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 10:17:53 +02:00
co-authored by Claude Sonnet 5
parent 193739c042
commit f3e6525467
26 changed files with 119 additions and 79 deletions
@@ -5,6 +5,7 @@ import { useAuth } from "@/hooks/useAuth";
import { useRouter } from "next/navigation";
import { apiFetch } from "@/lib/api";
import { scoreUser } from "@/lib/fuzzyMatch";
import { useDismissingState } from "@/hooks/useDismissingState";
type Mode = "registration" | "payment" | "tickets" | "refund";
@@ -154,30 +155,8 @@ export default function AtTheDoorPage() {
const [mode, setMode] = useState<Mode>("registration");
const [activeRegistration, setActiveRegistration] = useState<any | null>(null);
const [info, setInfo] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!info) return;
const id = setTimeout(() => {
setInfo(null);
}, 10000); // ⏱ disappears after 5s
return () => clearTimeout(id);
}, [info]);
useEffect(() => {
if (!error) return;
const id = setTimeout(() => {
setError(null);
}, 15000); // errors linger slightly longer
return () => clearTimeout(id);
}, [error]);
const [info, setInfo] = useDismissingState<string | null>(null);
const [error, setError] = useDismissingState<string | null>(null);
const handleRegistrationCreated = (registration: any) => {
setActiveRegistration(registration);
@@ -1332,7 +1311,7 @@ function SendTicketsModal({ open, onClose, token, registration, setError, setInf
const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
const [sending, setSending] = useState(false);
const [localError, setLocalError] = useState("");
const [localError, setLocalError] = useDismissingState("");
const [localInfo, setLocalInfo] = useState("");
useEffect(() => {
@@ -1369,7 +1348,7 @@ function SendTicketsModal({ open, onClose, token, registration, setError, setInf
},
});
setLocalInfo("Tickets sent successfully.");
setTimeout(() => { setLocalInfo(""); onClose(); }, 2000);
setTimeout(() => { setLocalInfo(""); onClose(); }, 7000);
} catch (e: any) {
setLocalError(e?.message || "Failed to send tickets");
} finally {
@@ -1461,7 +1440,7 @@ function RefundModal({ open, onClose, token, registration, maxRefund, onRefunded
const [method, setMethod] = useState("cash");
const [reason, setReason] = useState("");
const [saving, setSaving] = useState(false);
const [localError, setLocalError] = useState("");
const [localError, setLocalError] = useDismissingState("");
useEffect(() => {
if (open) { setAmount(""); setReason(""); setLocalError(""); }