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: 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).
|
- 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
|
## [1.0.0] - 2026-07-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -12,6 +12,23 @@ const isFuture = (d: string | Date) => new Date(d).getTime() > Date.now();
|
|||||||
const isEventOver = (ev: any) =>
|
const isEventOver = (ev: any) =>
|
||||||
!!ev && ((ev.endDate && new Date(ev.endDate).getTime() < Date.now()) || ev.cashupStatus === 'closed');
|
!!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.
|
// 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
|
// 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.
|
// 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 className="flex items-start justify-between gap-3">
|
||||||
<div>
|
<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">
|
<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>}
|
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>
|
</div>
|
||||||
@@ -728,7 +748,10 @@ export default function UserDashboardPage() {
|
|||||||
<ul className="grid md:grid-cols-2 gap-3">
|
<ul className="grid md:grid-cols-2 gap-3">
|
||||||
{upcomingEvents.map(ev => (
|
{upcomingEvents.map(ev => (
|
||||||
<li key={ev.id} className="border rounded p-4">
|
<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="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">
|
<div className="flex flex-wrap gap-2 mt-2">
|
||||||
<button
|
<button
|
||||||
@@ -806,6 +829,7 @@ export default function UserDashboardPage() {
|
|||||||
<input type="checkbox" checked={selected[t.id]} onChange={e => toggleSelect(t.id, e.target.checked)} />
|
<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>
|
<span className="font-medium">{t.event?.title || t.eventId}</span>
|
||||||
</label>
|
</label>
|
||||||
|
<EventStatusBadge event={t.event} />
|
||||||
{eventDate && <span className="text-xs text-gray-500">{formatDate(eventDate)}</span>}
|
{eventDate && <span className="text-xs text-gray-500">{formatDate(eventDate)}</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-700">{type}</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="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="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">
|
<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>
|
<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>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user