import React, { useCallback, useEffect, useState } from "react"; import { Check, Copy, Image, Link2, Trash2, Type } from "lucide-react"; import { PanelShell, Label, Field, Grid, IS, SS, focusIn, focusOut, HelperText, RadioGroup } from "../panels/PanelShell"; import { socialShareApi, uploadMediaFile, type SocialShareConfig, type SocialShareEventHeader, type SocialShareFrame, } from "../../../services/api"; import { socialShareVisitorUrl } from "../../utils/socialShareUrls"; import { EVENT_HEADER_TOGGLE_LABELS, eventHeaderFromApi, eventHeaderToApi, type EventHeaderToggleKey, } from "../../utils/formEventHeaderDisplay"; import { PhotoWallFrameDesigner } from "./PhotoWallFrameDesigner"; import { PhotoWallFrameInspector } from "./PhotoWallFrameInspector"; import { defaultCustomTextElement, defaultFrameImageLayout, defaultPhotoElement, normalizeFrameImageLayout, normalizeFrameLayout, type PhotoWallFrameElement, type PhotoWallFrameImageLayout, } from "./photoWallFrameLayout"; type Tab = "settings" | "frames" | "links"; const DEFAULT_HEADER = eventHeaderFromApi(null); export function SocialShareManagePanel({ eventId, eventName, canWrite, onClose, onChanged, }: { eventId: string; eventName?: string; canWrite: boolean; onClose: () => void; onChanged?: () => void; }) { const [tab, setTab] = useState("settings"); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [config, setConfig] = useState(null); const [frames, setFrames] = useState([]); const [copied, setCopied] = useState(false); const [editingFrameId, setEditingFrameId] = useState(null); const [selectedElId, setSelectedElId] = useState(null); const [uploadingFrame, setUploadingFrame] = useState(false); const [frameDraft, setFrameDraft] = useState({ name: "Frame", enabled: true, frameImageUrl: "" as string, frameImageLayout: defaultFrameImageLayout() as PhotoWallFrameImageLayout, frameCanvasWidth: 1080, frameCanvasHeight: 1080, frameLayout: [] as PhotoWallFrameElement[], }); const [draft, setDraft] = useState({ accessMode: "open" as "open" | "login", requireUploadCrop: false, maxPhotoWidth: 1080, maxPhotoHeight: 1080, allowDownload: true, eventHeader: DEFAULT_HEADER, sharePlatforms: { linkedin: false, facebook: false, instagram: false, snapchat: false, x: false, whatsapp: false, }, }); const load = useCallback(async () => { setLoading(true); setError(null); try { const res = await socialShareApi.get(eventId); if (res.error || !res.data) { setError(res.error || "Failed to load social share"); setConfig(null); setFrames([]); return; } const data = res.data; setConfig(data); setFrames(data.frames || []); setDraft({ accessMode: data.accessMode || "open", requireUploadCrop: Boolean(data.requireUploadCrop), maxPhotoWidth: data.maxPhotoWidth ?? 1080, maxPhotoHeight: data.maxPhotoHeight ?? 1080, allowDownload: data.allowDownload !== false, eventHeader: eventHeaderFromApi(data.eventHeader as SocialShareEventHeader | null), sharePlatforms: { linkedin: Boolean(data.sharePlatforms?.linkedin), facebook: Boolean(data.sharePlatforms?.facebook), instagram: Boolean(data.sharePlatforms?.instagram), snapchat: Boolean(data.sharePlatforms?.snapchat), x: Boolean(data.sharePlatforms?.x), whatsapp: Boolean(data.sharePlatforms?.whatsapp), }, }); } catch (e) { setError(e instanceof Error ? e.message : "Failed to load"); } finally { setLoading(false); } }, [eventId]); useEffect(() => { void load(); }, [load]); const openFrameEditor = (frame: SocialShareFrame) => { setEditingFrameId(frame._id); setSelectedElId(null); setFrameDraft({ name: frame.name || "Frame", enabled: frame.enabled !== false, frameImageUrl: frame.frameImageUrl || "", frameImageLayout: normalizeFrameImageLayout(frame.frameImageLayout || defaultFrameImageLayout()), frameCanvasWidth: frame.frameCanvasWidth ?? 1080, frameCanvasHeight: frame.frameCanvasHeight ?? 1080, frameLayout: normalizeFrameLayout( frame.frameLayout, frame.frameCanvasWidth ?? 1080, frame.frameCanvasHeight ?? 1080, ), }); }; const saveSettings = async () => { if (!canWrite) return; setSaving(true); setError(null); try { const res = await socialShareApi.update(eventId, { accessMode: draft.accessMode, requireUploadCrop: draft.requireUploadCrop, maxPhotoWidth: draft.maxPhotoWidth, maxPhotoHeight: draft.maxPhotoHeight, allowDownload: draft.allowDownload, sharePlatforms: draft.sharePlatforms, eventHeader: eventHeaderToApi(draft.eventHeader), }); if (res.error || !res.data) { setError(res.error || "Failed to save"); return; } setConfig(res.data); setFrames(res.data.frames || []); onChanged?.(); } catch (e) { setError(e instanceof Error ? e.message : "Failed to save"); } finally { setSaving(false); } }; const saveFrame = async () => { if (!canWrite || !editingFrameId) return; setSaving(true); setError(null); try { const res = await socialShareApi.updateFrame(eventId, editingFrameId, { name: frameDraft.name, enabled: frameDraft.enabled, frameImageUrl: frameDraft.frameImageUrl || null, frameImageLayout: frameDraft.frameImageLayout, frameCanvasWidth: frameDraft.frameCanvasWidth, frameCanvasHeight: frameDraft.frameCanvasHeight, frameLayout: frameDraft.frameLayout, }); if (res.error || !res.data) { setError(res.error || "Failed to save frame"); return; } setFrames((prev) => prev.map((f) => (f._id === res.data!._id ? res.data! : f))); onChanged?.(); } catch (e) { setError(e instanceof Error ? e.message : "Failed to save frame"); } finally { setSaving(false); } }; const addFrame = async () => { if (!canWrite) return; setSaving(true); setError(null); try { const res = await socialShareApi.createFrame(eventId, { name: `Frame ${frames.length + 1}`, frameLayout: [defaultPhotoElement(1080, 1080)], }); if (res.error || !res.data) { setError(res.error || "Failed to create frame"); return; } setFrames((prev) => [...prev, res.data!]); openFrameEditor(res.data); onChanged?.(); } catch (e) { setError(e instanceof Error ? e.message : "Failed to create frame"); } finally { setSaving(false); } }; const deleteFrame = async (frameId: string) => { if (!canWrite) return; if (!window.confirm("Delete this frame?")) return; setError(null); try { await socialShareApi.deleteFrame(eventId, frameId); setFrames((prev) => prev.filter((f) => f._id !== frameId)); if (editingFrameId === frameId) setEditingFrameId(null); onChanged?.(); } catch (e) { setError(e instanceof Error ? e.message : "Failed to delete"); } }; const uploadFrameImage = async (file: File | null) => { if (!file || !canWrite) return; setUploadingFrame(true); setError(null); try { const r = await uploadMediaFile(file, { eventId, module: "social-share/frames" }); const url = r.url || ""; if (!url) throw new Error("Upload succeeded but no URL returned"); setFrameDraft((d) => ({ ...d, frameImageUrl: url })); } catch (e) { setError(e instanceof Error ? e.message : "Frame upload failed"); } finally { setUploadingFrame(false); } }; const addFrameElement = (el: PhotoWallFrameElement) => { setFrameDraft((d) => ({ ...d, frameLayout: [...d.frameLayout, el] })); setSelectedElId(el.id); }; const copyLink = async () => { if (!config?.uploadToken) return; try { await navigator.clipboard.writeText(socialShareVisitorUrl(config.uploadToken)); setCopied(true); window.setTimeout(() => setCopied(false), 1500); } catch { /* ignore */ } }; const tabBtn = (id: Tab, label: string) => ( ); const ctaLabel = tab === "frames" && editingFrameId ? "Save frame" : tab === "settings" ? "Save settings" : "Save"; const showCta = tab === "settings" || (tab === "frames" && Boolean(editingFrameId)); return ( { if (tab === "frames" && editingFrameId) void saveFrame(); else void saveSettings(); }} panelWidth={tab === "frames" && editingFrameId ? "min(960px,100vw)" : "min(720px,100vw)"} >
{tabBtn("settings", "Settings")} {tabBtn("frames", "Frames")} {tabBtn("links", "Links")}
{error ? (

{error}

) : null} {loading ? (

Loading…

) : null} {!loading && tab === "links" && config ? (
Visitor upload URL

Guests pick a frame, upload a photo, then download or share

{config.uploadToken ? socialShareVisitorUrl(config.uploadToken) : "Enable social share to generate link"}
{canWrite ? ( ) : null}
) : null} {!loading && tab === "settings" ? (
) : null} {!loading && tab === "frames" ? ( editingFrameId ? (
setFrameDraft((d) => ({ ...d, frameLayout: next }))} onFrameImageLayoutChange={(next) => setFrameDraft((d) => ({ ...d, frameImageLayout: next })) } />
e.id === selectedElId) || null} disabled={!canWrite} canvasWidth={frameDraft.frameCanvasWidth} canvasHeight={frameDraft.frameCanvasHeight} onChange={(patch) => { if (!selectedElId) return; setFrameDraft((d) => ({ ...d, frameLayout: d.frameLayout.map((el) => el.id === selectedElId ? { ...el, ...patch } : el, ), })); }} /> {selectedElId ? ( ) : null}
) : (
{canWrite ? ( ) : null} {frames.length === 0 ? (

No frames yet. Add one to get started.

) : ( frames.map((frame) => (
{frame.frameImageUrl ? ( ) : null}
{frame.name || "Frame"}

{frame.enabled !== false ? "Enabled" : "Disabled"} · {frame.frameCanvasWidth}× {frame.frameCanvasHeight}

{canWrite ? ( ) : null}
)) )}
) ) : null}
); }