import React, { useCallback, useEffect, useState } from "react";
import { BarChart2, CalendarDays, Check, FileText, Globe, Image, Images, Link2, Mail, 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 { 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}
{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 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 [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 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 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}
{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}
{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}
>
);
}