"use client"; import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { useAuth } from "@/hooks/useAuth"; import { useRouter } from "next/navigation"; import { apiFetch, resolveToApiOrigin } from "@/lib/api"; import { useDismissingState } from "@/hooks/useDismissingState"; import { useSiteSettings } from "@/contexts/SiteSettingsContext"; import { Calendar } from "lucide-react"; // ─── helpers ──────────────────────────────────────────────────────────────── function toLocalDT(input: string | Date | null | undefined): string { if (!input) return ""; const d = new Date(input); if (isNaN(d.getTime())) return ""; const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); const hh = String(d.getHours()).padStart(2, "0"); const mm = String(d.getMinutes()).padStart(2, "0"); return `${y}-${m}-${day}T${hh}:${mm}`; } const DT_MIN = "2000-01-01T00:00"; const DT_MAX = "2099-12-31T23:59"; function DTInput({ label, value, onChange, hint, required }: { label: string; value: string; onChange: (v: string) => void; hint?: string; required?: boolean }) { return (
onChange(e.target.value)} /> {hint &&

{hint}

}
); } // ─── types ─────────────────────────────────────────────────────────────────── type FormFieldType = "yes_no" | "text" | "date" | "numeric" | "statement" | "paragraph"; type EventFormDef = { isRequired: boolean; fields: { id?: string; type: FormFieldType; label: string; isRequired?: boolean; order?: number; helpText?: string | null; options?: any }[] }; type Tier = { id?: string; deadline: string; price: string; stockLimit: string; order?: number }; type Variant = { id?: string; name: string; price: string; // empty → use option base price stockLimit: string; earlyBirdTiers: Tier[]; }; type OptionDraft = { id?: string; name: string; price: string; isMainTicket: boolean; stockLimit: string; earlyBirdTiers: Tier[]; // option-level (used only when no variants) variants: Variant[]; }; type SectionDraft = { name: string; optionIndices: number[]; }; // ─── sub-components ───────────────────────────────────────────────────────── function UploadImageButton({ onUploaded, label = "Upload image" }: { onUploaded: (url: string) => void; label?: string }) { const { token } = useAuth(); const [uploading, setUploading] = useState(false); const ref = useRef(null); return (
{ const file = e.target.files?.[0]; if (!file || !token) return; setUploading(true); try { const fd = new FormData(); fd.append("image", file); const res: any = await apiFetch("/api/uploads/event-image", { method: "POST", body: fd, authToken: token }); if (res?.url || res?.path) onUploaded(res.url || res.path); } finally { setUploading(false); if (ref.current) ref.current.value = ""; } }} />
); } function FormBuilder({ value, onChange }: { value: EventFormDef; onChange: (v: EventFormDef) => void }) { const fields = value.fields || []; const add = (type: FormFieldType) => onChange({ ...value, fields: [...fields, { type, label: "", isRequired: false, helpText: "" }] }); const upd = (idx: number, patch: any) => { const c = fields.slice(); c[idx] = { ...c[idx], ...patch }; onChange({ ...value, fields: c }); }; const rem = (idx: number) => { const c = fields.slice(); c.splice(idx, 1); onChange({ ...value, fields: c }); }; const mov = (idx: number, dir: -1 | 1) => { const c = fields.slice(); const t = idx + dir; if (t < 0 || t >= c.length) return; [c[idx], c[t]] = [c[t], c[idx]]; onChange({ ...value, fields: c }); }; const typeLabel = (t: FormFieldType) => ({ text: "Text", numeric: "Number", date: "Date", yes_no: "Yes/No", statement: "Statement", paragraph: "Heading" }[t] || t); return (
Registration Form Fields
{fields.length === 0 ?

No fields yet.

: (