diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..d402b116 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# EventBuzz Frontend – copy to .env and adjust +# Backend API base URL (used by services/api/client.ts) +VITE_API_URL=http://localhost:3001/api + +# Optional: public base URL for published custom microsites (no trailing slash). Links to {base}/{folder}/index.html +# VITE_MICROSITE_PUBLIC_BASE=https://web.eventbuzz.live + +# Public web app for default microsites, forms, and registration (no trailing slash) +# VITE_PUBLIC_WEB_BASE=https://web.eventbuzz.live + +# GrapesJS microsite builder (Vite app in /website_builder). Iframe src + postMessage target origin. +VITE_WEBSITE_BUILDER_URL=http://localhost:5173 +# Optional: exact origin for postMessage (defaults to origin of VITE_WEBSITE_BUILDER_URL) +# VITE_WEBSITE_BUILDER_ORIGIN=http://localhost:5173 + +# Optional: Google Sign-In (OAuth Web client ID — must match backend GOOGLE_CLIENT_ID) +# VITE_GOOGLE_CLIENT_ID= + +# Firebase Realtime Database (audience questions mirror — same project as backend FIREBASE_*) +# VITE_FIREBASE_API_KEY= +# VITE_FIREBASE_DATABASE_URL= diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8ddfe8c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +dist/ +.vite/ +.DS_Store +*.local + +# Env (keep .env.example) +.env +.env.* +!.env.example diff --git a/src/app/components/dashboard/EventHomeModules.tsx b/src/app/components/dashboard/EventHomeModules.tsx index 20f2f7b5..9dcafc49 100644 --- a/src/app/components/dashboard/EventHomeModules.tsx +++ b/src/app/components/dashboard/EventHomeModules.tsx @@ -141,14 +141,16 @@ function moduleButtonStyle(primary = false): React.CSSProperties { 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); } -function formPanelMode(apiType: string): "feedback" | "registration" | "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"; } @@ -186,7 +188,7 @@ export function EventHomeModules() { const [eventForms, setEventForms] = useState([]); const [formEditTarget, setFormEditTarget] = useState(null); const [formResponsesTarget, setFormResponsesTarget] = useState(null); - const [newFormMode, setNewFormMode] = useState<"registration" | "feedback" | "other" | null>(null); + const [newFormMode, setNewFormMode] = useState<"registration" | "feedback" | "other" | "web-check-in" | null>(null); const [copiedFormLinkId, setCopiedFormLinkId] = useState(null); const [pollQuizCounts, setPollQuizCounts] = useState({ polls: 0, quizzes: 0 }); const [pollQuizLoading, setPollQuizLoading] = useState(false); @@ -210,6 +212,9 @@ export function EventHomeModules() { const [staticMicrositeLoading, setStaticMicrositeLoading] = 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); useEffect(() => { if (!selectedEventId) { @@ -220,6 +225,8 @@ export function EventHomeModules() { 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; @@ -492,6 +499,28 @@ export function EventHomeModules() { : agendaLoading ? "Loading…" : `${agendaCounts.tracks} track${agendaCounts.tracks === 1 ? "" : "s"} · ${agendaCounts.sessions} session${agendaCounts.sessions === 1 ? "" : "s"}`; + const webCheckInMeta = !selectedEventId + ? "Select an event to configure web check-in" + : webCheckInLoading + ? "Saving…" + : webCheckInEnabled + ? "Enabled" + : "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 handleSavePollQuiz = async ( kind: "poll" | "quiz", @@ -835,6 +864,40 @@ export function EventHomeModules() { } /> + } + title="Web check-in" + meta={webCheckInMeta} + description="Show or hide accommodation choice for participants" + actions={ + <> + + + + } + /> } title="Create New Form" @@ -885,6 +948,18 @@ export function EventHomeModules() { > Feedback + + ); + })} + + + ) : null}