import React, { useMemo, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { MessageCircle, KeyRound, Send, FileText, Clock, Settings2, Loader2, CheckCircle2, XCircle, ExternalLink, ChevronDown, Eye, EyeOff, Save, ShieldCheck, Code, Copy, Check, Zap, Info, ArrowLeft, Pencil, Plus, Trash2, UserCircle, Webhook, Users, History, RotateCcw, } from "lucide-react"; import { __, sprintf } from "../lib/i18n"; import { todayYmd } from "../lib/dateFormat"; import { PageHeader } from "../components/common/PageHeader"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "../components/ui/card"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { ModulePageSkeleton, ModuleFormSkeleton, } from "../components/ui/module-skeleton"; import { Label } from "../components/ui/label"; import { Select } from "../components/ui/select"; import { Badge } from "../components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../components/ui/table"; import { Pagination } from "../components/shared/Pagination"; import { useToast } from "../components/ui/toast"; import { ConfirmationDialog } from "../components/ui/confirmation-dialog"; import { Modal } from "../components/ui/modal"; import { Alert } from "../components/ui/alert"; import { whatsappApi, type WhatsappMeta, type WhatsappSettings, type WhatsappSettingsResponse, type WhatsappTemplate, type WhatsappTemplateVersion, type WhatsappEvent, type WhatsappWidgetSettings, } from "../api/whatsapp-api"; /** * Yatra → WhatsApp Notifications hub. * * Mirrors EmailAutomation's tab strip + card pattern. Templates are * now backed by the `yatra_whatsapp_templates` DB table: * * - System templates: editable body / meta_template_name / language / * active; CANNOT change event_key, recipient_type, or be deleted * - Custom templates: every field editable, including delete */ type WhatsappTab = "delivery" | "templates" | "widget" | "logs" | "opt-ins"; function getInitialTab(): WhatsappTab { if (typeof window === "undefined") return "delivery"; const tab = new URLSearchParams(window.location.search).get("tab"); if (tab === "templates" || tab === "template") return "templates"; if (tab === "widget") return "widget"; if (tab === "logs" || tab === "log") return "logs"; if (tab === "opt-ins" || tab === "optins") return "opt-ins"; return "delivery"; } const Whatsapp: React.FC = () => { const queryClient = useQueryClient(); const { showToast } = useToast(); const [activeTab, setActiveTab] = useState(() => getInitialTab(), ); const switchTab = (next: WhatsappTab) => { setActiveTab(next); if (typeof window !== "undefined") { const url = new URL(window.location.href); url.searchParams.set("tab", next); window.history.replaceState({}, "", url.toString()); } }; const { data: meta, isLoading: metaLoading } = useQuery({ queryKey: ["whatsapp-meta"], queryFn: () => whatsappApi.getMeta(), }); const { data: cfg } = useQuery({ queryKey: ["whatsapp-settings"], queryFn: () => whatsappApi.getSettings(), enabled: Boolean(meta?.is_eligible && meta?.is_module_enabled), }); const saveSettings = useMutation({ mutationFn: (patch: Partial) => whatsappApi.updateSettings(patch), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["whatsapp-settings"] }); queryClient.invalidateQueries({ queryKey: ["whatsapp-meta"] }); showToast(__("WhatsApp settings saved.", "yatra"), "success"); }, onError: (e: any) => showToast(extractError(e), "error"), }); const saveCredential = useMutation({ mutationFn: (vars: { field: string; value: string }) => whatsappApi.updateCredential("cloud_api", vars.field, vars.value), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["whatsapp-settings"] }); queryClient.invalidateQueries({ queryKey: ["whatsapp-meta"] }); showToast(__("Credential saved.", "yatra"), "success"); }, onError: (e: any) => showToast(extractError(e), "error"), }); if (metaLoading) { return ; } if (!meta || !meta.is_eligible) { return (
); } const automationReady = Boolean(meta.is_module_enabled); const tabs: Array<{ key: WhatsappTab; label: string; icon: any }> = [ { key: "delivery", label: __("Delivery", "yatra"), icon: Send }, { key: "templates", label: __("Templates", "yatra"), icon: FileText }, { key: "widget", label: __("Frontend widget", "yatra"), icon: MessageCircle, }, { key: "logs", label: __("Message logs", "yatra"), icon: MessageCircle }, { key: "opt-ins", label: __("Opt-ins", "yatra"), icon: Users }, ]; return (
{activeTab === "delivery" && ( <> {!automationReady ? ( ) : !cfg ? ( ) : ( saveCredential.mutate({ field, value }) } onSaveSettings={(patch) => saveSettings.mutate(patch)} saving={saveSettings.isPending || saveCredential.isPending} /> )} )} {activeTab === "templates" && ( <> {!automationReady ? : } )} {activeTab === "widget" && ( <> {!automationReady ? ( ) : !cfg ? ( ) : ( saveSettings.mutate(patch)} saving={saveSettings.isPending} /> )} )} {activeTab === "logs" && (automationReady ? : )} {activeTab === "opt-ins" && (automationReady ? : )}
); }; /* -------------------------------------------------------------------------- */ /* Upgrade card / module prompt — same patterns as Email Automation */ /* -------------------------------------------------------------------------- */ const UpgradeCard: React.FC<{ meta?: WhatsappMeta }> = ({ meta }) => { const upgradeUrl = meta?.upgrade_url || "https://wpyatra.com/pricing?module=whatsapp"; return (
{__("WhatsApp Notifications", "yatra")} {__( "Available on the Growth plan (or Scale). Send booking + payment + reminder messages — bring your own Meta Cloud API credentials.", "yatra", )}
); }; const WhatsappModulePrompt: React.FC = () => (

{__("Enable the WhatsApp Notifications module", "yatra")}

{__( "Your license tier qualifies. Turn on WhatsApp Notifications under Modules to start configuring it.", "yatra", )}

); /* -------------------------------------------------------------------------- */ /* Delivery section */ /* -------------------------------------------------------------------------- */ const DeliverySection: React.FC<{ cfg: WhatsappSettingsResponse; meta: WhatsappMeta; onSaveCredential: (field: string, value: string) => void; onSaveSettings: (patch: Partial) => void; saving: boolean; }> = ({ cfg, meta, onSaveCredential, onSaveSettings, saving }) => { const settings = cfg.settings; const credStatus = cfg.credentials?.cloud_api ?? {}; const [tokenInput, setTokenInput] = useState(""); const [webhookInput, setWebhookInput] = useState(""); const [showToken, setShowToken] = useState(false); const [showWebhook, setShowWebhook] = useState(false); const [phoneNumberId, setPhoneNumberId] = useState(settings.phone_number_id); const [businessAccountId, setBusinessAccountId] = useState( settings.business_account_id, ); const [defaultCountryCode, setDefaultCountryCode] = useState( settings.default_country_code, ); const [senderName, setSenderName] = useState(settings.sender_display_name); const [adminPhone, setAdminPhone] = useState(settings.admin_phone); const [optInRequired, setOptInRequired] = useState(settings.opt_in_required); const [optInCopy, setOptInCopy] = useState(settings.opt_in_copy); const [reminderBefore, setReminderBefore] = useState( settings.reminder_hours_before, ); const [reviewAfter, setReviewAfter] = useState( settings.review_hours_after, ); React.useEffect(() => { setPhoneNumberId(settings.phone_number_id); setBusinessAccountId(settings.business_account_id); setDefaultCountryCode(settings.default_country_code); setSenderName(settings.sender_display_name); setAdminPhone(settings.admin_phone); setOptInRequired(settings.opt_in_required); setOptInCopy(settings.opt_in_copy); setReminderBefore(settings.reminder_hours_before); setReviewAfter(settings.review_hours_after); }, [ settings.phone_number_id, settings.business_account_id, settings.default_country_code, settings.sender_display_name, settings.admin_phone, settings.opt_in_required, settings.opt_in_copy, settings.reminder_hours_before, settings.review_hours_after, ]); const dirty = phoneNumberId !== settings.phone_number_id || businessAccountId !== settings.business_account_id || defaultCountryCode !== settings.default_country_code || senderName !== settings.sender_display_name || adminPhone !== settings.admin_phone || optInRequired !== settings.opt_in_required || optInCopy !== settings.opt_in_copy || reminderBefore !== settings.reminder_hours_before || reviewAfter !== settings.review_hours_after; const saveAll = () => onSaveSettings({ phone_number_id: phoneNumberId, business_account_id: businessAccountId, sender_display_name: senderName, default_country_code: defaultCountryCode, admin_phone: adminPhone, opt_in_required: optInRequired, opt_in_copy: optInCopy, reminder_hours_before: reminderBefore, review_hours_after: reviewAfter, }); return (
{/* Setup walkthrough */} {__( "Before you start — getting WhatsApp Cloud API access", "yatra", )} {__( "All values below come from Meta's WhatsApp Business Platform. Bring your own credentials — messages are billed directly by Meta, no markup from the plugin.", "yatra", )} {/* Credentials */} {__("WhatsApp Cloud API credentials", "yatra")} {__( "Encrypted credentials Meta requires for the plugin to send messages on your behalf. Stored with libsodium AEAD; the browser only ever sees a masked last-4 hint.", "yatra", )} setShowToken((v) => !v)} onChange={setTokenInput} onSave={() => { onSaveCredential("access_token", tokenInput); setTokenInput(""); }} onClear={() => onSaveCredential("access_token", "")} saving={saving} /> setShowWebhook((v) => !v)} onChange={setWebhookInput} onSave={() => { onSaveCredential("webhook_secret", webhookInput); setWebhookInput(""); }} onClear={() => onSaveCredential("webhook_secret", "")} saving={saving} /> {/* Identifiers + admin phone */} {__("Business identifiers", "yatra")} {__( "Numeric IDs from Meta + the admin phone that receives operator-recipient templates.", "yatra", )}
setPhoneNumberId(e.target.value)} />
setBusinessAccountId(e.target.value)} />
setSenderName(e.target.value)} />
setDefaultCountryCode(e.target.value)} />
{/* Admin phone — destination for recipient_type=admin templates */}

{__( 'Destination for any template with recipient type "Admin" — for example a "New booking" alert sent to the operator. Leave blank to skip those sends. Must be in E.164 format.', "yatra", )}

setAdminPhone(e.target.value)} />
{/* Compliance */} {__("Compliance & opt-in", "yatra")}