- 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>
67 lines
1.8 KiB
TypeScript
67 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
|
|
|
|
interface PrivacyContextType {
|
|
/** true = stats and sensitive data are hidden */
|
|
privacyMode: boolean;
|
|
setPrivacyMode: (value: boolean) => void;
|
|
togglePrivacyMode: () => void;
|
|
}
|
|
|
|
const PrivacyContext = createContext<PrivacyContextType | undefined>(undefined);
|
|
|
|
// Same key the old per-page useStatsPrivacy hook used ('true' = hidden), so
|
|
// existing operators keep their saved preference.
|
|
const STORAGE_KEY = 'spanglish-admin-stats-hidden';
|
|
|
|
export function PrivacyProvider({ children }: { children: ReactNode }) {
|
|
const [privacyMode, setPrivacyModeState] = useState(false);
|
|
|
|
useEffect(() => {
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
if (stored !== null) {
|
|
setPrivacyModeState(stored === 'true');
|
|
}
|
|
} catch {
|
|
// localStorage unavailable (private mode etc.) - keep default
|
|
}
|
|
}, []);
|
|
|
|
const setPrivacyMode = useCallback((value: boolean) => {
|
|
setPrivacyModeState(value);
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, String(value));
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}, []);
|
|
|
|
const togglePrivacyMode = useCallback(() => {
|
|
setPrivacyModeState((prev) => {
|
|
const next = !prev;
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, String(next));
|
|
} catch {
|
|
// ignore
|
|
}
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
return (
|
|
<PrivacyContext.Provider value={{ privacyMode, setPrivacyMode, togglePrivacyMode }}>
|
|
{children}
|
|
</PrivacyContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function usePrivacy() {
|
|
const context = useContext(PrivacyContext);
|
|
if (context === undefined) {
|
|
throw new Error('usePrivacy must be used within a PrivacyProvider');
|
|
}
|
|
return context;
|
|
}
|