525 lines
26 KiB
TypeScript
525 lines
26 KiB
TypeScript
import type { Dispatch, FormEvent, SetStateAction } from 'react';
|
|
import { Ticket } from '@/lib/api';
|
|
import Card from '@/components/ui/Card';
|
|
import Button from '@/components/ui/Button';
|
|
import { BottomSheet } from '@/components/admin/MobileComponents';
|
|
import clsx from 'clsx';
|
|
import {
|
|
CheckCircleIcon,
|
|
EnvelopeIcon,
|
|
PlusIcon,
|
|
StarIcon,
|
|
XMarkIcon,
|
|
} from '@heroicons/react/24/outline';
|
|
import type { AttendeeStatusFilter, AttendeeFormState, AddAtDoorFormState } from '../_types';
|
|
|
|
interface EventModalsProps {
|
|
// counts + filter
|
|
ticketsCount: number;
|
|
pendingCount: number;
|
|
confirmedCount: number;
|
|
checkedInCount: number;
|
|
cancelledCount: number;
|
|
onHoldCount: number;
|
|
statusFilter: AttendeeStatusFilter;
|
|
setStatusFilter: (value: AttendeeStatusFilter) => void;
|
|
// mobile filter sheet
|
|
mobileFilterOpen: boolean;
|
|
setMobileFilterOpen: (value: boolean) => void;
|
|
// add ticket sheet
|
|
showAddTicketSheet: boolean;
|
|
setShowAddTicketSheet: (value: boolean) => void;
|
|
// export sheets
|
|
showExportSheet: boolean;
|
|
setShowExportSheet: (value: boolean) => void;
|
|
handleExportAttendees: (status: 'confirmed' | 'checked_in' | 'confirmed_pending' | 'all') => void;
|
|
showTicketExportSheet: boolean;
|
|
setShowTicketExportSheet: (value: boolean) => void;
|
|
handleExportTickets: (status: 'confirmed' | 'checked_in' | 'all') => void;
|
|
// add at door
|
|
showAddAtDoorModal: boolean;
|
|
setShowAddAtDoorModal: (value: boolean) => void;
|
|
addAtDoorForm: AddAtDoorFormState;
|
|
setAddAtDoorForm: Dispatch<SetStateAction<AddAtDoorFormState>>;
|
|
handleAddAtDoor: (e: FormEvent) => void;
|
|
// manual ticket
|
|
showManualTicketModal: boolean;
|
|
setShowManualTicketModal: (value: boolean) => void;
|
|
manualTicketForm: AttendeeFormState;
|
|
setManualTicketForm: Dispatch<SetStateAction<AttendeeFormState>>;
|
|
handleManualTicket: (e: FormEvent) => void;
|
|
// invite guest
|
|
showInviteGuestModal: boolean;
|
|
setShowInviteGuestModal: (value: boolean) => void;
|
|
inviteGuestForm: AttendeeFormState;
|
|
setInviteGuestForm: Dispatch<SetStateAction<AttendeeFormState>>;
|
|
handleInviteGuest: (e: FormEvent) => void;
|
|
// shared submit flag
|
|
submitting: boolean;
|
|
// note modal
|
|
showNoteModal: boolean;
|
|
setShowNoteModal: (value: boolean) => void;
|
|
selectedTicket: Ticket | null;
|
|
setSelectedTicket: (value: Ticket | null) => void;
|
|
noteText: string;
|
|
setNoteText: (value: string) => void;
|
|
handleSaveNote: () => void;
|
|
// preview modal
|
|
previewHtml: string | null;
|
|
setPreviewHtml: (value: string | null) => void;
|
|
}
|
|
|
|
export function EventModals(props: EventModalsProps) {
|
|
const {
|
|
ticketsCount,
|
|
pendingCount,
|
|
confirmedCount,
|
|
checkedInCount,
|
|
cancelledCount,
|
|
onHoldCount,
|
|
statusFilter,
|
|
setStatusFilter,
|
|
mobileFilterOpen,
|
|
setMobileFilterOpen,
|
|
showAddTicketSheet,
|
|
setShowAddTicketSheet,
|
|
showExportSheet,
|
|
setShowExportSheet,
|
|
handleExportAttendees,
|
|
showTicketExportSheet,
|
|
setShowTicketExportSheet,
|
|
handleExportTickets,
|
|
showAddAtDoorModal,
|
|
setShowAddAtDoorModal,
|
|
addAtDoorForm,
|
|
setAddAtDoorForm,
|
|
handleAddAtDoor,
|
|
showManualTicketModal,
|
|
setShowManualTicketModal,
|
|
manualTicketForm,
|
|
setManualTicketForm,
|
|
handleManualTicket,
|
|
showInviteGuestModal,
|
|
setShowInviteGuestModal,
|
|
inviteGuestForm,
|
|
setInviteGuestForm,
|
|
handleInviteGuest,
|
|
submitting,
|
|
showNoteModal,
|
|
setShowNoteModal,
|
|
selectedTicket,
|
|
setSelectedTicket,
|
|
noteText,
|
|
setNoteText,
|
|
handleSaveNote,
|
|
previewHtml,
|
|
setPreviewHtml,
|
|
} = props;
|
|
|
|
return (
|
|
<>
|
|
{/* Mobile filter bottom sheet */}
|
|
<BottomSheet
|
|
open={mobileFilterOpen}
|
|
onClose={() => setMobileFilterOpen(false)}
|
|
title="Filter by Status"
|
|
>
|
|
<div className="space-y-1">
|
|
{[
|
|
{ value: 'all', label: `All (${ticketsCount})` },
|
|
{ value: 'pending', label: `Pending (${pendingCount})` },
|
|
{ value: 'confirmed', label: `Confirmed (${confirmedCount})` },
|
|
{ value: 'checked_in', label: `Checked In (${checkedInCount})` },
|
|
{ value: 'cancelled', label: `Cancelled (${cancelledCount})` },
|
|
{ value: 'on_hold', label: `On Hold (${onHoldCount})` },
|
|
].map((option) => (
|
|
<button
|
|
key={option.value}
|
|
onClick={() => { setStatusFilter(option.value as AttendeeStatusFilter); setMobileFilterOpen(false); }}
|
|
className={clsx(
|
|
'w-full text-left px-4 py-3 rounded-btn text-sm min-h-[44px] flex items-center justify-between',
|
|
statusFilter === option.value ? 'bg-yellow-50 text-primary-dark font-medium' : 'hover:bg-gray-50'
|
|
)}
|
|
>
|
|
{option.label}
|
|
{statusFilter === option.value && <CheckCircleIcon className="w-4 h-4 text-primary-yellow" />}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</BottomSheet>
|
|
|
|
{/* Mobile FAB bottom sheet */}
|
|
<BottomSheet
|
|
open={showAddTicketSheet}
|
|
onClose={() => setShowAddTicketSheet(false)}
|
|
title="Add Ticket"
|
|
>
|
|
<div className="space-y-1">
|
|
<button
|
|
onClick={() => { setShowManualTicketModal(true); setShowAddTicketSheet(false); }}
|
|
className="w-full text-left px-4 py-3 rounded-btn text-sm hover:bg-gray-50 min-h-[44px] flex items-center gap-3"
|
|
>
|
|
<EnvelopeIcon className="w-5 h-5 text-gray-500" />
|
|
<div>
|
|
<p className="font-medium">Manual Ticket</p>
|
|
<p className="text-xs text-gray-500">Send confirmation email with ticket</p>
|
|
</div>
|
|
</button>
|
|
<button
|
|
onClick={() => { setShowAddAtDoorModal(true); setShowAddTicketSheet(false); }}
|
|
className="w-full text-left px-4 py-3 rounded-btn text-sm hover:bg-gray-50 min-h-[44px] flex items-center gap-3"
|
|
>
|
|
<PlusIcon className="w-5 h-5 text-gray-500" />
|
|
<div>
|
|
<p className="font-medium">Add at Door</p>
|
|
<p className="text-xs text-gray-500">Quick add with optional auto check-in</p>
|
|
</div>
|
|
</button>
|
|
<button
|
|
onClick={() => { setShowInviteGuestModal(true); setShowAddTicketSheet(false); }}
|
|
className="w-full text-left px-4 py-3 rounded-btn text-sm hover:bg-gray-50 min-h-[44px] flex items-center gap-3"
|
|
>
|
|
<StarIcon className="w-5 h-5 text-gray-500" />
|
|
<div>
|
|
<p className="font-medium">Invite Guest</p>
|
|
<p className="text-xs text-gray-500">Free ticket, not counted in revenue</p>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</BottomSheet>
|
|
|
|
{/* Mobile export bottom sheet (attendees) */}
|
|
<BottomSheet
|
|
open={showExportSheet}
|
|
onClose={() => setShowExportSheet(false)}
|
|
title="Export Attendees"
|
|
>
|
|
<div className="space-y-1">
|
|
{[
|
|
{ status: 'all' as const, label: 'Export All' },
|
|
{ status: 'confirmed' as const, label: 'Export Confirmed' },
|
|
{ status: 'checked_in' as const, label: 'Export Checked-in' },
|
|
{ status: 'confirmed_pending' as const, label: 'Confirmed & Pending' },
|
|
].map((opt) => (
|
|
<button
|
|
key={opt.status}
|
|
onClick={() => { handleExportAttendees(opt.status); setShowExportSheet(false); }}
|
|
className="w-full text-left px-4 py-3 rounded-btn text-sm hover:bg-gray-50 min-h-[44px]"
|
|
>
|
|
{opt.label}
|
|
</button>
|
|
))}
|
|
<p className="text-[10px] text-gray-400 px-4 pt-2">Format: CSV</p>
|
|
</div>
|
|
</BottomSheet>
|
|
|
|
{/* Mobile export bottom sheet (tickets) */}
|
|
<BottomSheet
|
|
open={showTicketExportSheet}
|
|
onClose={() => setShowTicketExportSheet(false)}
|
|
title="Export Tickets"
|
|
>
|
|
<div className="space-y-1">
|
|
{[
|
|
{ status: 'all' as const, label: 'Export All' },
|
|
{ status: 'confirmed' as const, label: 'Export Valid' },
|
|
{ status: 'checked_in' as const, label: 'Export Checked-in' },
|
|
].map((opt) => (
|
|
<button
|
|
key={opt.status}
|
|
onClick={() => { handleExportTickets(opt.status); setShowTicketExportSheet(false); }}
|
|
className="w-full text-left px-4 py-3 rounded-btn text-sm hover:bg-gray-50 min-h-[44px]"
|
|
>
|
|
{opt.label}
|
|
</button>
|
|
))}
|
|
<p className="text-[10px] text-gray-400 px-4 pt-2">Format: CSV</p>
|
|
</div>
|
|
</BottomSheet>
|
|
|
|
{/* Add at Door Modal */}
|
|
{showAddAtDoorModal && (
|
|
<div
|
|
className="fixed inset-0 bg-black/50 z-50 flex items-end md:items-center justify-center p-0 md:p-4"
|
|
onClick={() => setShowAddAtDoorModal(false)}
|
|
role="presentation"
|
|
>
|
|
<Card
|
|
className="w-full md:max-w-md max-h-[90vh] flex flex-col overflow-hidden rounded-t-2xl md:rounded-card"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="flex items-center justify-between p-4 border-b border-secondary-light-gray flex-shrink-0">
|
|
<h2 className="text-base font-bold">Add Attendee at Door</h2>
|
|
<button
|
|
onClick={() => setShowAddAtDoorModal(false)}
|
|
className="p-2 hover:bg-gray-100 rounded-btn min-h-[44px] min-w-[44px] flex items-center justify-center"
|
|
>
|
|
<XMarkIcon className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
<form onSubmit={handleAddAtDoor} className="p-4 space-y-3 overflow-y-auto flex-1 min-h-0">
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">First Name *</label>
|
|
<input type="text" required value={addAtDoorForm.firstName}
|
|
onChange={(e) => setAddAtDoorForm({ ...addAtDoorForm, firstName: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="First name" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Last Name</label>
|
|
<input type="text" value={addAtDoorForm.lastName}
|
|
onChange={(e) => setAddAtDoorForm({ ...addAtDoorForm, lastName: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="Last name" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Email</label>
|
|
<input type="email" value={addAtDoorForm.email}
|
|
onChange={(e) => setAddAtDoorForm({ ...addAtDoorForm, email: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="email@example.com" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Phone</label>
|
|
<input type="tel" value={addAtDoorForm.phone}
|
|
onChange={(e) => setAddAtDoorForm({ ...addAtDoorForm, phone: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="+595 981 123456" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Admin Note</label>
|
|
<textarea value={addAtDoorForm.adminNote}
|
|
onChange={(e) => setAddAtDoorForm({ ...addAtDoorForm, adminNote: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
rows={2} placeholder="Internal note..." />
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<input type="checkbox" id="autoCheckin" checked={addAtDoorForm.autoCheckin}
|
|
onChange={(e) => setAddAtDoorForm({ ...addAtDoorForm, autoCheckin: e.target.checked })}
|
|
className="w-4 h-4 rounded border-secondary-light-gray text-primary-yellow focus:ring-primary-yellow" />
|
|
<label htmlFor="autoCheckin" className="text-sm font-medium">Auto check-in immediately</label>
|
|
</div>
|
|
<div className="flex gap-3 pt-2">
|
|
<Button type="button" variant="outline" onClick={() => setShowAddAtDoorModal(false)} className="flex-1 min-h-[44px]">Cancel</Button>
|
|
<Button type="submit" isLoading={submitting} className="flex-1 min-h-[44px]">Add Attendee</Button>
|
|
</div>
|
|
</form>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
|
|
{/* Manual Ticket Modal */}
|
|
{showManualTicketModal && (
|
|
<div
|
|
className="fixed inset-0 bg-black/50 z-50 flex items-end md:items-center justify-center p-0 md:p-4"
|
|
onClick={() => setShowManualTicketModal(false)}
|
|
role="presentation"
|
|
>
|
|
<Card
|
|
className="w-full md:max-w-md max-h-[90vh] flex flex-col overflow-hidden rounded-t-2xl md:rounded-card"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="flex items-center justify-between p-4 border-b border-secondary-light-gray flex-shrink-0">
|
|
<div>
|
|
<h2 className="text-base font-bold">Create Manual Ticket</h2>
|
|
<p className="text-xs text-gray-500">Confirmation email will be sent</p>
|
|
</div>
|
|
<button onClick={() => setShowManualTicketModal(false)}
|
|
className="p-2 hover:bg-gray-100 rounded-btn min-h-[44px] min-w-[44px] flex items-center justify-center">
|
|
<XMarkIcon className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
<form onSubmit={handleManualTicket} className="p-4 space-y-3 overflow-y-auto flex-1 min-h-0">
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">First Name *</label>
|
|
<input type="text" required value={manualTicketForm.firstName}
|
|
onChange={(e) => setManualTicketForm({ ...manualTicketForm, firstName: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="First name" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Last Name</label>
|
|
<input type="text" value={manualTicketForm.lastName}
|
|
onChange={(e) => setManualTicketForm({ ...manualTicketForm, lastName: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="Last name" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Email *</label>
|
|
<input type="email" required value={manualTicketForm.email}
|
|
onChange={(e) => setManualTicketForm({ ...manualTicketForm, email: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="email@example.com" />
|
|
<p className="text-[10px] text-gray-500 mt-1">Ticket will be sent to this email</p>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Phone</label>
|
|
<input type="tel" value={manualTicketForm.phone}
|
|
onChange={(e) => setManualTicketForm({ ...manualTicketForm, phone: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="+595 981 123456" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Admin Note</label>
|
|
<textarea value={manualTicketForm.adminNote}
|
|
onChange={(e) => setManualTicketForm({ ...manualTicketForm, adminNote: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
rows={2} placeholder="Internal note..." />
|
|
</div>
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
|
<div className="flex items-start gap-2">
|
|
<EnvelopeIcon className="w-4 h-4 text-blue-500 mt-0.5 flex-shrink-0" />
|
|
<div className="text-xs text-blue-800">
|
|
<p className="font-medium">This will send:</p>
|
|
<ul className="list-disc ml-4 mt-0.5 space-y-0.5">
|
|
<li>Booking confirmation email</li>
|
|
<li>Ticket with QR code</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-3 pt-2">
|
|
<Button type="button" variant="outline" onClick={() => setShowManualTicketModal(false)} className="flex-1 min-h-[44px]">Cancel</Button>
|
|
<Button type="submit" isLoading={submitting} className="flex-1 min-h-[44px]">
|
|
<EnvelopeIcon className="w-4 h-4 mr-1.5" />
|
|
Create & Send
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
|
|
{/* Invite Guest Modal */}
|
|
{showInviteGuestModal && (
|
|
<div
|
|
className="fixed inset-0 bg-black/50 z-50 flex items-end md:items-center justify-center p-0 md:p-4"
|
|
onClick={() => setShowInviteGuestModal(false)}
|
|
role="presentation"
|
|
>
|
|
<Card
|
|
className="w-full md:max-w-md max-h-[90vh] flex flex-col overflow-hidden rounded-t-2xl md:rounded-card"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="flex items-center justify-between p-4 border-b border-secondary-light-gray flex-shrink-0">
|
|
<div>
|
|
<h2 className="text-base font-bold">Invite Guest</h2>
|
|
<p className="text-xs text-gray-500">Free ticket — not counted in revenue</p>
|
|
</div>
|
|
<button onClick={() => setShowInviteGuestModal(false)}
|
|
className="p-2 hover:bg-gray-100 rounded-btn min-h-[44px] min-w-[44px] flex items-center justify-center">
|
|
<XMarkIcon className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
<form onSubmit={handleInviteGuest} className="p-4 space-y-3 overflow-y-auto flex-1 min-h-0">
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">First Name *</label>
|
|
<input type="text" required value={inviteGuestForm.firstName}
|
|
onChange={(e) => setInviteGuestForm({ ...inviteGuestForm, firstName: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="First name" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Last Name</label>
|
|
<input type="text" value={inviteGuestForm.lastName}
|
|
onChange={(e) => setInviteGuestForm({ ...inviteGuestForm, lastName: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="Last name" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Email</label>
|
|
<input type="email" value={inviteGuestForm.email}
|
|
onChange={(e) => setInviteGuestForm({ ...inviteGuestForm, email: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="email@example.com (optional)" />
|
|
<p className="text-[10px] text-gray-500 mt-1">If provided, a confirmation email will be sent</p>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Phone</label>
|
|
<input type="tel" value={inviteGuestForm.phone}
|
|
onChange={(e) => setInviteGuestForm({ ...inviteGuestForm, phone: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
placeholder="+595 981 123456" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Admin Note</label>
|
|
<textarea value={inviteGuestForm.adminNote}
|
|
onChange={(e) => setInviteGuestForm({ ...inviteGuestForm, adminNote: e.target.value })}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
rows={2} placeholder="Internal note..." />
|
|
</div>
|
|
<div className="bg-amber-50 border border-amber-200 rounded-lg p-3">
|
|
<div className="flex items-start gap-2">
|
|
<StarIcon className="w-4 h-4 text-amber-500 mt-0.5 flex-shrink-0" />
|
|
<p className="text-xs text-amber-800">
|
|
Guest tickets are <strong>free</strong> and are automatically confirmed. They are not counted toward revenue or paid ticket totals.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-3 pt-2">
|
|
<Button type="button" variant="outline" onClick={() => setShowInviteGuestModal(false)} className="flex-1 min-h-[44px]">Cancel</Button>
|
|
<Button type="submit" isLoading={submitting} className="flex-1 min-h-[44px]">
|
|
<StarIcon className="w-4 h-4 mr-1.5" />
|
|
Invite Guest
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
|
|
{/* Note Modal */}
|
|
{showNoteModal && selectedTicket && (
|
|
<div className="fixed inset-0 bg-black/50 z-50 flex items-end md:items-center justify-center p-0 md:p-4">
|
|
<Card className="w-full md:max-w-md rounded-t-2xl md:rounded-card">
|
|
<div className="flex items-center justify-between p-4 border-b border-secondary-light-gray">
|
|
<div>
|
|
<h2 className="text-base font-bold">Admin Note</h2>
|
|
<p className="text-xs text-gray-500">{selectedTicket.attendeeFirstName} {selectedTicket.attendeeLastName || ''}</p>
|
|
</div>
|
|
<button onClick={() => { setShowNoteModal(false); setSelectedTicket(null); }}
|
|
className="p-2 hover:bg-gray-100 rounded-btn min-h-[44px] min-w-[44px] flex items-center justify-center">
|
|
<XMarkIcon className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
<div className="p-4 space-y-3">
|
|
<div>
|
|
<label className="block text-xs font-medium mb-1">Note</label>
|
|
<textarea value={noteText} onChange={(e) => setNoteText(e.target.value)}
|
|
className="w-full px-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
|
|
rows={4} placeholder="Add a private note..." maxLength={1000} />
|
|
<p className="text-[10px] text-gray-400 mt-1 text-right">{noteText.length}/1000</p>
|
|
</div>
|
|
<div className="flex gap-3">
|
|
<Button variant="outline" onClick={() => { setShowNoteModal(false); setSelectedTicket(null); }} className="flex-1 min-h-[44px]">Cancel</Button>
|
|
<Button onClick={handleSaveNote} isLoading={submitting} className="flex-1 min-h-[44px]">Save Note</Button>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
|
|
{/* Preview Modal */}
|
|
{previewHtml && (
|
|
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
|
<Card className="w-full max-w-3xl max-h-[90vh] overflow-hidden flex flex-col">
|
|
<div className="flex items-center justify-between p-4 border-b border-secondary-light-gray">
|
|
<h2 className="text-base font-bold">Email Preview</h2>
|
|
<Button variant="outline" size="sm" onClick={() => setPreviewHtml(null)} className="min-h-[44px] md:min-h-0">Close</Button>
|
|
</div>
|
|
<div className="flex-1 overflow-auto">
|
|
<iframe srcDoc={previewHtml} sandbox="" className="w-full h-full min-h-[500px]" title="Email Preview" />
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|