import React, { useCallback, useEffect, useState } from "react"; import { Plus, Trash2, Download, Settings } from "lucide-react"; import { conciergeBotsApi, uploadMediaFile, whatsappTemplatesApi, type ConciergeBotRecord, type ConciergeBotReception, type ConciergeReceptionDrip, type ConciergeReceptionInfo, } from "../../../services/api"; import type { WhatsAppTemplateRecord } from "../../../services/api/types"; import { useAppNav } from "../../contexts/AppNavContext"; import { WhatsAppCtaEditor, normalizeCtasForSave, type WhatsAppCtaItem } from "../whatsapp/WhatsAppCtaEditor"; const IS: React.CSSProperties = { width: "100%", padding: "8px 12px", border: "1px solid var(--border)", borderRadius: "var(--radius-button)", background: "var(--input-background)", fontSize: "var(--text-sm)", color: "var(--card-foreground)", boxSizing: "border-box", }; const btnPrimary: React.CSSProperties = { display: "inline-flex", alignItems: "center", gap: 6, padding: "8px 14px", borderRadius: "var(--radius-button)", background: "var(--primary)", color: "var(--primary-foreground)", border: "none", cursor: "pointer", fontSize: "var(--text-sm)", }; const btnSecondary: React.CSSProperties = { ...btnPrimary, background: "var(--secondary)", color: "var(--card-foreground)", border: "1px solid var(--border)", }; const DEFAULT_WELCOME = "Hi {firstName}! Welcome to {eventName}."; const DEFAULT_BADGE = "We couldn't match your WhatsApp to a registration. Please send a clear photo of your badge QR code."; const DEFAULT_STAFF = "We still couldn't identify you. Please see the reception desk for help. You can also send another badge photo."; function emptyDrip(): ConciergeReceptionDrip { return { trigger: "after_first_inbound", delayMinutes: 15, sendAt: "", body: "", mediaType: "none", mediaUrl: "", contentKind: "custom", agendaCount: 3, templateId: "", ctas: [], }; } function receptionFromBot(bot: ConciergeBotRecord): ConciergeBotReception { const r = bot.reception || {}; return { enabled: Boolean(r.enabled), prefillText: r.prefillText || "Hi", welcomeMessage: r.welcomeMessage || DEFAULT_WELCOME, unmatchedBadgePrompt: r.unmatchedBadgePrompt || DEFAULT_BADGE, staffHandoffMessage: r.staffHandoffMessage || DEFAULT_STAFF, dripAudience: r.dripAudience === "all_inbound" ? "all_inbound" : "matched", drips: Array.isArray(r.drips) && r.drips.length ? r.drips.map((d) => ({ ...emptyDrip(), ...d })) : [], }; } export function WhatsAppReceptionTab({ bot, eventId, onSaved, }: { bot: ConciergeBotRecord; eventId: string; onSaved: () => Promise | void; }) { const { setActiveNav, setSettingsInitialSection } = useAppNav(); const [reception, setReception] = useState(() => receptionFromBot(bot)); const [info, setInfo] = useState(null); const [templates, setTemplates] = useState([]); const [msg, setMsg] = useState(null); const [saving, setSaving] = useState(false); useEffect(() => { setReception(receptionFromBot(bot)); }, [bot._id, bot.updatedAt]); const loadInfo = useCallback(async () => { const res = await conciergeBotsApi.getReception(bot._id); if (!res.error && res.data) setInfo(res.data); }, [bot._id]); useEffect(() => { loadInfo(); whatsappTemplatesApi.list({ status: "APPROVED" }).then((r) => { if (!r.error) setTemplates(r.data?.templates || []); }); }, [loadInfo]); const patch = (partial: Partial) => setReception((prev) => ({ ...prev, ...partial })); const patchDrip = (index: number, partial: Partial) => { const drips = [...(reception.drips || [])]; drips[index] = { ...drips[index], ...partial }; patch({ drips }); }; const save = async () => { setSaving(true); setMsg(null); let dripsPayload: ConciergeReceptionDrip[]; try { dripsPayload = (reception.drips || []).map((d) => { const ctas = d.contentKind === "upcoming_agenda" ? [] : normalizeCtasForSave((d.ctas || []) as WhatsAppCtaItem[]); if (ctas.length && !String(d.body || "").trim()) { throw new Error("Drip message text is required when buttons are set."); } return { _id: d._id, trigger: d.trigger, delayMinutes: d.delayMinutes, body: d.body, mediaType: d.contentKind === "upcoming_agenda" ? "none" : d.mediaType, mediaUrl: d.contentKind === "upcoming_agenda" ? "" : d.mediaUrl, contentKind: d.contentKind || "custom", agendaCount: d.agendaCount, templateId: d.templateId || null, sendAt: d.sendAt || null, ctas, }; }); } catch (e) { setSaving(false); setMsg(e instanceof Error ? e.message : "Invalid buttons"); return; } const res = await conciergeBotsApi.update(bot._id, { reception: { ...reception, drips: dripsPayload, }, }); setSaving(false); if (res.error) { setMsg(res.error); return; } setMsg("Reception settings saved"); await onSaved(); await loadInfo(); }; const uploadDripMedia = async (index: number, file: File) => { const up = await uploadMediaFile(file, { eventId: eventId || "portfolio", module: "whatsapp/reception-drip" }); if (up.error || !up.url) { setMsg(up.error || "Upload failed"); return; } const mime = String(file.type || "").toLowerCase(); const mediaType = mime.startsWith("video/") ? "video" : "image"; patchDrip(index, { mediaUrl: up.url, mediaType }); }; const goSettings = () => { setSettingsInitialSection("whatsapp"); setActiveNav("settings"); }; const outside24h = (d: ConciergeReceptionDrip) => { if (d.trigger === "absolute") return true; return Number(d.delayMinutes || 0) > 24 * 60; }; return (

Desk / reception

{!info?.canSendMessages && (

WhatsApp Cloud is not ready to send. Configure WABA ID, token, and phone number ID in Organisation settings.

)} {info?.resolveError && info.canSendMessages && (

{info.resolveError}

)} {info?.displayPhone && (

Desk number (from organisation settings): {info.displayPhone}

)} {info?.qrPngBase64 && ( )}

Visitors scan the QR and send this text (default Hi). WhatsApp Cloud credentials stay in Organisation settings.