Unify admin ticket creation into one modal with first-class payment status.
- Replace the three Attendees-tab modals (Manual Ticket / Add at Door / Invite Guest) with a single Add Ticket modal: Paid/Unpaid/Guest segmented control, shared fields, "Check in now" for all types, and a live "what happens" preview, backed by one POST /api/tickets/admin/add. - Add tickets.payment_status (paid | unpaid | comp) with a backfill migration; keep it in sync on every payment-settlement path (mark-paid, admin approval, Lightning, free bookings, hold recovery). - Show Paid/Unpaid/Comp badges in the attendee list, count only paid tickets toward revenue, let unpaid tickets be resolved via Mark Paid, and flag unpaid tickets with their balance due in the door scanner. - Replace the per-page useStatsPrivacy hook with an admin-wide PrivacyContext + SensitiveValue mask, toggled from the admin layout. - Add server-side pagination with page-size options to the users page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e38d14970d
commit
9b2668f498
@@ -4,8 +4,10 @@ import { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useLanguage } from '@/context/LanguageContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { usePrivacy } from '@/context/PrivacyContext';
|
||||
import { adminApi, DashboardData } from '@/lib/api';
|
||||
import Card from '@/components/ui/Card';
|
||||
import SensitiveValue from '@/components/admin/SensitiveValue';
|
||||
import {
|
||||
UsersIcon,
|
||||
CalendarIcon,
|
||||
@@ -20,6 +22,7 @@ import { parseDate, eventSpotsLeft, isEventSoldOut } from '@/lib/utils';
|
||||
export default function AdminDashboardPage() {
|
||||
const { t, locale } = useLanguage();
|
||||
const { user } = useAuth();
|
||||
const { privacyMode } = usePrivacy();
|
||||
const [data, setData] = useState<DashboardData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -89,6 +92,7 @@ export default function AdminDashboardPage() {
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
{!privacyMode && (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
{statCards.map((stat) => (
|
||||
<Link key={stat.label} href={stat.href}>
|
||||
@@ -106,6 +110,7 @@ export default function AdminDashboardPage() {
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Alerts */}
|
||||
@@ -131,7 +136,7 @@ export default function AdminDashboardPage() {
|
||||
<ExclamationTriangleIcon className="w-5 h-5 text-orange-600" />
|
||||
<div>
|
||||
<span className="text-sm font-medium">{event.title}</span>
|
||||
<p className="text-xs text-gray-500">Only {spotsLeft} spots left ({percentFull}% full)</p>
|
||||
<p className="text-xs text-gray-500"><SensitiveValue>Only {spotsLeft} spots left ({percentFull}% full)</SensitiveValue></p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="badge badge-warning">Low capacity</span>
|
||||
@@ -169,7 +174,7 @@ export default function AdminDashboardPage() {
|
||||
<CurrencyDollarIcon className="w-5 h-5 text-yellow-600" />
|
||||
<span className="text-sm">Payments awaiting verification</span>
|
||||
</div>
|
||||
<span className="badge badge-warning">{data.stats.awaitingApprovalPayments}</span>
|
||||
<span className="badge badge-warning"><SensitiveValue>{data.stats.awaitingApprovalPayments}</SensitiveValue></span>
|
||||
</Link>
|
||||
)}
|
||||
{/* Informational: opened checkouts that never paid — hold no seats */}
|
||||
@@ -182,7 +187,7 @@ export default function AdminDashboardPage() {
|
||||
<CurrencyDollarIcon className="w-5 h-5 text-gray-400" />
|
||||
<span className="text-sm text-gray-500">Unpaid started bookings</span>
|
||||
</div>
|
||||
<span className="badge badge-info">{data.stats.pendingPayments}</span>
|
||||
<span className="badge badge-info"><SensitiveValue>{data.stats.pendingPayments}</SensitiveValue></span>
|
||||
</Link>
|
||||
)}
|
||||
{data && data.stats.newContacts > 0 && (
|
||||
@@ -231,14 +236,16 @@ export default function AdminDashboardPage() {
|
||||
<p className="font-medium text-sm">{event.title}</p>
|
||||
<p className="text-xs text-gray-500">{formatDate(event.startDatetime)}</p>
|
||||
</div>
|
||||
<span className="text-sm text-gray-600">
|
||||
{(event.bookedCount || 0) + (event.claimedCount || 0)}/{event.capacity}
|
||||
{(event.claimedCount || 0) > 0 && (
|
||||
<span className="text-xs text-yellow-600 block text-right">
|
||||
{event.claimedCount} pending approval
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{!privacyMode && (
|
||||
<span className="text-sm text-gray-600">
|
||||
{(event.bookedCount || 0) + (event.claimedCount || 0)}/{event.capacity}
|
||||
{(event.claimedCount || 0) > 0 && (
|
||||
<span className="text-xs text-yellow-600 block text-right">
|
||||
{event.claimedCount} pending approval
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
@@ -246,6 +253,7 @@ export default function AdminDashboardPage() {
|
||||
</Card>
|
||||
|
||||
{/* Quick Stats */}
|
||||
{!privacyMode && (
|
||||
<Card className="p-6">
|
||||
<h2 className="font-semibold text-lg mb-4">Quick Stats</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
@@ -261,6 +269,7 @@ export default function AdminDashboardPage() {
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user