import React, { useCallback, useEffect, useState } from "react"; import { BarChart2, CalendarDays, Check, FileText, Globe, Image, Images, Link2, Mail, MessageSquare, Palette, Plus, Share2, ShieldCheck, Sparkles, Store, Tag, Ticket, Trophy, Upload } from "lucide-react"; import { useBreakpoint } from "../../hooks/useBreakpoint"; import { useAppNav } from "../../contexts/AppNavContext"; import { useAuth } from "../../contexts/AuthContext"; import { usePermission } from "../../hooks/usePermission"; import { eventsApi } from "../../../services/api/events"; import { eventStaffApi } from "../../../services/api/eventStaff"; import { quizPollsApi } from "../../../services/api/quizPolls"; import { badgeBatchesApi } from "../../../services/api/badgeBatches"; import { eventTicketsApi } from "../../../services/api/eventTickets"; import { campaignsApi } from "../../../services/api/campaigns"; import { countConfiguredRewardRules, RewardRulesSettingsPanel } from "../rewards/rewardRulesSettings"; import { ManageStaticMicrositePanel } from "../microsite-v2/ManageStaticMicrositePanel"; import { VisitorThemeSettingsPanel } from "../panels/VisitorThemeSettingsPanel"; import { CreateFormPanel } from "../panels/CreateFormPanel"; import { CreateEmailCampaignPanel } from "../panels/CreateEmailCampaignPanel"; import { FormSubmissionsPanel } from "../panels/FormSubmissionsPanel"; import { ShareFormPanel } from "../forms/ShareFormPanel"; import { CreateBadgePanel } from "../panels/CreateBadgePanel"; import { EventBadgesPanel } from "./EventBadgesPanel"; import { EventDocumentsPanel } from "./EventDocumentsPanel"; import { PhotoWallManagePanel } from "./PhotoWallManagePanel"; import { SocialShareManagePanel } from "./SocialShareManagePanel"; import { AiAvatarManagePanel } from "./AiAvatarManagePanel"; import { EventPhotoSearchPanel } from "../panels/EventPhotoSearchPanel"; import { photoWallApi } from "../../../services/api/photoWall"; import { eventPhotoSearchApi } from "../../../services/api/eventPhotoSearch"; import { socialShareApi } from "../../../services/api/socialShare"; import { aiAvatarApi } from "../../../services/api/aiAvatar"; import { photoWallUploadUrl, photoWallPresentUrl } from "../../utils/photoWallUrls"; import { photoSearchVisitorUrl } from "../../utils/photoSearchUrls"; import { socialShareVisitorUrl } from "../../utils/socialShareUrls"; import { aiAvatarVisitorUrl } from "../../utils/aiAvatarUrls"; import { EventEmailCampaignsPanel } from "./EventEmailCampaignsPanel"; import { WhatsAppBlastHistoryPanel } from "./WhatsAppBlastHistoryPanel"; import { SendWhatsAppNotificationPanel } from "./SendWhatsAppNotificationPanel"; import { whatsappBlastsApi } from "../../../services/api/whatsappBlasts"; import { EventExhibitorsPanel } from "./EventExhibitorsPanel"; import { AddExhibitorPanel } from "../panels/AddExhibitorPanel"; import { AddQuizPollModal, buildQuizPollSettingsPayload } from "../quiz/AddQuizPollModal"; import type { QuizPollModalForm } from "../quiz/AddQuizPollModal"; import type { Question } from "../quiz/AddQuestionModal"; import { modalQuestionToApi } from "../quiz/quizQuestionMappers"; import { sessionsApi, tracksApi, formsApi, staticMicrositeApi, staticMicrositePublicUrl, visitorThemeApi, exhibitorsApi, type ApiExhibitor, } from "../../../services/api"; import type { FormWithSubmissionCount } from "../../../services/api/types"; import { publicFormPageUrl } from "../../utils/publicFormUrl"; function ModuleCard({ icon, title, description, meta, headerAction, actions, }: { icon: React.ReactNode; title: string; description?: string; meta?: string; headerAction?: React.ReactNode; actions: React.ReactNode; }) { const bp = useBreakpoint(); const isMobile = bp === "mobile"; return (
{icon}

{title}

{headerAction}
{description ? (

{description}

) : null} {meta ? (

{meta}

) : null}
{actions}
); } function moduleButtonStyle(primary = false): React.CSSProperties { return { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "6px", flex: "1 1 0", minWidth: 0, padding: "6px 8px", borderRadius: "var(--radius-button)", border: primary ? "1px solid var(--primary)" : "1px solid var(--border)", background: "var(--card)", color: primary ? "var(--primary)" : "var(--card-foreground)", fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", fontWeight: "var(--font-weight-medium)", cursor: "pointer", }; } function formDisplayType(apiType: string): string { if (apiType === "feedback") return "Feedback"; if (apiType === "registration") return "Registration"; if (apiType === "web-check-in") return "Web check-in"; if (apiType === "other") return "Other"; if (apiType === "survey" || apiType === "custom") return "Other"; return apiType.charAt(0).toUpperCase() + apiType.slice(1); } const NEW_FORM_OPTIONS: { value: "registration" | "feedback" | "web-check-in" | "other"; label: string }[] = [ { value: "registration", label: "Registration" }, { value: "feedback", label: "Feedback" }, { value: "web-check-in", label: "Web check-in" }, { value: "other", label: "Other" }, ]; function formPanelMode(apiType: string): "feedback" | "registration" | "other" | "web-check-in" { if (apiType === "feedback") return "feedback"; if (apiType === "registration") return "registration"; if (apiType === "web-check-in") return "web-check-in"; return "other"; } function formMetaLine(form: FormWithSubmissionCount): string { const count = form.submissionCount ?? 0; return `${formDisplayType(form.type)} · ${count.toLocaleString()} response${count === 1 ? "" : "s"}`; } function formLinkCopyButtonStyle(copied: boolean): React.CSSProperties { return { width: "26px", height: "26px", display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "var(--radius-button)", border: "1px solid var(--border)", background: copied ? "#dcfce7" : "var(--card)", color: copied ? "#15803d" : "var(--muted)", cursor: "pointer", flexShrink: 0, padding: 0, }; } function formHeaderIconButtonStyle(): React.CSSProperties { return { ...formLinkCopyButtonStyle(false), color: "var(--muted)", }; } export function EventHomeModules() { const { selectedEventId, setActiveNav, setPermissionsOpenAddOnce, setOpenAgendaSessionPanelOnce } = useAppNav(); const { user } = useAuth(); const { canModule } = usePermission(); const isSuperAdmin = user?.role === "super_admin"; const canReadForms = canModule("forms", "read"); const canWriteForms = canModule("forms", "write"); const canReadSessions = canModule("sessions", "read"); const canWriteSessions = canModule("sessions", "write"); const canReadUserMgmt = canModule("user_management", "read"); const canWriteUserMgmt = canModule("user_management", "write"); const canReadActivities = canModule("live_polls", "read") || canModule("multiple_quiz_modes", "read"); const canWriteActivities = canModule("live_polls", "write") || canModule("multiple_quiz_modes", "write"); const canReadBadges = canModule("custom_badge_and_print_file_creation", "read"); const canWriteBadges = canModule("custom_badge_and_print_file_creation", "write"); const canReadTicketing = canModule("ticketing_free_and_paid", "read"); const canReadMicrosite = canModule("event_website", "read"); const canReadRewards = canModule("gamification", "read"); const canReadWebCheckIn = canModule("web_check_in", "read"); const canWriteWebCheckIn = canModule("web_check_in", "write"); const canReadDocuments = canModule("content_management", "read"); const canReadPhotoWall = canModule("photo_wall", "read"); const canWritePhotoWall = canModule("photo_wall", "write"); const canReadSocialShare = canModule("social_share", "read"); const canWriteSocialShare = canModule("social_share", "write"); const canReadAiAvatar = canModule("ai_avatar", "read"); const canWriteAiAvatar = canModule("ai_avatar", "write"); const canReadEmailCampaigns = canModule("email_outreach_campaigns", "read"); const canWriteEmailCampaigns = canModule("email_outreach_campaigns", "write"); const canReadWaNotifications = canModule("custom_template_for_wa_messaging", "read"); const canWriteWaNotifications = canModule("custom_template_for_wa_messaging", "write"); const canReadExhibitors = canModule("exhibitors", "read"); const canWriteExhibitors = canModule("exhibitors", "write"); const bp = useBreakpoint(); const isMobile = bp === "mobile"; const isTablet = bp === "tablet"; const gridTemplateColumns = isMobile ? "1fr" : isTablet ? "repeat(2, minmax(0, 1fr))" : "repeat(auto-fill, minmax(240px, 1fr))"; const [eventForms, setEventForms] = useState([]); const [formEditTarget, setFormEditTarget] = useState(null); const [formResponsesTarget, setFormResponsesTarget] = useState(null); const [formShareTarget, setFormShareTarget] = useState(null); const [newFormMode, setNewFormMode] = useState<"registration" | "feedback" | "other" | "web-check-in" | null>(null); const [newFormType, setNewFormType] = useState<"registration" | "feedback" | "web-check-in" | "other">("registration"); const [copiedFormLinkId, setCopiedFormLinkId] = useState(null); const [pollQuizCounts, setPollQuizCounts] = useState({ polls: 0, quizzes: 0 }); const [pollQuizLoading, setPollQuizLoading] = useState(false); const [showAddPoll, setShowAddPoll] = useState(false); const [showAddQuiz, setShowAddQuiz] = useState(false); const [staffCounts, setStaffCounts] = useState({ members: 0, invites: 0 }); const [staffLoading, setStaffLoading] = useState(false); const [badgeCount, setBadgeCount] = useState(0); const [badgeLoading, setBadgeLoading] = useState(false); const [eventName, setEventName] = useState(null); const [badgesOpen, setBadgesOpen] = useState(false); const [showAddBadge, setShowAddBadge] = useState(false); const [duplicateBadgeBatchId, setDuplicateBadgeBatchId] = useState(null); const [ticketCount, setTicketCount] = useState(0); const [ticketLoading, setTicketLoading] = useState(false); const [rewardRulesCount, setRewardRulesCount] = useState(0); const [rewardLoading, setRewardLoading] = useState(false); const [rewardRulesOpen, setRewardRulesOpen] = useState(false); const [staticMicrositeOpen, setStaticMicrositeOpen] = useState(false); const [staticMicrositeMeta, setStaticMicrositeMeta] = useState(null); const [staticMicrositeLoading, setStaticMicrositeLoading] = useState(false); const [visitorThemeOpen, setVisitorThemeOpen] = useState(false); const [visitorThemeMeta, setVisitorThemeMeta] = useState(null); const [visitorThemeLoading, setVisitorThemeLoading] = useState(false); const [agendaCounts, setAgendaCounts] = useState({ tracks: 0, sessions: 0 }); const [agendaLoading, setAgendaLoading] = useState(false); const [eventSettings, setEventSettings] = useState>({}); const [webCheckInEnabled, setWebCheckInEnabled] = useState(false); const [webCheckInLoading, setWebCheckInLoading] = useState(false); const [showDocumentsPanel, setShowDocumentsPanel] = useState(false); const [showEventPhotoSearchPanel, setShowEventPhotoSearchPanel] = useState(false); const [photoSearchEnabled, setPhotoSearchEnabled] = useState(false); const [photoSearchLoading, setPhotoSearchLoading] = useState(false); const [photoSearchCount, setPhotoSearchCount] = useState(0); const [photoSearchToken, setPhotoSearchToken] = useState(null); const [copiedPhotoSearchLink, setCopiedPhotoSearchLink] = useState(false); const [showPhotoWallPanel, setShowPhotoWallPanel] = useState(false); const [photoWallEnabled, setPhotoWallEnabled] = useState(false); const [photoWallLoading, setPhotoWallLoading] = useState(false); const [photoWallCount, setPhotoWallCount] = useState(0); const [photoWallUploadToken, setPhotoWallUploadToken] = useState(null); const [photoWallPresentToken, setPhotoWallPresentToken] = useState(null); const [copiedPhotoWallLink, setCopiedPhotoWallLink] = useState<"upload" | "present" | null>(null); const [showSocialSharePanel, setShowSocialSharePanel] = useState(false); const [socialShareEnabled, setSocialShareEnabled] = useState(false); const [socialShareLoading, setSocialShareLoading] = useState(false); const [socialShareFrameCount, setSocialShareFrameCount] = useState(0); const [socialShareToken, setSocialShareToken] = useState(null); const [copiedSocialShareLink, setCopiedSocialShareLink] = useState(false); const [showAiAvatarPanel, setShowAiAvatarPanel] = useState(false); const [aiAvatarEnabled, setAiAvatarEnabled] = useState(false); const [aiAvatarLoading, setAiAvatarLoading] = useState(false); const [aiAvatarCount, setAiAvatarCount] = useState(0); const [aiAvatarToken, setAiAvatarToken] = useState(null); const [copiedAiAvatarLink, setCopiedAiAvatarLink] = useState(false); const [emailCampaignCount, setEmailCampaignCount] = useState(0); const [emailCampaignLoading, setEmailCampaignLoading] = useState(false); const [showCreateEmailCampaign, setShowCreateEmailCampaign] = useState(false); const [showEmailCampaignsPanel, setShowEmailCampaignsPanel] = useState(false); const [waBlastCount, setWaBlastCount] = useState(0); const [waBlastLoading, setWaBlastLoading] = useState(false); const [showWaBlastHistory, setShowWaBlastHistory] = useState(false); const [showSendWaNotification, setShowSendWaNotification] = useState(false); const [exhibitorCount, setExhibitorCount] = useState(0); const [exhibitorLoading, setExhibitorLoading] = useState(false); const [showExhibitorsPanel, setShowExhibitorsPanel] = useState(false); const [showAddExhibitor, setShowAddExhibitor] = useState(false); const [editingExhibitor, setEditingExhibitor] = useState(null); const actionsDisabled = !selectedEventId; const photoSearchHeaderButtonStyle: React.CSSProperties = { width: "26px", height: "26px", display: "inline-flex", alignItems: "center", justifyContent: "center", borderRadius: "var(--radius-button)", border: "1px solid var(--border)", background: "var(--card)", color: "var(--muted)", cursor: actionsDisabled ? "not-allowed" : "pointer", opacity: actionsDisabled ? 0.55 : 1, flexShrink: 0, padding: 0, }; useEffect(() => { if (!selectedEventId) { setEventName(null); return; } let cancelled = false; void eventsApi.get(selectedEventId).then((res) => { if (cancelled) return; setEventName(res.data?.name ?? null); setEventSettings((res.data?.settings as Record | undefined) ?? {}); setWebCheckInEnabled(Boolean((res.data?.settings as Record | undefined)?.webCheckInEnabled)); }); return () => { cancelled = true; }; }, [selectedEventId]); const loadEventForms = useCallback(async () => { if (!selectedEventId) { setEventForms([]); return; } try { const forms = await formsApi.listForEvent(selectedEventId); const sorted = [...forms].sort((a, b) => (a.name || "").localeCompare(b.name || "", undefined, { sensitivity: "base" }), ); setEventForms(sorted); } catch { setEventForms([]); } }, [selectedEventId]); useEffect(() => { void loadEventForms(); }, [loadEventForms]); const loadPollQuizCounts = useCallback(async () => { if (!selectedEventId) { setPollQuizCounts({ polls: 0, quizzes: 0 }); setPollQuizLoading(false); return; } setPollQuizLoading(true); try { const res = await quizPollsApi.listByEvent(selectedEventId); const items = res.data?.quizPolls ?? []; setPollQuizCounts({ polls: items.filter((q) => q.type === "poll").length, quizzes: items.filter((q) => q.type === "quiz").length, }); } catch { setPollQuizCounts({ polls: 0, quizzes: 0 }); } finally { setPollQuizLoading(false); } }, [selectedEventId]); useEffect(() => { void loadPollQuizCounts(); }, [loadPollQuizCounts]); const loadStaffCounts = useCallback(async () => { if (!selectedEventId) { setStaffCounts({ members: 0, invites: 0 }); setStaffLoading(false); return; } setStaffLoading(true); try { const res = await eventStaffApi.listStaff(selectedEventId); if (res.data) { setStaffCounts({ members: res.data.members?.length ?? 0, invites: res.data.invites?.length ?? 0, }); } else { setStaffCounts({ members: 0, invites: 0 }); } } catch { setStaffCounts({ members: 0, invites: 0 }); } finally { setStaffLoading(false); } }, [selectedEventId]); useEffect(() => { void loadStaffCounts(); }, [loadStaffCounts]); const loadBadgeCount = useCallback(async () => { if (!selectedEventId) { setBadgeCount(0); setBadgeLoading(false); return; } setBadgeLoading(true); try { const res = await badgeBatchesApi.list({ event: selectedEventId, limit: 100 }); setBadgeCount(res.data?.batches?.length ?? 0); } catch { setBadgeCount(0); } finally { setBadgeLoading(false); } }, [selectedEventId]); useEffect(() => { void loadBadgeCount(); }, [loadBadgeCount]); const loadTicketCount = useCallback(async () => { if (!selectedEventId) { setTicketCount(0); setTicketLoading(false); return; } setTicketLoading(true); try { const tickets = await eventTicketsApi.list(selectedEventId); setTicketCount(tickets.length); } catch { setTicketCount(0); } finally { setTicketLoading(false); } }, [selectedEventId]); useEffect(() => { void loadTicketCount(); }, [loadTicketCount]); const loadRewardRulesMeta = useCallback(async () => { if (!selectedEventId) { setRewardRulesCount(0); setRewardLoading(false); return; } setRewardLoading(true); try { const res = await eventsApi.get(selectedEventId); setRewardRulesCount(countConfiguredRewardRules(res.data?.rewardRules)); } catch { setRewardRulesCount(0); } finally { setRewardLoading(false); } }, [selectedEventId]); useEffect(() => { void loadRewardRulesMeta(); }, [loadRewardRulesMeta]); const loadStaticMicrositeMeta = useCallback(async () => { if (!selectedEventId) { setStaticMicrositeMeta(null); setStaticMicrositeLoading(false); return; } setStaticMicrositeLoading(true); try { const res = await staticMicrositeApi.getForEvent(selectedEventId); if (res.data?.subdomain) { const status = res.data.status === "published" ? "Published" : "Draft"; setStaticMicrositeMeta(`${staticMicrositePublicUrl(res.data.subdomain)} · ${status}`); } else { setStaticMicrositeMeta("Not configured"); } } catch { setStaticMicrositeMeta("Not configured"); } finally { setStaticMicrositeLoading(false); } }, [selectedEventId]); useEffect(() => { void loadStaticMicrositeMeta(); }, [loadStaticMicrositeMeta]); const loadVisitorThemeMeta = useCallback(async () => { if (!selectedEventId) { setVisitorThemeMeta(null); setVisitorThemeLoading(false); return; } setVisitorThemeLoading(true); try { const res = await visitorThemeApi.get(selectedEventId); const mode = res.data?.theme; if (mode === "gradient") setVisitorThemeMeta("Gradient"); else if (mode === "background") setVisitorThemeMeta("Background"); else setVisitorThemeMeta("Simple"); } catch { setVisitorThemeMeta("Simple"); } finally { setVisitorThemeLoading(false); } }, [selectedEventId]); useEffect(() => { void loadVisitorThemeMeta(); }, [loadVisitorThemeMeta]); const loadAgendaMeta = useCallback(async () => { if (!selectedEventId) { setAgendaCounts({ tracks: 0, sessions: 0 }); setAgendaLoading(false); return; } setAgendaLoading(true); try { const [trRes, sesRes] = await Promise.all([ tracksApi.list(selectedEventId), sessionsApi.list(selectedEventId), ]); const tracks = (trRes.data as { tracks?: unknown[] } | undefined)?.tracks ?? []; const sessions = (sesRes.data as { sessions?: unknown[] } | undefined)?.sessions ?? []; setAgendaCounts({ tracks: tracks.length, sessions: sessions.length }); } catch { setAgendaCounts({ tracks: 0, sessions: 0 }); } finally { setAgendaLoading(false); } }, [selectedEventId]); useEffect(() => { void loadAgendaMeta(); }, [loadAgendaMeta]); const loadEmailCampaignMeta = useCallback(async () => { if (!selectedEventId) { setEmailCampaignCount(0); setEmailCampaignLoading(false); return; } setEmailCampaignLoading(true); try { const res = await campaignsApi.listForEvent(selectedEventId); const campaigns = res.data?.campaigns ?? []; setEmailCampaignCount(campaigns.filter((c) => c.channel === "email").length); } catch { setEmailCampaignCount(0); } finally { setEmailCampaignLoading(false); } }, [selectedEventId]); useEffect(() => { void loadEmailCampaignMeta(); }, [loadEmailCampaignMeta]); const loadWaBlastMeta = useCallback(async () => { if (!selectedEventId || !canReadWaNotifications) { setWaBlastCount(0); setWaBlastLoading(false); return; } setWaBlastLoading(true); try { const res = await whatsappBlastsApi.list(selectedEventId); setWaBlastCount(res.data?.blasts?.length ?? 0); } catch { setWaBlastCount(0); } finally { setWaBlastLoading(false); } }, [selectedEventId, canReadWaNotifications]); useEffect(() => { void loadWaBlastMeta(); }, [loadWaBlastMeta]); const loadExhibitorMeta = useCallback(async () => { if (!selectedEventId) { setExhibitorCount(0); setExhibitorLoading(false); return; } setExhibitorLoading(true); try { const res = await exhibitorsApi.list(selectedEventId); setExhibitorCount(res.data?.total ?? res.data?.exhibitors?.length ?? 0); } catch { setExhibitorCount(0); } finally { setExhibitorLoading(false); } }, [selectedEventId]); useEffect(() => { void loadExhibitorMeta(); }, [loadExhibitorMeta]); const loadPhotoWallMeta = useCallback(async () => { if (!selectedEventId || !canReadPhotoWall) { setPhotoWallEnabled(false); setPhotoWallCount(0); setPhotoWallUploadToken(null); setPhotoWallPresentToken(null); setPhotoWallLoading(false); return; } setPhotoWallLoading(true); try { const res = await photoWallApi.get(selectedEventId); const data = res.data; setPhotoWallEnabled(Boolean(data?.enabled)); setPhotoWallCount(data?.photoCounts?.total ?? 0); setPhotoWallUploadToken(data?.uploadToken ?? null); setPhotoWallPresentToken(data?.presentToken ?? null); } catch { setPhotoWallEnabled(false); setPhotoWallCount(0); setPhotoWallUploadToken(null); setPhotoWallPresentToken(null); } finally { setPhotoWallLoading(false); } }, [selectedEventId, canReadPhotoWall]); useEffect(() => { void loadPhotoWallMeta(); }, [loadPhotoWallMeta]); const loadSocialShareMeta = useCallback(async () => { if (!selectedEventId || !canReadSocialShare) { setSocialShareEnabled(false); setSocialShareFrameCount(0); setSocialShareToken(null); setSocialShareLoading(false); return; } setSocialShareLoading(true); try { const res = await socialShareApi.get(selectedEventId); const data = res.data; setSocialShareEnabled(Boolean(data?.enabled)); setSocialShareFrameCount(data?.frameCount ?? data?.frames?.length ?? 0); setSocialShareToken(data?.uploadToken ?? null); } catch { setSocialShareEnabled(false); setSocialShareFrameCount(0); setSocialShareToken(null); } finally { setSocialShareLoading(false); } }, [selectedEventId, canReadSocialShare]); useEffect(() => { void loadSocialShareMeta(); }, [loadSocialShareMeta]); const loadAiAvatarMeta = useCallback(async () => { if (!selectedEventId || !canReadAiAvatar) { setAiAvatarEnabled(false); setAiAvatarCount(0); setAiAvatarToken(null); setAiAvatarLoading(false); return; } setAiAvatarLoading(true); try { const res = await aiAvatarApi.get(selectedEventId); const data = res.data; setAiAvatarEnabled(Boolean(data?.enabled)); setAiAvatarCount(data?.avatarCount ?? data?.avatars?.length ?? 0); setAiAvatarToken(data?.uploadToken ?? null); } catch { setAiAvatarEnabled(false); setAiAvatarCount(0); setAiAvatarToken(null); } finally { setAiAvatarLoading(false); } }, [selectedEventId, canReadAiAvatar]); useEffect(() => { void loadAiAvatarMeta(); }, [loadAiAvatarMeta]); const loadPhotoSearchMeta = useCallback(async () => { if (!selectedEventId) { setPhotoSearchEnabled(false); setPhotoSearchCount(0); setPhotoSearchToken(null); setPhotoSearchLoading(false); return; } setPhotoSearchLoading(true); try { const res = await eventPhotoSearchApi.get(selectedEventId); const data = res.data; setPhotoSearchEnabled(Boolean(data?.enabled)); setPhotoSearchCount(data?.photoCount ?? 0); setPhotoSearchToken(data?.searchToken ?? null); } catch { setPhotoSearchEnabled(false); setPhotoSearchCount(0); setPhotoSearchToken(null); } finally { setPhotoSearchLoading(false); } }, [selectedEventId]); useEffect(() => { void loadPhotoSearchMeta(); }, [loadPhotoSearchMeta]); const handleBadgeSuccess = () => { setShowAddBadge(false); setDuplicateBadgeBatchId(null); void loadBadgeCount(); }; const openAddBadge = () => { setDuplicateBadgeBatchId(null); setShowAddBadge(true); }; const openDuplicateBadge = (batchId: string) => { setBadgesOpen(false); setDuplicateBadgeBatchId(batchId); setShowAddBadge(true); }; const handleFormEditSuccess = () => { setFormEditTarget(null); void loadEventForms(); }; const handleNewFormSuccess = () => { setNewFormMode(null); void loadEventForms(); }; const copyFormPublicLink = useCallback( (form: FormWithSubmissionCount) => { const url = publicFormPageUrl(form, selectedEventId); void navigator.clipboard.writeText(url).then(() => { setCopiedFormLinkId(form._id); window.setTimeout(() => { setCopiedFormLinkId((prev) => (prev === form._id ? null : prev)); }, 4000); }); }, [selectedEventId], ); const pollQuizMeta = !selectedEventId ? "Select an event to create polls and quizzes" : pollQuizLoading ? "Loading…" : `${pollQuizCounts.polls} poll${pollQuizCounts.polls === 1 ? "" : "s"} · ${pollQuizCounts.quizzes} quiz${pollQuizCounts.quizzes === 1 ? "" : "zes"}`; const permissionsMeta = !selectedEventId ? "Select an event to manage team access" : staffLoading ? "Loading…" : `${staffCounts.members} member${staffCounts.members === 1 ? "" : "s"} · ${staffCounts.invites} pending invite${staffCounts.invites === 1 ? "" : "s"}`; const badgeMeta = !selectedEventId ? "Select an event to manage badges" : badgeLoading ? "Loading…" : `${badgeCount} badge${badgeCount === 1 ? "" : "s"}`; const ticketMeta = !selectedEventId ? "Select an event to configure tickets" : ticketLoading ? "Loading…" : `${ticketCount} ticket type${ticketCount === 1 ? "" : "s"}`; const rewardMeta = !selectedEventId ? "Select an event to configure reward rules" : rewardLoading ? "Loading…" : rewardRulesCount === 0 ? "No rules configured" : `${rewardRulesCount} rule${rewardRulesCount === 1 ? "" : "s"} configured`; const visitorThemeCardMeta = !selectedEventId ? "Select an event to manage visitor theme" : visitorThemeLoading ? "Loading…" : visitorThemeMeta || "Simple"; const micrositeMeta = !selectedEventId ? "Select an event to manage microsite" : staticMicrositeLoading ? "Loading…" : staticMicrositeMeta || "Not configured"; const agendaMeta = !selectedEventId ? "Select an event to manage the agenda" : agendaLoading ? "Loading…" : `${agendaCounts.tracks} track${agendaCounts.tracks === 1 ? "" : "s"} · ${agendaCounts.sessions} session${agendaCounts.sessions === 1 ? "" : "s"}`; const emailCampaignMeta = !selectedEventId ? "Select an event to manage email campaigns" : emailCampaignLoading ? "Loading…" : `${emailCampaignCount} email campaign${emailCampaignCount === 1 ? "" : "s"}`; const waBlastMeta = !selectedEventId ? "Select an event to send WhatsApp notifications" : waBlastLoading ? "Loading…" : `${waBlastCount} notification${waBlastCount === 1 ? "" : "s"}`; const exhibitorMeta = !selectedEventId ? "Select an event to manage exhibitors" : exhibitorLoading ? "Loading…" : `${exhibitorCount} exhibitor${exhibitorCount === 1 ? "" : "s"}`; const webCheckInMeta = !selectedEventId ? "Select an event to configure web check-in" : webCheckInLoading ? "Saving…" : webCheckInEnabled ? "Enabled" : "Disabled"; const photoWallMeta = !selectedEventId ? "Select an event to configure photo wall" : photoWallLoading ? "Loading…" : photoWallEnabled ? `Enabled · ${photoWallCount} photo${photoWallCount === 1 ? "" : "s"}` : "Disabled"; const socialShareMeta = !selectedEventId ? "Select an event to configure social share" : socialShareLoading ? "Loading…" : socialShareEnabled ? `Enabled · ${socialShareFrameCount} frame${socialShareFrameCount === 1 ? "" : "s"}` : "Disabled"; const aiAvatarMeta = !selectedEventId ? "Select an event to configure AI avatar" : aiAvatarLoading ? "Loading…" : aiAvatarEnabled ? `Enabled · ${aiAvatarCount} avatar${aiAvatarCount === 1 ? "" : "s"}` : "Disabled"; const photoSearchMeta = !selectedEventId ? "Select an event to configure photo search" : photoSearchLoading ? "Loading…" : photoSearchEnabled ? `Enabled · ${photoSearchCount} photo${photoSearchCount === 1 ? "" : "s"}` : "Disabled"; const handleWebCheckInToggle = async (enabled: boolean) => { if (!selectedEventId) return; setWebCheckInLoading(true); try { const nextSettings = { ...(eventSettings || {}), webCheckInEnabled: enabled }; const res = await eventsApi.update(selectedEventId, { settings: nextSettings }); if (!res.error) { setEventSettings(nextSettings); setWebCheckInEnabled(enabled); } } finally { setWebCheckInLoading(false); } }; const handlePhotoWallToggle = async (enabled: boolean) => { if (!selectedEventId || !canWritePhotoWall) return; setPhotoWallLoading(true); try { const res = await photoWallApi.update(selectedEventId, { enabled }); if (!res.error && res.data) { setPhotoWallEnabled(Boolean(res.data.enabled)); setPhotoWallCount(res.data.photoCounts?.total ?? 0); setPhotoWallUploadToken(res.data.uploadToken ?? null); setPhotoWallPresentToken(res.data.presentToken ?? null); } } finally { setPhotoWallLoading(false); } }; const handleSocialShareToggle = async (enabled: boolean) => { if (!selectedEventId || !canWriteSocialShare) return; setSocialShareLoading(true); try { const res = await socialShareApi.update(selectedEventId, { enabled }); if (!res.error && res.data) { setSocialShareEnabled(Boolean(res.data.enabled)); setSocialShareFrameCount(res.data.frameCount ?? res.data.frames?.length ?? 0); setSocialShareToken(res.data.uploadToken ?? null); } } finally { setSocialShareLoading(false); } }; const copySocialShareLink = async () => { if (!socialShareToken) return; try { await navigator.clipboard.writeText(socialShareVisitorUrl(socialShareToken)); setCopiedSocialShareLink(true); window.setTimeout(() => setCopiedSocialShareLink(false), 1500); } catch { /* ignore */ } }; const handleAiAvatarToggle = async (enabled: boolean) => { if (!selectedEventId || !canWriteAiAvatar) return; setAiAvatarLoading(true); try { const res = await aiAvatarApi.update(selectedEventId, { enabled }); if (!res.error && res.data) { setAiAvatarEnabled(Boolean(res.data.enabled)); setAiAvatarCount(res.data.avatarCount ?? res.data.avatars?.length ?? 0); setAiAvatarToken(res.data.uploadToken ?? null); } } finally { setAiAvatarLoading(false); } }; const copyAiAvatarLink = async () => { if (!aiAvatarToken) return; try { await navigator.clipboard.writeText(aiAvatarVisitorUrl(aiAvatarToken)); setCopiedAiAvatarLink(true); window.setTimeout(() => setCopiedAiAvatarLink(false), 1500); } catch { /* ignore */ } }; const handlePhotoSearchToggle = async (enabled: boolean) => { if (!selectedEventId) return; setPhotoSearchLoading(true); try { const res = await eventPhotoSearchApi.update(selectedEventId, { enabled }); if (!res.error && res.data) { setPhotoSearchEnabled(Boolean(res.data.enabled)); setPhotoSearchCount(res.data.photoCount ?? 0); setPhotoSearchToken(res.data.searchToken ?? null); } } finally { setPhotoSearchLoading(false); } }; const copyPhotoSearchLink = async () => { if (!photoSearchToken) return; const url = photoSearchVisitorUrl(photoSearchToken); try { await navigator.clipboard.writeText(url); setCopiedPhotoSearchLink(true); window.setTimeout(() => setCopiedPhotoSearchLink(false), 1500); } catch { /* ignore */ } }; const copyPhotoWallLink = async (kind: "upload" | "present") => { const url = kind === "upload" && photoWallUploadToken ? photoWallUploadUrl(photoWallUploadToken) : kind === "present" && photoWallPresentToken ? photoWallPresentUrl(photoWallPresentToken) : ""; if (!url) return; try { await navigator.clipboard.writeText(url); setCopiedPhotoWallLink(kind); window.setTimeout(() => setCopiedPhotoWallLink(null), 1500); } catch { /* ignore */ } }; const handleSavePollQuiz = async ( kind: "poll" | "quiz", data: { form: QuizPollModalForm; questions: Question[] }, ) => { if (!selectedEventId) return; const sesRes = await sessionsApi.list(selectedEventId); const firstSession = sesRes.data?.sessions?.[0]; const payload = { ...(firstSession?._id ? { session: firstSession._id } : {}), title: data.form.title, type: kind, description: data.form.description || undefined, questions: data.questions.map((q) => modalQuestionToApi(q, kind)), settings: buildQuizPollSettingsPayload(data.form, kind), }; const res = await quizPollsApi.create(selectedEventId, payload); if (!res.error) { if (kind === "poll") setShowAddPoll(false); else setShowAddQuiz(false); void loadPollQuizCounts(); } }; return ( <>
{canReadSessions ? ( } title="Agenda" meta={agendaMeta} actions={ <> } /> ) : null} {canReadForms ? eventForms.map((form) => { const linkCopied = copiedFormLinkId === form._id; return ( } title={form.name?.trim() || "Untitled form"} meta={formMetaLine(form)} headerAction={
{isSuperAdmin ? ( ) : null}
} actions={ <> {canWriteForms ? ( ) : null} {canReadForms ? ( ) : null} } /> ); }) : null} {canWriteForms ? ( } title="Create New Form" description="Add a form assigned to this event" actions={ <> } /> ) : null} {canReadUserMgmt ? ( } title="Users & Permissions" meta={permissionsMeta} actions={ <> {canWriteUserMgmt ? ( ) : null} } /> ) : null} {canReadActivities ? ( } title="Activities" meta={pollQuizMeta} actions={ <> } /> ) : null} {canReadBadges ? ( } title="Tag & Badge Configuration" meta={badgeMeta} actions={ <> {canWriteBadges ? ( ) : null} } /> ) : null} {canReadTicketing ? ( } title="Ticket configuration" meta={ticketMeta} actions={ } /> ) : null} } title="Visitor Theme" description="Colors and background for visitor event, session, quiz, and photo pages" meta={visitorThemeCardMeta} actions={ } /> {canReadMicrosite ? ( } title="Microsite" meta={micrositeMeta} actions={ } /> ) : null} {canReadRewards ? ( } title="Reward Points Config" meta={rewardMeta} actions={ } /> ) : null} {canReadWebCheckIn ? ( } title="Web check-in" meta={webCheckInMeta} description="Show or hide accommodation choice for participants" actions={ webCheckInEnabled ? ( ) : ( ) } /> ) : null} {canReadDocuments ? ( } title="Upload Document" description="Manage documents uploaded for this event" actions={ } /> ) : null} } title="Event Photo Search" meta={photoSearchMeta} description="Upload event photos and share visitor Find Photos" headerAction={ photoSearchEnabled && photoSearchToken ? ( ) : null } actions={ <> {photoSearchEnabled ? ( ) : ( )} } /> {canReadSocialShare ? ( } title="Social Share" meta={socialShareMeta} description="Multi-frame photo create, download, and share" headerAction={ socialShareEnabled && socialShareToken ? ( ) : null } actions={ <> {socialShareEnabled ? ( ) : ( )} } /> ) : null} {canReadAiAvatar ? ( } title="AI Avatar" meta={aiAvatarMeta} description="AI head-swap avatars from visitor selfies" headerAction={ aiAvatarEnabled && aiAvatarToken ? ( ) : null } actions={ <> {aiAvatarEnabled ? ( ) : ( )} } /> ) : null} {canReadPhotoWall ? ( } title="Photo Wall" meta={photoWallMeta} description="Visitor selfie uploads and projector wall" headerAction={ photoWallEnabled && photoWallUploadToken && photoWallPresentToken ? (
) : null } actions={ <> {photoWallEnabled ? ( ) : ( )} } /> ) : null} {canReadWaNotifications ? ( } title="WhatsApp Notifications" meta={waBlastMeta} description="Blast a message to this event’s open WhatsApp chats (24-hour window)" actions={ <> } /> ) : null} {canReadEmailCampaigns ? ( } title="Email Campaign" meta={emailCampaignMeta} actions={ <> } /> ) : null} {canReadExhibitors ? ( } title="Exhibitors" meta={exhibitorMeta} description="Companies and booths for this event" actions={ <> } /> ) : null}
{formShareTarget ? ( setFormShareTarget(null)} /> ) : null} {formEditTarget && selectedEventId ? ( setFormEditTarget(null)} onSuccess={handleFormEditSuccess} /> ) : null} {formResponsesTarget ? ( setFormResponsesTarget(null)} /> ) : null} {newFormMode && selectedEventId ? ( setNewFormMode(null)} onSuccess={handleNewFormSuccess} /> ) : null} {showAddPoll ? ( setShowAddPoll(false)} onSave={(data) => void handleSavePollQuiz("poll", data)} /> ) : null} {showAddQuiz ? ( setShowAddQuiz(false)} onSave={(data) => void handleSavePollQuiz("quiz", data)} /> ) : null} {badgesOpen && selectedEventId ? ( setBadgesOpen(false)} onAddBadge={() => { setBadgesOpen(false); openAddBadge(); }} onDuplicateBadge={openDuplicateBadge} /> ) : null} {showAddBadge && selectedEventId ? ( { setShowAddBadge(false); setDuplicateBadgeBatchId(null); }} onSuccess={handleBadgeSuccess} /> ) : null} {rewardRulesOpen && selectedEventId ? ( setRewardRulesOpen(false)} onSaved={() => void loadRewardRulesMeta()} /> ) : null} {visitorThemeOpen && selectedEventId ? ( setVisitorThemeOpen(false)} onSaved={() => void loadVisitorThemeMeta()} /> ) : null} {staticMicrositeOpen && selectedEventId ? ( setStaticMicrositeOpen(false)} onSaved={() => void loadStaticMicrositeMeta()} /> ) : null} {showDocumentsPanel && selectedEventId ? ( setShowDocumentsPanel(false)} /> ) : null} {showEventPhotoSearchPanel && selectedEventId ? ( setShowEventPhotoSearchPanel(false)} onChanged={() => void loadPhotoSearchMeta()} /> ) : null} {showPhotoWallPanel && selectedEventId ? ( setShowPhotoWallPanel(false)} onChanged={() => void loadPhotoWallMeta()} /> ) : null} {showSocialSharePanel && selectedEventId ? ( setShowSocialSharePanel(false)} onChanged={() => void loadSocialShareMeta()} /> ) : null} {showAiAvatarPanel && selectedEventId ? ( setShowAiAvatarPanel(false)} onChanged={() => void loadAiAvatarMeta()} /> ) : null} {showWaBlastHistory && selectedEventId ? ( setShowWaBlastHistory(false)} /> ) : null} {showSendWaNotification && selectedEventId ? ( setShowSendWaNotification(false)} onSuccess={() => void loadWaBlastMeta()} /> ) : null} {showEmailCampaignsPanel && selectedEventId ? ( setShowEmailCampaignsPanel(false)} onChanged={() => void loadEmailCampaignMeta()} /> ) : null} {showCreateEmailCampaign && selectedEventId ? ( setShowCreateEmailCampaign(false)} onSuccess={() => void loadEmailCampaignMeta()} /> ) : null} {showExhibitorsPanel && selectedEventId ? ( setShowExhibitorsPanel(false)} onAdd={() => { setShowExhibitorsPanel(false); setEditingExhibitor(null); setShowAddExhibitor(true); }} onEdit={(ex) => { setShowExhibitorsPanel(false); setEditingExhibitor(ex); setShowAddExhibitor(true); }} onChanged={() => void loadExhibitorMeta()} /> ) : null} {showAddExhibitor && selectedEventId ? ( { setShowAddExhibitor(false); setEditingExhibitor(null); }} onSuccess={() => { void loadExhibitorMeta(); setShowExhibitorsPanel(true); }} /> ) : null} ); }