Add closed/past/inactive status badges to user dashboard event titles
Adds an EventStatusBadge component shown next to every event title on the dashboard (registrations list, upcoming events, tickets, and the registration modal), with precedence Closed > Past > Inactive when more than one applies — reusing the badge styling already established in the supervisor events list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,10 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
||||
- User dashboard: registration-edit errors now show inside the edit popup instead of being hidden behind it.
|
||||
- User dashboard: closed events are now hidden by default alongside past events (revealed via "Show past events"), and the Edit, Make payment, and Cancel registration actions no longer appear for closed or past registrations (also enforced server-side).
|
||||
|
||||
### Added
|
||||
|
||||
- User dashboard: event titles now show a status badge (Closed / Past / Inactive, in that precedence) wherever they're listed.
|
||||
|
||||
## [1.0.0] - 2026-07-23
|
||||
|
||||
### Added
|
||||
|
||||
@@ -12,6 +12,23 @@ const isFuture = (d: string | Date) => new Date(d).getTime() > Date.now();
|
||||
const isEventOver = (ev: any) =>
|
||||
!!ev && ((ev.endDate && new Date(ev.endDate).getTime() < Date.now()) || ev.cashupStatus === 'closed');
|
||||
|
||||
// Status badge for an event, shown wherever an event title is displayed on this page.
|
||||
// Precedence when more than one applies: Closed > Past > Inactive.
|
||||
function EventStatusBadge({ event }: { event: any }) {
|
||||
if (!event) return null;
|
||||
let label: string | null = null;
|
||||
let className = '';
|
||||
if (event.cashupStatus === 'closed') {
|
||||
label = 'Closed'; className = 'bg-rose-50 text-rose-700';
|
||||
} else if (event.endDate && new Date(event.endDate).getTime() < Date.now()) {
|
||||
label = 'Past'; className = 'bg-amber-50 text-amber-700';
|
||||
} else if (event.isActive === false) {
|
||||
label = 'Inactive'; className = 'bg-gray-100 text-gray-500';
|
||||
}
|
||||
if (!label) return null;
|
||||
return <span className={`text-[10px] px-1.5 py-0.5 rounded ${className}`}>{label}</span>;
|
||||
}
|
||||
|
||||
// Effective unit price for an event option (or one of its variants), early-bird aware.
|
||||
// Used by the registration editor, which works off raw /api/events/:id data rather than
|
||||
// a registration's priceSnapshot — mirrors the pricing logic in register/[eventId]/RegisterForm.tsx.
|
||||
@@ -655,7 +672,10 @@ export default function UserDashboardPage() {
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="font-medium">{r.event?.title || r.eventId}</div>
|
||||
<div className="font-medium flex items-center gap-2">
|
||||
<span>{r.event?.title || r.eventId}</span>
|
||||
<EventStatusBadge event={r.event} />
|
||||
</div>
|
||||
<div className="text-xs text-gray-600">
|
||||
Status: {r.status}{isCancelled && <span className="ml-2 inline-block text-[10px] px-1.5 py-0.5 rounded bg-gray-200 text-gray-700">cancelled</span>}
|
||||
</div>
|
||||
@@ -728,7 +748,10 @@ export default function UserDashboardPage() {
|
||||
<ul className="grid md:grid-cols-2 gap-3">
|
||||
{upcomingEvents.map(ev => (
|
||||
<li key={ev.id} className="border rounded p-4">
|
||||
<div className="font-medium">{ev.title}</div>
|
||||
<div className="font-medium flex items-center gap-2">
|
||||
<span>{ev.title}</span>
|
||||
<EventStatusBadge event={ev} />
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">{new Date(ev.startDate).toLocaleString()} - {new Date(ev.endDate).toLocaleString()}</div>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
<button
|
||||
@@ -806,6 +829,7 @@ export default function UserDashboardPage() {
|
||||
<input type="checkbox" checked={selected[t.id]} onChange={e => toggleSelect(t.id, e.target.checked)} />
|
||||
<span className="font-medium">{t.event?.title || t.eventId}</span>
|
||||
</label>
|
||||
<EventStatusBadge event={t.event} />
|
||||
{eventDate && <span className="text-xs text-gray-500">{formatDate(eventDate)}</span>}
|
||||
</div>
|
||||
<div className="text-sm text-gray-700">{type}</div>
|
||||
@@ -832,7 +856,10 @@ export default function UserDashboardPage() {
|
||||
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50" onClick={() => { setEditMode(false); setEditError(null); setShowCancelConfirm(false); setActiveRegId(null); }}>
|
||||
<div className="bg-white rounded-lg shadow-lg max-w-xl w-full mx-4 p-5 max-h-[90vh] overflow-y-auto" onClick={e => e.stopPropagation()}>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-lg font-semibold">{activeReg.event?.title || activeReg.eventId}</h3>
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
<span>{activeReg.event?.title || activeReg.eventId}</span>
|
||||
<EventStatusBadge event={activeReg.event} />
|
||||
</h3>
|
||||
<button className="px-2.5 py-1 text-sm rounded bg-gray-100 hover:bg-gray-200 text-gray-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1" onClick={() => { setEditMode(false); setEditError(null); setShowCancelConfirm(false); setActiveRegId(null); }}>Close</button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
|
||||
Reference in New Issue
Block a user