PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.13
Yatra – Travel Booking & Tour Operator Software v3.0.13
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / assets / admin / dist / js / Whatsapp.js

Whatsapp.js in Yatra – Travel Booking & Tour Operator Software 3.0.13, at assets/admin/dist/js/Whatsapp.js

2,197 lines 115.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { t as useQueryClient, r as reactExports, u as useQuery, v as useMutation, j as jsxRuntimeExports, bj as Send, k as FileText, M as MessageCircle, U as Users, V as ExternalLink, bt as Settings2, a5 as React, aF as Info, bU as KeyRound, g as CircleUser, bB as ShieldCheck, ar as Clock, D as Loader2, aV as Save, aD as CheckCircle2, b8 as XCircle, bw as EyeOff, aQ as Eye, w as Webhook, aA as Check, aG as Copy, z as ArrowLeft, Z as Zap, x as ChevronDown, bu as Code, aw as Plus, bD as Pencil, bI as History, aN as Trash2, R as RotateCcw } from "../../../dist/js/react-vendor-xzSqHjRF.js";
2 import { a as apiClient, u as useToast, _ as __, I as Input, g as todayYmd, s as sprintf } from "../../../dist/js/index-d5SvbCd2.js";
3 import { P as PageHeader, C as Card, d as CardContent, f as CardHeader, g as CardTitle, h as CardDescription, B as Button, v as Label, s as CardFooter, S as Select, a3 as Table, a4 as TableHeader, a5 as TableRow, a6 as TableHead, a7 as TableBody, a8 as TableCell, e as Badge, U as Pagination, k as ConfirmationDialog, M as Modal, A as Alert } from "./app.js";
4 import { M as ModulePageSkeleton, a as ModuleFormSkeleton } from "../../../dist/js/module-skeleton-Cb1q-Zbx.js";
5 const whatsappApi = {
6 getMeta: () => apiClient.get("/whatsapp/meta"),
7 getSettings: () => apiClient.get("/whatsapp/settings"),
8 updateSettings: (patch) => apiClient.put("/whatsapp/settings", { settings: patch }),
9 updateCredential: (provider, field, value) => apiClient.put("/whatsapp/credentials", {
10 provider,
11 field,
12 value
13 }),
14 listEvents: () => apiClient.get("/whatsapp/events"),
15 listTemplates: () => apiClient.get("/whatsapp/templates"),
16 getTemplate: (id) => apiClient.get(`/whatsapp/templates/${id}`),
17 createTemplate: (payload) => apiClient.post("/whatsapp/templates", payload),
18 updateTemplate: (id, payload) => apiClient.put(`/whatsapp/templates/${id}`, payload),
19 deleteTemplate: (id) => apiClient.delete(`/whatsapp/templates/${id}`),
20 /**
21 * Re-seed the bundled defaults. Useful when the operator has missed
22 * a system template (deleted accidentally pre-lockdown, or a
23 * first-install race left them with none).
24 */
25 restoreDefaults: () => apiClient.post("/whatsapp/templates/restore-defaults", {}),
26 testSendTemplate: (id, params) => apiClient.post(
27 `/whatsapp/templates/${id}/test-send`,
28 params
29 ),
30 /**
31 * Paginated message log. Returns `total` so the UI can render correct
32 * page counts without local slicing. `page` is 1-indexed.
33 */
34 listMessages: (params = {}) => apiClient.get("/whatsapp/messages", {
35 params: {
36 page: params.page ?? 1,
37 per_page: params.per_page ?? 20,
38 ...params.status ? { status: params.status } : {},
39 ...params.phone ? { phone: params.phone } : {}
40 }
41 }),
42 listOptIns: () => apiClient.get("/whatsapp/opt-ins"),
43 /* ----------------------- template versions ----------------------- */
44 /**
45 * Append-only edit history for a template. Returned newest-first.
46 * Operators restore from this list when a recent edit needs rollback.
47 */
48 listTemplateVersions: (id) => apiClient.get(`/whatsapp/templates/${id}/versions`),
49 /**
50 * Restore the template to a prior snapshot. Server snapshots the
51 * CURRENT state first so the chain stays linear ("undo this restore"
52 * is just another restore from the most-recent row).
53 */
54 restoreTemplateVersion: (id, versionId) => apiClient.post(
55 `/whatsapp/templates/${id}/versions/${versionId}/restore`,
56 {}
57 )
58 };
59 function getInitialTab() {
60 if (typeof window === "undefined") return "delivery";
61 const tab = new URLSearchParams(window.location.search).get("tab");
62 if (tab === "templates" || tab === "template") return "templates";
63 if (tab === "widget") return "widget";
64 if (tab === "logs" || tab === "log") return "logs";
65 if (tab === "opt-ins" || tab === "optins") return "opt-ins";
66 return "delivery";
67 }
68 const Whatsapp = () => {
69 const queryClient = useQueryClient();
70 const { showToast } = useToast();
71 const [activeTab, setActiveTab] = reactExports.useState(
72 () => getInitialTab()
73 );
74 const switchTab = (next) => {
75 setActiveTab(next);
76 if (typeof window !== "undefined") {
77 const url = new URL(window.location.href);
78 url.searchParams.set("tab", next);
79 window.history.replaceState({}, "", url.toString());
80 }
81 };
82 const { data: meta, isLoading: metaLoading } = useQuery({
83 queryKey: ["whatsapp-meta"],
84 queryFn: () => whatsappApi.getMeta()
85 });
86 const { data: cfg } = useQuery({
87 queryKey: ["whatsapp-settings"],
88 queryFn: () => whatsappApi.getSettings(),
89 enabled: Boolean((meta == null ? void 0 : meta.is_eligible) && (meta == null ? void 0 : meta.is_module_enabled))
90 });
91 const saveSettings = useMutation({
92 mutationFn: (patch) => whatsappApi.updateSettings(patch),
93 onSuccess: () => {
94 queryClient.invalidateQueries({ queryKey: ["whatsapp-settings"] });
95 queryClient.invalidateQueries({ queryKey: ["whatsapp-meta"] });
96 showToast(__("WhatsApp settings saved.", "yatra"), "success");
97 },
98 onError: (e) => showToast(extractError(e), "error")
99 });
100 const saveCredential = useMutation({
101 mutationFn: (vars) => whatsappApi.updateCredential("cloud_api", vars.field, vars.value),
102 onSuccess: () => {
103 queryClient.invalidateQueries({ queryKey: ["whatsapp-settings"] });
104 queryClient.invalidateQueries({ queryKey: ["whatsapp-meta"] });
105 showToast(__("Credential saved.", "yatra"), "success");
106 },
107 onError: (e) => showToast(extractError(e), "error")
108 });
109 if (metaLoading) {
110 return /* @__PURE__ */ jsxRuntimeExports.jsx(ModulePageSkeleton, { variant: "tabs" });
111 }
112 if (!meta || !meta.is_eligible) {
113 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
114 /* @__PURE__ */ jsxRuntimeExports.jsx(
115 PageHeader,
116 {
117 description: __(
118 "Send booking + payment + reminder messages over WhatsApp.",
119 "yatra"
120 )
121 }
122 ),
123 /* @__PURE__ */ jsxRuntimeExports.jsx(UpgradeCard, { meta })
124 ] });
125 }
126 const automationReady = Boolean(meta.is_module_enabled);
127 const tabs = [
128 { key: "delivery", label: __("Delivery", "yatra"), icon: Send },
129 { key: "templates", label: __("Templates", "yatra"), icon: FileText },
130 {
131 key: "widget",
132 label: __("Frontend widget", "yatra"),
133 icon: MessageCircle
134 },
135 { key: "logs", label: __("Message logs", "yatra"), icon: MessageCircle },
136 { key: "opt-ins", label: __("Opt-ins", "yatra"), icon: Users }
137 ];
138 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
139 /* @__PURE__ */ jsxRuntimeExports.jsx(
140 PageHeader,
141 {
142 description: __(
143 "WhatsApp credentials, system + custom templates, and message logs — bring your own Cloud API account.",
144 "yatra"
145 )
146 }
147 ),
148 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
149 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-b border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxRuntimeExports.jsx("nav", { className: "flex flex-wrap gap-1 px-4", children: tabs.map((tab) => {
150 const Icon = tab.icon;
151 const active = activeTab === tab.key;
152 return /* @__PURE__ */ jsxRuntimeExports.jsxs(
153 "button",
154 {
155 type: "button",
156 onClick: () => switchTab(tab.key),
157 className: `flex items-center gap-2 px-4 py-3 border-b-2 font-medium text-sm transition-colors ${active ? "border-blue-500 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600"}`,
158 children: [
159 /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { className: "w-4 h-4" }),
160 tab.label
161 ]
162 },
163 tab.key
164 );
165 }) }) }),
166 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "p-6", children: [
167 activeTab === "delivery" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: !automationReady ? /* @__PURE__ */ jsxRuntimeExports.jsx(WhatsappModulePrompt, {}) : !cfg ? /* @__PURE__ */ jsxRuntimeExports.jsx(ModuleFormSkeleton, { rows: 5 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
168 DeliverySection,
169 {
170 cfg,
171 meta,
172 onSaveCredential: (field, value) => saveCredential.mutate({ field, value }),
173 onSaveSettings: (patch) => saveSettings.mutate(patch),
174 saving: saveSettings.isPending || saveCredential.isPending
175 }
176 ) }),
177 activeTab === "templates" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: !automationReady ? /* @__PURE__ */ jsxRuntimeExports.jsx(WhatsappModulePrompt, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(TemplatesTab, {}) }),
178 activeTab === "widget" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: !automationReady ? /* @__PURE__ */ jsxRuntimeExports.jsx(WhatsappModulePrompt, {}) : !cfg ? /* @__PURE__ */ jsxRuntimeExports.jsx(ModuleFormSkeleton, { rows: 5 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
179 WidgetSection,
180 {
181 settings: cfg.settings,
182 onSave: (patch) => saveSettings.mutate(patch),
183 saving: saveSettings.isPending
184 }
185 ) }),
186 activeTab === "logs" && (automationReady ? /* @__PURE__ */ jsxRuntimeExports.jsx(LogsList, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(WhatsappModulePrompt, {})),
187 activeTab === "opt-ins" && (automationReady ? /* @__PURE__ */ jsxRuntimeExports.jsx(OptInsList, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(WhatsappModulePrompt, {}))
188 ] })
189 ] })
190 ] });
191 };
192 const UpgradeCard = ({ meta }) => {
193 const upgradeUrl = (meta == null ? void 0 : meta.upgrade_url) || "https://wpyatra.com/pricing?module=whatsapp";
194 return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { className: "max-w-3xl", children: [
195 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3", children: [
196 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md bg-green-100 p-2 text-green-600 dark:bg-green-900/30 dark:text-green-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(MessageCircle, { className: "h-5 w-5" }) }),
197 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
198 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("WhatsApp Notifications", "yatra") }),
199 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { className: "mt-1", children: __(
200 "Available on the Growth plan (or Scale). Send booking + payment + reminder messages — bring your own Meta Cloud API credentials.",
201 "yatra"
202 ) })
203 ] })
204 ] }) }),
205 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("a", { href: upgradeUrl, target: "_blank", rel: "noopener noreferrer", children: [
206 __("Upgrade to Growth", "yatra"),
207 /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "ml-2 h-4 w-4" })
208 ] }) }) })
209 ] });
210 };
211 const WhatsappModulePrompt = () => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-6 dark:border-amber-800 dark:bg-amber-950/30", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
212 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3", children: [
213 /* @__PURE__ */ jsxRuntimeExports.jsx(Settings2, { className: "mt-0.5 h-6 w-6 shrink-0 text-amber-600 dark:text-amber-400" }),
214 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
215 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-base font-semibold text-amber-900 dark:text-amber-100", children: __("Enable the WhatsApp Notifications module", "yatra") }),
216 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-1 text-sm text-amber-800/90 dark:text-amber-200/90", children: __(
217 "Your license tier qualifies. Turn on WhatsApp Notifications under Modules to start configuring it.",
218 "yatra"
219 ) })
220 ] })
221 ] }),
222 /* @__PURE__ */ jsxRuntimeExports.jsx(
223 Button,
224 {
225 type: "button",
226 variant: "outline",
227 asChild: true,
228 className: "border-amber-300 text-amber-800 hover:bg-amber-100 dark:border-amber-700 dark:text-amber-200 dark:hover:bg-amber-900/40",
229 children: /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "admin.php?page=yatra&subpage=modules", children: __("Open Modules", "yatra") })
230 }
231 )
232 ] }) });
233 const DeliverySection = ({ cfg, meta, onSaveCredential, onSaveSettings, saving }) => {
234 var _a, _b, _c, _d, _e;
235 const settings = cfg.settings;
236 const credStatus = ((_a = cfg.credentials) == null ? void 0 : _a.cloud_api) ?? {};
237 const [tokenInput, setTokenInput] = reactExports.useState("");
238 const [webhookInput, setWebhookInput] = reactExports.useState("");
239 const [showToken, setShowToken] = reactExports.useState(false);
240 const [showWebhook, setShowWebhook] = reactExports.useState(false);
241 const [phoneNumberId, setPhoneNumberId] = reactExports.useState(settings.phone_number_id);
242 const [businessAccountId, setBusinessAccountId] = reactExports.useState(
243 settings.business_account_id
244 );
245 const [defaultCountryCode, setDefaultCountryCode] = reactExports.useState(
246 settings.default_country_code
247 );
248 const [senderName, setSenderName] = reactExports.useState(settings.sender_display_name);
249 const [adminPhone, setAdminPhone] = reactExports.useState(settings.admin_phone);
250 const [optInRequired, setOptInRequired] = reactExports.useState(settings.opt_in_required);
251 const [optInCopy, setOptInCopy] = reactExports.useState(settings.opt_in_copy);
252 const [reminderBefore, setReminderBefore] = reactExports.useState(
253 settings.reminder_hours_before
254 );
255 const [reviewAfter, setReviewAfter] = reactExports.useState(
256 settings.review_hours_after
257 );
258 React.useEffect(() => {
259 setPhoneNumberId(settings.phone_number_id);
260 setBusinessAccountId(settings.business_account_id);
261 setDefaultCountryCode(settings.default_country_code);
262 setSenderName(settings.sender_display_name);
263 setAdminPhone(settings.admin_phone);
264 setOptInRequired(settings.opt_in_required);
265 setOptInCopy(settings.opt_in_copy);
266 setReminderBefore(settings.reminder_hours_before);
267 setReviewAfter(settings.review_hours_after);
268 }, [
269 settings.phone_number_id,
270 settings.business_account_id,
271 settings.default_country_code,
272 settings.sender_display_name,
273 settings.admin_phone,
274 settings.opt_in_required,
275 settings.opt_in_copy,
276 settings.reminder_hours_before,
277 settings.review_hours_after
278 ]);
279 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;
280 const saveAll = () => onSaveSettings({
281 phone_number_id: phoneNumberId,
282 business_account_id: businessAccountId,
283 sender_display_name: senderName,
284 default_country_code: defaultCountryCode,
285 admin_phone: adminPhone,
286 opt_in_required: optInRequired,
287 opt_in_copy: optInCopy,
288 reminder_hours_before: reminderBefore,
289 review_hours_after: reviewAfter
290 });
291 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
292 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { className: "border-blue-200 bg-blue-50/40 dark:border-blue-800 dark:bg-blue-950/20", children: [
293 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
294 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "flex items-center gap-2", children: [
295 /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { className: "h-5 w-5 text-blue-600 dark:text-blue-400" }),
296 __(
297 "Before you start — getting WhatsApp Cloud API access",
298 "yatra"
299 )
300 ] }),
301 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { className: "mt-2", children: __(
302 "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.",
303 "yatra"
304 ) })
305 ] }),
306 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-2", children: [
307 /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "outline", size: "sm", asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
308 "a",
309 {
310 href: "https://developers.facebook.com/docs/whatsapp/cloud-api/get-started",
311 target: "_blank",
312 rel: "noopener noreferrer",
313 children: [
314 /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }),
315 __("Cloud API: Get started", "yatra")
316 ]
317 }
318 ) }),
319 /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "outline", size: "sm", asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
320 "a",
321 {
322 href: "https://business.facebook.com/wa/manage/home/",
323 target: "_blank",
324 rel: "noopener noreferrer",
325 children: [
326 /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }),
327 __("Open WhatsApp Manager", "yatra")
328 ]
329 }
330 ) })
331 ] }) })
332 ] }),
333 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
334 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
335 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "flex items-center gap-2", children: [
336 /* @__PURE__ */ jsxRuntimeExports.jsx(KeyRound, { className: "h-5 w-5 text-blue-500" }),
337 __("WhatsApp Cloud API credentials", "yatra")
338 ] }),
339 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __(
340 "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.",
341 "yatra"
342 ) })
343 ] }),
344 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-6", children: [
345 /* @__PURE__ */ jsxRuntimeExports.jsx(
346 CredentialField,
347 {
348 label: __("Permanent access token", "yatra"),
349 description: __(
350 "Required for every API call to Meta. Find it under Meta for Developers → your app → WhatsApp → API Setup, or generate a permanent token under Business Settings → System Users.",
351 "yatra"
352 ),
353 docsUrl: "https://developers.facebook.com/docs/whatsapp/business-management-api/get-started#access-tokens",
354 placeholder: ((_b = credStatus.access_token) == null ? void 0 : _b.configured) ? credStatus.access_token.hint : "EAAB...",
355 configured: !!((_c = credStatus.access_token) == null ? void 0 : _c.configured),
356 visible: showToken,
357 value: tokenInput,
358 onToggle: () => setShowToken((v) => !v),
359 onChange: setTokenInput,
360 onSave: () => {
361 onSaveCredential("access_token", tokenInput);
362 setTokenInput("");
363 },
364 onClear: () => onSaveCredential("access_token", ""),
365 saving
366 }
367 ),
368 /* @__PURE__ */ jsxRuntimeExports.jsx(
369 CredentialField,
370 {
371 label: __("Webhook verify secret (App Secret)", "yatra"),
372 description: __(
373 "Required only when receiving inbound replies. Find it under your app → Settings → Basic → App Secret.",
374 "yatra"
375 ),
376 docsUrl: "https://developers.facebook.com/docs/whatsapp/cloud-api/guides/set-up-webhooks",
377 optional: true,
378 placeholder: ((_d = credStatus.webhook_secret) == null ? void 0 : _d.configured) ? credStatus.webhook_secret.hint : __("Optional — only needed when receiving messages.", "yatra"),
379 configured: !!((_e = credStatus.webhook_secret) == null ? void 0 : _e.configured),
380 visible: showWebhook,
381 value: webhookInput,
382 onToggle: () => setShowWebhook((v) => !v),
383 onChange: setWebhookInput,
384 onSave: () => {
385 onSaveCredential("webhook_secret", webhookInput);
386 setWebhookInput("");
387 },
388 onClear: () => onSaveCredential("webhook_secret", ""),
389 saving
390 }
391 )
392 ] })
393 ] }),
394 /* @__PURE__ */ jsxRuntimeExports.jsx(WebhookSetupCard, { meta }),
395 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
396 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
397 /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("Business identifiers", "yatra") }),
398 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __(
399 "Numeric IDs from Meta + the admin phone that receives operator-recipient templates.",
400 "yatra"
401 ) })
402 ] }),
403 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
404 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
405 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
406 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Phone number ID", "yatra") }),
407 /* @__PURE__ */ jsxRuntimeExports.jsx(
408 Input,
409 {
410 value: phoneNumberId,
411 placeholder: "123456789012345",
412 onChange: (e) => setPhoneNumberId(e.target.value)
413 }
414 )
415 ] }),
416 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
417 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("WhatsApp Business Account ID", "yatra") }),
418 /* @__PURE__ */ jsxRuntimeExports.jsx(
419 Input,
420 {
421 value: businessAccountId,
422 placeholder: "987654321098765",
423 onChange: (e) => setBusinessAccountId(e.target.value)
424 }
425 )
426 ] }),
427 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
428 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Sender display name", "yatra") }),
429 /* @__PURE__ */ jsxRuntimeExports.jsx(
430 Input,
431 {
432 value: senderName,
433 placeholder: __("Your tour brand", "yatra"),
434 onChange: (e) => setSenderName(e.target.value)
435 }
436 )
437 ] }),
438 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
439 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Default country code", "yatra") }),
440 /* @__PURE__ */ jsxRuntimeExports.jsx(
441 Input,
442 {
443 value: defaultCountryCode,
444 placeholder: "+977",
445 onChange: (e) => setDefaultCountryCode(e.target.value)
446 }
447 )
448 ] })
449 ] }),
450 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 rounded-md border border-orange-200 bg-orange-50/40 p-3 dark:border-orange-700 dark:bg-orange-900/20", children: [
451 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
452 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleUser, { className: "h-4 w-4 text-orange-600 dark:text-orange-400" }),
453 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { className: "font-semibold text-orange-900 dark:text-orange-200", children: __("Admin phone (for operator notifications)", "yatra") })
454 ] }),
455 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-orange-900/80 dark:text-orange-200/80", children: __(
456 '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.',
457 "yatra"
458 ) }),
459 /* @__PURE__ */ jsxRuntimeExports.jsx(
460 Input,
461 {
462 value: adminPhone,
463 placeholder: "+9779800000000",
464 onChange: (e) => setAdminPhone(e.target.value)
465 }
466 )
467 ] })
468 ] })
469 ] }),
470 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
471 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "flex items-center gap-2", children: [
472 /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldCheck, { className: "h-5 w-5 text-green-500" }),
473 __("Compliance & opt-in", "yatra")
474 ] }) }),
475 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
476 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-start gap-3 cursor-pointer", children: [
477 /* @__PURE__ */ jsxRuntimeExports.jsx(
478 "input",
479 {
480 type: "checkbox",
481 checked: optInRequired,
482 onChange: (e) => setOptInRequired(e.target.checked),
483 className: "mt-1"
484 }
485 ),
486 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
487 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-white", children: __("Require opt-in before sending", "yatra") }),
488 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: __(
489 "Refuses sends to phone numbers that haven't explicitly opted in. Admin-recipient templates are exempt (the operator owns that number).",
490 "yatra"
491 ) })
492 ] })
493 ] }),
494 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
495 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Opt-in checkbox copy", "yatra") }),
496 /* @__PURE__ */ jsxRuntimeExports.jsx(
497 "textarea",
498 {
499 className: "w-full rounded-md border border-gray-300 bg-white p-2 text-sm dark:border-gray-600 dark:bg-gray-800",
500 rows: 3,
501 value: optInCopy,
502 onChange: (e) => setOptInCopy(e.target.value),
503 placeholder: __(
504 "Send me booking updates and trip reminders on WhatsApp. Reply STOP to opt out anytime.",
505 "yatra"
506 )
507 }
508 )
509 ] })
510 ] })
511 ] }),
512 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
513 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
514 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "flex items-center gap-2", children: [
515 /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "h-5 w-5 text-blue-500" }),
516 __("Reminder schedule", "yatra")
517 ] }),
518 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __(
519 "How far before / after a trip the time-based templates fire. Clamped 1–168 hours.",
520 "yatra"
521 ) })
522 ] }),
523 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
524 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
525 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Departure reminder (hours before)", "yatra") }),
526 /* @__PURE__ */ jsxRuntimeExports.jsx(
527 Input,
528 {
529 type: "number",
530 min: 1,
531 max: 168,
532 value: reminderBefore,
533 onChange: (e) => setReminderBefore(Number(e.target.value) || 0)
534 }
535 )
536 ] }),
537 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
538 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Review request (hours after)", "yatra") }),
539 /* @__PURE__ */ jsxRuntimeExports.jsx(
540 Input,
541 {
542 type: "number",
543 min: 1,
544 max: 168,
545 value: reviewAfter,
546 onChange: (e) => setReviewAfter(Number(e.target.value) || 0)
547 }
548 )
549 ] })
550 ] }) })
551 ] }),
552 dirty && /* @__PURE__ */ jsxRuntimeExports.jsx(CardFooter, { className: "flex justify-end border-t border-gray-100 pt-4 dark:border-gray-700/50", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: saveAll, disabled: saving, children: saving ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
553 /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }),
554 __("Saving…", "yatra")
555 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
556 /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "mr-1.5 h-4 w-4" }),
557 __("Save WhatsApp settings", "yatra")
558 ] }) }) })
559 ] });
560 };
561 const WebhookSetupCard = ({ meta }) => {
562 const { showToast } = useToast();
563 const webhook = meta.webhook;
564 const [justCopied, setJustCopied] = reactExports.useState(null);
565 const copy = async (text, label) => {
566 try {
567 if (typeof navigator !== "undefined" && navigator.clipboard) {
568 await navigator.clipboard.writeText(text);
569 } else {
570 const ta = document.createElement("textarea");
571 ta.value = text;
572 ta.style.position = "fixed";
573 ta.style.opacity = "0";
574 document.body.appendChild(ta);
575 ta.select();
576 document.execCommand("copy");
577 document.body.removeChild(ta);
578 }
579 setJustCopied(label);
580 window.setTimeout(() => setJustCopied(null), 2e3);
581 } catch (_e) {
582 showToast(__("Failed to copy to clipboard.", "yatra"), "error");
583 }
584 };
585 const tokenMissing = !(webhook == null ? void 0 : webhook.verify_token);
586 return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
587 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
588 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "flex items-center gap-2", children: [
589 /* @__PURE__ */ jsxRuntimeExports.jsx(Webhook, { className: "h-4 w-4 text-blue-600 dark:text-blue-400" }),
590 __("Meta webhook setup", "yatra")
591 ] }),
592 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardDescription, { children: [
593 __(
594 "Paste these values into your Meta app → WhatsApp → Configuration → Webhooks. Once verified, Meta will deliver message status callbacks (sent / delivered / read / failed) and inbound replies here.",
595 "yatra"
596 ),
597 " ",
598 /* @__PURE__ */ jsxRuntimeExports.jsxs(
599 "a",
600 {
601 href: "https://developers.facebook.com/docs/whatsapp/cloud-api/guides/set-up-webhooks",
602 target: "_blank",
603 rel: "noopener noreferrer",
604 className: "text-blue-600 hover:underline dark:text-blue-400",
605 children: [
606 __("Setup guide", "yatra"),
607 " ",
608 /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "inline h-3 w-3" })
609 ]
610 }
611 )
612 ] })
613 ] }),
614 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
615 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
616 /* @__PURE__ */ jsxRuntimeExports.jsx(
617 Label,
618 {
619 htmlFor: "whatsapp-webhook-url",
620 className: "text-xs text-gray-500 dark:text-gray-400",
621 children: __("Callback URL", "yatra")
622 }
623 ),
624 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex gap-2", children: [
625 /* @__PURE__ */ jsxRuntimeExports.jsx(
626 Input,
627 {
628 id: "whatsapp-webhook-url",
629 readOnly: true,
630 value: (webhook == null ? void 0 : webhook.url) || "",
631 className: "font-mono text-sm",
632 onFocus: (e) => e.currentTarget.select()
633 }
634 ),
635 /* @__PURE__ */ jsxRuntimeExports.jsx(
636 Button,
637 {
638 type: "button",
639 variant: "outline",
640 size: "sm",
641 onClick: () => copy((webhook == null ? void 0 : webhook.url) || "", "url"),
642 disabled: !(webhook == null ? void 0 : webhook.url),
643 "aria-label": __("Copy callback URL", "yatra"),
644 children: justCopied === "url" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
645 /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "mr-1.5 h-3.5 w-3.5" }),
646 __("Copied", "yatra")
647 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
648 /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "mr-1.5 h-3.5 w-3.5" }),
649 __("Copy", "yatra")
650 ] })
651 }
652 )
653 ] })
654 ] }),
655 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
656 /* @__PURE__ */ jsxRuntimeExports.jsx(
657 Label,
658 {
659 htmlFor: "whatsapp-verify-token",
660 className: "text-xs text-gray-500 dark:text-gray-400",
661 children: __("Verify token", "yatra")
662 }
663 ),
664 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex gap-2", children: [
665 /* @__PURE__ */ jsxRuntimeExports.jsx(
666 Input,
667 {
668 id: "whatsapp-verify-token",
669 readOnly: true,
670 value: (webhook == null ? void 0 : webhook.verify_token) || "",
671 placeholder: tokenMissing ? __(
672 "Will be generated when you save settings for the first time.",
673 "yatra"
674 ) : "",
675 className: "font-mono text-sm",
676 onFocus: (e) => e.currentTarget.select()
677 }
678 ),
679 /* @__PURE__ */ jsxRuntimeExports.jsx(
680 Button,
681 {
682 type: "button",
683 variant: "outline",
684 size: "sm",
685 onClick: () => copy((webhook == null ? void 0 : webhook.verify_token) || "", "token"),
686 disabled: tokenMissing,
687 "aria-label": __("Copy verify token", "yatra"),
688 children: justCopied === "token" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
689 /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "mr-1.5 h-3.5 w-3.5" }),
690 __("Copied", "yatra")
691 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
692 /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "mr-1.5 h-3.5 w-3.5" }),
693 __("Copy", "yatra")
694 ] })
695 }
696 )
697 ] }),
698 tokenMissing && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-1 text-xs text-amber-600 dark:text-amber-400", children: __(
699 "Save your settings once to generate a verify token, then come back to copy it.",
700 "yatra"
701 ) })
702 ] }),
703 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md bg-gray-50 dark:bg-gray-800/50 px-3 py-2.5 text-sm", children: (webhook == null ? void 0 : webhook.app_secret_configured) ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-2 text-green-700 dark:text-green-400", children: [
704 /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-4 w-4" }),
705 __(
706 "App Secret is configured — webhook signatures will be verified.",
707 "yatra"
708 )
709 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-2 text-amber-700 dark:text-amber-400", children: [
710 /* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-4 w-4" }),
711 __(
712 "App Secret not set — inbound webhook events will be rejected for security. Paste your Meta App Secret above as “Webhook verify secret”.",
713 "yatra"
714 )
715 ] }) })
716 ] })
717 ] });
718 };
719 const CredentialField = ({
720 label,
721 description,
722 docsUrl,
723 optional,
724 placeholder,
725 configured,
726 visible,
727 value,
728 onToggle,
729 onChange,
730 onSave,
731 onClear,
732 saving
733 }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
734 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
735 /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { className: "flex items-center gap-1.5", children: [
736 label,
737 optional && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wide text-gray-400", children: __("optional", "yatra") })
738 ] }),
739 docsUrl && /* @__PURE__ */ jsxRuntimeExports.jsxs(
740 "a",
741 {
742 href: docsUrl,
743 target: "_blank",
744 rel: "noopener noreferrer",
745 className: "inline-flex items-center gap-1 text-xs text-blue-600 hover:underline dark:text-blue-400",
746 children: [
747 __("Where to find this", "yatra"),
748 /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "h-3 w-3" })
749 ]
750 }
751 )
752 ] }),
753 description && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400 flex items-start gap-1.5", children: [
754 /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { className: "h-3.5 w-3.5 mt-0.5 flex-shrink-0 text-gray-400" }),
755 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: description })
756 ] }),
757 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
758 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex-1", children: [
759 /* @__PURE__ */ jsxRuntimeExports.jsx(
760 Input,
761 {
762 type: visible ? "text" : "password",
763 value,
764 placeholder,
765 onChange: (e) => onChange(e.target.value)
766 }
767 ),
768 /* @__PURE__ */ jsxRuntimeExports.jsx(
769 "button",
770 {
771 type: "button",
772 onClick: onToggle,
773 className: "absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600",
774 children: visible ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "h-4 w-4" })
775 }
776 )
777 ] }),
778 /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { onClick: onSave, disabled: saving || value.trim() === "", children: [
779 /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "mr-1.5 h-4 w-4" }),
780 __("Save", "yatra")
781 ] }),
782 configured && /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "outline", onClick: onClear, disabled: saving, children: __("Clear", "yatra") })
783 ] }),
784 configured && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-green-600 dark:text-green-400", children: [
785 /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "mr-1 inline h-3 w-3" }),
786 __("Configured", "yatra")
787 ] })
788 ] });
789 const TemplatesTab = () => {
790 const [editingId, setEditingId] = reactExports.useState(null);
791 const { data: tplData, isLoading: tplLoading } = useQuery({
792 queryKey: ["whatsapp-templates"],
793 queryFn: () => whatsappApi.listTemplates()
794 });
795 const { data: eventsData, isLoading: eventsLoading } = useQuery({
796 queryKey: ["whatsapp-events"],
797 queryFn: () => whatsappApi.listEvents()
798 });
799 if (tplLoading || eventsLoading) {
800 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center py-16", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-10 w-10 animate-spin text-gray-400" }) });
801 }
802 const templates = (tplData == null ? void 0 : tplData.data) ?? [];
803 const events = (eventsData == null ? void 0 : eventsData.data) ?? [];
804 if (editingId !== null) {
805 const existing = editingId === "new" ? null : templates.find((t) => t.id === editingId) ?? null;
806 return /* @__PURE__ */ jsxRuntimeExports.jsx(
807 TemplateEditForm,
808 {
809 existing,
810 events,
811 onClose: () => setEditingId(null)
812 }
813 );
814 }
815 return /* @__PURE__ */ jsxRuntimeExports.jsx(
816 TemplatesList,
817 {
818 templates,
819 events,
820 onEdit: (id) => setEditingId(id),
821 onCreate: () => setEditingId("new")
822 }
823 );
824 };
825 const TemplatesList = ({ templates, events, onEdit, onCreate }) => {
826 const queryClient = useQueryClient();
827 const { showToast } = useToast();
828 const eventName = (key) => {
829 var _a;
830 return ((_a = events.find((e) => e.key === key)) == null ? void 0 : _a.name) ?? key;
831 };
832 const [pendingDelete, setPendingDelete] = reactExports.useState(null);
833 const [historyFor, setHistoryFor] = reactExports.useState(null);
834 const deleteTpl = useMutation({
835 mutationFn: (id) => whatsappApi.deleteTemplate(id),
836 onSuccess: () => {
837 queryClient.invalidateQueries({ queryKey: ["whatsapp-templates"] });
838 showToast(__("Template deleted.", "yatra"), "success");
839 setPendingDelete(null);
840 },
841 onError: (e) => {
842 showToast(extractError(e), "error");
843 setPendingDelete(null);
844 }
845 });
846 const restoreDefaults = useMutation({
847 mutationFn: () => whatsappApi.restoreDefaults(),
848 onSuccess: () => {
849 queryClient.invalidateQueries({ queryKey: ["whatsapp-templates"] });
850 showToast(__("Default templates restored.", "yatra"), "success");
851 },
852 onError: (e) => showToast(extractError(e), "error")
853 });
854 const noSystemTemplates = templates.every((t) => !t.is_system);
855 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
856 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
857 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
858 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white", children: __("WhatsApp templates", "yatra") }),
859 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: __(
860 "System templates ship with the plugin — you can toggle them on/off but not edit or delete them. Add custom templates for any event you want.",
861 "yatra"
862 ) })
863 ] }),
864 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
865 noSystemTemplates && /* @__PURE__ */ jsxRuntimeExports.jsxs(
866 Button,
867 {
868 variant: "outline",
869 onClick: () => restoreDefaults.mutate(),
870 disabled: restoreDefaults.isPending,
871 children: [
872 restoreDefaults.isPending ? /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
873 __("Restore default templates", "yatra")
874 ]
875 }
876 ),
877 /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { onClick: onCreate, children: [
878 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
879 __("Add custom template", "yatra")
880 ] })
881 ] })
882 ] }),
883 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "p-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full", children: [
884 /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { className: "bg-gray-50 dark:bg-gray-800", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
885 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase", children: __("Name", "yatra") }),
886 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase", children: __("Event", "yatra") }),
887 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase", children: __("Recipient", "yatra") }),
888 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase", children: __("Type", "yatra") }),
889 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase", children: __("Status", "yatra") }),
890 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase", children: __("Actions", "yatra") })
891 ] }) }),
892 /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { className: "divide-y divide-gray-200 dark:divide-gray-700", children: templates.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
893 "td",
894 {
895 colSpan: 6,
896 className: "p-8 text-center text-gray-500 dark:text-gray-400",
897 children: __(
898 "No templates yet — click Add custom template.",
899 "yatra"
900 )
901 }
902 ) }) : templates.map((tpl) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
903 "tr",
904 {
905 className: "hover:bg-gray-50 dark:hover:bg-gray-800/50",
906 children: [
907 /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "px-4 py-3", children: [
908 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-900 dark:text-white", children: [
909 /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { className: "h-4 w-4 text-blue-500" }),
910 tpl.name
911 ] }),
912 tpl.description && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 text-xs text-gray-500 dark:text-gray-400 line-clamp-1", children: tpl.description })
913 ] }),
914 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400", children: [
915 /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "w-3 h-3" }),
916 eventName(tpl.event_key)
917 ] }) }),
918 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
919 Badge,
920 {
921 className: tpl.recipient_type === "admin" ? "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
922 children: tpl.recipient_type === "admin" ? __("Admin", "yatra") : __("Customer", "yatra")
923 }
924 ) }),
925 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
926 Badge,
927 {
928 className: tpl.is_system ? "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
929 children: tpl.is_system ? __("System", "yatra") : __("Custom", "yatra")
930 }
931 ) }),
932 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
933 Badge,
934 {
935 className: tpl.is_active ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-300",
936 children: tpl.is_active ? __("Active", "yatra") : __("Disabled", "yatra")
937 }
938 ) }),
939 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-3 text-right", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "inline-flex gap-1", children: [
940 /* @__PURE__ */ jsxRuntimeExports.jsxs(
941 Button,
942 {
943 variant: "outline",
944 size: "sm",
945 onClick: () => onEdit(tpl.id),
946 children: [
947 /* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { className: "mr-1.5 h-3.5 w-3.5" }),
948 __("Edit", "yatra")
949 ]
950 }
951 ),
952 /* @__PURE__ */ jsxRuntimeExports.jsx(
953 Button,
954 {
955 variant: "outline",
956 size: "sm",
957 onClick: () => setHistoryFor({
958 id: tpl.id,
959 name: tpl.name,
960 isSystem: tpl.is_system
961 }),
962 "aria-label": __("Version history", "yatra"),
963 title: __("Version history", "yatra"),
964 children: /* @__PURE__ */ jsxRuntimeExports.jsx(History, { className: "h-3.5 w-3.5" })
965 }
966 ),
967 !tpl.is_system && /* @__PURE__ */ jsxRuntimeExports.jsx(
968 Button,
969 {
970 variant: "outline",
971 size: "sm",
972 className: "border-red-200 text-red-700 hover:bg-red-50 dark:border-red-700 dark:text-red-300 dark:hover:bg-red-900/30",
973 onClick: () => setPendingDelete({ id: tpl.id, name: tpl.name }),
974 disabled: deleteTpl.isPending,
975 "aria-label": __("Delete template", "yatra"),
976 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "h-3.5 w-3.5" })
977 }
978 )
979 ] }) })
980 ]
981 },
982 tpl.id
983 )) })
984 ] }) }) }) }),
985 /* @__PURE__ */ jsxRuntimeExports.jsx(
986 ConfirmationDialog,
987 {
988 isOpen: pendingDelete !== null,
989 onClose: () => {
990 if (!deleteTpl.isPending) setPendingDelete(null);
991 },
992 onConfirm: () => {
993 if (pendingDelete) deleteTpl.mutate(pendingDelete.id);
994 },
995 title: __("Delete template?", "yatra"),
996 description: pendingDelete ? __(
997 "Delete the “{name}” template? This cannot be undone, and any events bound to it will stop sending until you create a replacement.",
998 "yatra"
999 ).replace("{name}", pendingDelete.name) : "",
1000 confirmText: __("Delete template", "yatra"),
1001 cancelText: __("Cancel", "yatra"),
1002 variant: "danger",
1003 isLoading: deleteTpl.isPending
1004 }
1005 ),
1006 /* @__PURE__ */ jsxRuntimeExports.jsx(
1007 TemplateHistoryDialog,
1008 {
1009 template: historyFor,
1010 onClose: () => setHistoryFor(null)
1011 }
1012 )
1013 ] });
1014 };
1015 const TemplateHistoryDialog = ({ template, onClose }) => {
1016 const queryClient = useQueryClient();
1017 const { showToast } = useToast();
1018 const [expandedId, setExpandedId] = reactExports.useState(null);
1019 const [pendingRestore, setPendingRestore] = reactExports.useState(null);
1020 const enabled = template !== null;
1021 const { data, isLoading } = useQuery({
1022 queryKey: ["whatsapp-template-versions", template == null ? void 0 : template.id],
1023 queryFn: () => whatsappApi.listTemplateVersions(template.id),
1024 enabled
1025 });
1026 const restore = useMutation({
1027 mutationFn: (versionId) => whatsappApi.restoreTemplateVersion(template.id, versionId),
1028 onSuccess: (r) => {
1029 showToast(r.message, "success");
1030 void queryClient.invalidateQueries({ queryKey: ["whatsapp-templates"] });
1031 void queryClient.invalidateQueries({
1032 queryKey: ["whatsapp-template-versions", template.id]
1033 });
1034 setPendingRestore(null);
1035 },
1036 onError: (e) => {
1037 showToast(extractError(e), "error");
1038 setPendingRestore(null);
1039 }
1040 });
1041 if (!template) return null;
1042 const versions = (data == null ? void 0 : data.data) ?? [];
1043 return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1044 Modal,
1045 {
1046 isOpen: true,
1047 onClose,
1048 title: sprintf(
1049 /* translators: %s: template name */
1050 __("Version history — %s", "yatra"),
1051 template.name
1052 ),
1053 size: "xl",
1054 children: [
1055 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
1056 template.isSystem && /* @__PURE__ */ jsxRuntimeExports.jsx(Alert, { variant: "info", children: __(
1057 "Restoring a system template only resets its Active/Disabled toggle. Body, event binding, and recipient type are managed by the plugin and can't be reverted from history.",
1058 "yatra"
1059 ) }),
1060 isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-6 w-6 animate-spin text-gray-400" }) }) : versions.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center py-10", children: [
1061 /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "w-10 h-10 text-gray-400 mx-auto" }),
1062 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-3 text-sm text-gray-600 dark:text-gray-400", children: __(
1063 "No prior versions yet. Make an edit and history starts building from the next save.",
1064 "yatra"
1065 ) })
1066 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2 max-h-[60vh] overflow-y-auto", children: versions.map((v) => {
1067 const isExpanded = expandedId === v.id;
1068 return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1069 "div",
1070 {
1071 className: "rounded-md border border-gray-200 dark:border-gray-700",
1072 children: [
1073 /* @__PURE__ */ jsxRuntimeExports.jsxs(
1074 "button",
1075 {
1076 type: "button",
1077 className: "w-full flex items-start gap-3 px-3 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-800/40",
1078 onClick: () => setExpandedId(isExpanded ? null : v.id),
1079 children: [
1080 /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { className: "bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300 mt-0.5", children: [
1081 "v",
1082 v.version_number
1083 ] }),
1084 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
1085 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-gray-900 dark:text-white", children: v.change_summary || __("(no diff captured)", "yatra") }),
1086 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-gray-500 mt-0.5", children: [
1087 v.created_at,
1088 v.created_by !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
1089 " · ",
1090 sprintf(
1091 /* translators: %d: WP user id of the editor */
1092 __("by user #%d", "yatra"),
1093 v.created_by
1094 )
1095 ] })
1096 ] })
1097 ] }),
1098 /* @__PURE__ */ jsxRuntimeExports.jsx(
1099 ChevronDown,
1100 {
1101 className: `h-4 w-4 text-gray-400 mt-1 transition-transform ${isExpanded ? "rotate-180" : ""}`
1102 }
1103 )
1104 ]
1105 }
1106 ),
1107 isExpanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-gray-200 dark:border-gray-700 p-3 space-y-2 text-xs", children: [
1108 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-2 text-gray-600 dark:text-gray-300", children: [
1109 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1110 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-gray-500", children: __("Name", "yatra") }),
1111 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: v.snapshot.name || "—" })
1112 ] }),
1113 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1114 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-gray-500", children: __("Event", "yatra") }),
1115 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono", children: v.snapshot.event_key || "—" })
1116 ] }),
1117 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1118 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-gray-500", children: __("Recipient", "yatra") }),
1119 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: v.snapshot.recipient_type || "—" })
1120 ] }),
1121 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1122 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-gray-500", children: __("Active", "yatra") }),
1123 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: v.snapshot.is_active ? __("Yes", "yatra") : __("No", "yatra") })
1124 ] })
1125 ] }),
1126 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1127 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-gray-500", children: __("Body", "yatra") }),
1128 /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "mt-1 whitespace-pre-wrap rounded bg-gray-50 dark:bg-gray-800/50 p-2 font-mono text-[11px] max-h-48 overflow-y-auto", children: v.snapshot.body || "—" })
1129 ] }),
1130 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end pt-1", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1131 Button,
1132 {
1133 size: "sm",
1134 variant: "outline",
1135 onClick: () => setPendingRestore(v),
1136 children: [
1137 /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "h-3.5 w-3.5 mr-1" }),
1138 __("Restore this version", "yatra")
1139 ]
1140 }
1141 ) })
1142 ] })
1143 ]
1144 },
1145 v.id
1146 );
1147 }) })
1148 ] }),
1149 /* @__PURE__ */ jsxRuntimeExports.jsx(
1150 ConfirmationDialog,
1151 {
1152 isOpen: pendingRestore !== null,
1153 onClose: () => {
1154 if (!restore.isPending) setPendingRestore(null);
1155 },
1156 onConfirm: () => {
1157 if (pendingRestore) restore.mutate(pendingRestore.id);
1158 },
1159 title: __("Restore this version?", "yatra"),
1160 description: pendingRestore ? sprintf(
1161 __(
1162 "This rewrites the live template with v%d's contents. The current state is saved as a new version first so you can roll back this restore later.",
1163 "yatra"
1164 ),
1165 pendingRestore.version_number
1166 ) : "",
1167 confirmText: __("Restore version", "yatra"),
1168 cancelText: __("Cancel", "yatra"),
1169 isLoading: restore.isPending
1170 }
1171 )
1172 ]
1173 }
1174 );
1175 };
1176 const TemplateEditForm = ({ existing, events, onClose }) => {
1177 const queryClient = useQueryClient();
1178 const { showToast } = useToast();
1179 const isCreate = existing === null;
1180 const isSystem = !!(existing == null ? void 0 : existing.is_system);
1181 const [name, setName] = reactExports.useState((existing == null ? void 0 : existing.name) ?? "");
1182 const [description, setDescription] = reactExports.useState((existing == null ? void 0 : existing.description) ?? "");
1183 const [eventKey, setEventKey] = reactExports.useState((existing == null ? void 0 : existing.event_key) ?? "");
1184 const [recipient, setRecipient] = reactExports.useState(
1185 (existing == null ? void 0 : existing.recipient_type) ?? "customer"
1186 );
1187 const [isActive, setIsActive] = reactExports.useState((existing == null ? void 0 : existing.is_active) ?? true);
1188 const [metaTemplateName, setMetaTemplateName] = reactExports.useState(
1189 (existing == null ? void 0 : existing.meta_template_name) ?? ""
1190 );
1191 const [languageCode, setLanguageCode] = reactExports.useState(
1192 (existing == null ? void 0 : existing.language_code) ?? "en_US"
1193 );
1194 const [body, setBody] = reactExports.useState((existing == null ? void 0 : existing.body) ?? "");
1195 const [copiedVar, setCopiedVar] = reactExports.useState(null);
1196 const [showEventDropdown, setShowEventDropdown] = reactExports.useState(false);
1197 const bodyRef = React.useRef(null);
1198 const selectedEvent = reactExports.useMemo(
1199 () => events.find((e) => e.key === eventKey) ?? null,
1200 [events, eventKey]
1201 );
1202 const variables = (selectedEvent == null ? void 0 : selectedEvent.variables) ?? [];
1203 const save = useMutation({
1204 mutationFn: () => {
1205 const payload = isSystem ? { is_active: isActive } : {
1206 name,
1207 description,
1208 event_key: eventKey,
1209 recipient_type: recipient,
1210 is_active: isActive,
1211 body,
1212 settings: {
1213 meta_template_name: metaTemplateName,
1214 language_code: languageCode
1215 }
1216 };
1217 return isCreate ? whatsappApi.createTemplate(payload) : whatsappApi.updateTemplate(existing.id, payload);
1218 },
1219 onSuccess: () => {
1220 queryClient.invalidateQueries({ queryKey: ["whatsapp-templates"] });
1221 showToast(
1222 isCreate ? __("Template created.", "yatra") : __("Template saved.", "yatra"),
1223 "success"
1224 );
1225 onClose();
1226 },
1227 onError: (e) => showToast(extractError(e), "error")
1228 });
1229 const copyVariable = (placeholder) => {
1230 var _a;
1231 try {
1232 (_a = navigator.clipboard) == null ? void 0 : _a.writeText(placeholder);
1233 setCopiedVar(placeholder);
1234 setTimeout(() => setCopiedVar(null), 1500);
1235 } catch {
1236 }
1237 };
1238 const insertVariable = (placeholder) => {
1239 const el = bodyRef.current;
1240 if (!el) {
1241 setBody((prev) => prev + placeholder);
1242 return;
1243 }
1244 const start = el.selectionStart ?? body.length;
1245 const end = el.selectionEnd ?? body.length;
1246 const next = body.slice(0, start) + placeholder + body.slice(end);
1247 setBody(next);
1248 requestAnimationFrame(() => {
1249 if (!bodyRef.current) return;
1250 const cursor = start + placeholder.length;
1251 bodyRef.current.focus();
1252 bodyRef.current.setSelectionRange(cursor, cursor);
1253 });
1254 };
1255 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
1256 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-4", children: [
1257 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
1258 /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "outline", size: "sm", onClick: onClose, children: [
1259 /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { className: "mr-1.5 h-4 w-4" }),
1260 __("Back to templates", "yatra")
1261 ] }),
1262 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1263 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "flex items-center gap-2 text-xl font-semibold text-gray-900 dark:text-white", children: [
1264 /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { className: "h-5 w-5 text-blue-500" }),
1265 isCreate ? __("Add custom template", "yatra") : existing.name,
1266 isSystem && /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { className: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300", children: __("System", "yatra") })
1267 ] }),
1268 (existing == null ? void 0 : existing.description) && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-0.5 text-sm text-gray-500 dark:text-gray-400", children: existing.description })
1269 ] })
1270 ] }),
1271 /* @__PURE__ */ jsxRuntimeExports.jsx(
1272 Button,
1273 {
1274 onClick: () => save.mutate(),
1275 disabled: save.isPending || name.trim() === "",
1276 children: save.isPending ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1277 /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }),
1278 __("Saving…", "yatra")
1279 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1280 /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "mr-1.5 h-4 w-4" }),
1281 isCreate ? __("Create template", "yatra") : __("Save changes", "yatra")
1282 ] })
1283 }
1284 )
1285 ] }),
1286 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 gap-6 lg:grid-cols-3", children: [
1287 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-6 lg:col-span-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
1288 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "flex items-center justify-between", children: [
1289 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: __("Template details", "yatra") }),
1290 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm font-normal", children: [
1291 /* @__PURE__ */ jsxRuntimeExports.jsx(
1292 "input",
1293 {
1294 type: "checkbox",
1295 checked: isActive,
1296 onChange: (e) => setIsActive(e.target.checked)
1297 }
1298 ),
1299 /* @__PURE__ */ jsxRuntimeExports.jsx(
1300 Badge,
1301 {
1302 className: isActive ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-300",
1303 children: isActive ? __("Active", "yatra") : __("Disabled", "yatra")
1304 }
1305 )
1306 ] })
1307 ] }) }),
1308 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [
1309 isSystem && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-blue-200 bg-blue-50/60 p-3 dark:border-blue-700 dark:bg-blue-900/20", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2", children: [
1310 /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { className: "mt-0.5 h-4 w-4 shrink-0 text-blue-600 dark:text-blue-400" }),
1311 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-blue-900 dark:text-blue-200", children: __(
1312 "This is a system template. You can toggle it on or off but every other field is locked — that way plugin updates can safely refresh the copy. Create a custom template if you need different content for this event.",
1313 "yatra"
1314 ) })
1315 ] }) }),
1316 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1317 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Template name", "yatra") }),
1318 /* @__PURE__ */ jsxRuntimeExports.jsx(
1319 Input,
1320 {
1321 value: name,
1322 onChange: (e) => setName(e.target.value),
1323 readOnly: isSystem,
1324 className: isSystem ? "bg-gray-50 dark:bg-gray-900/40" : "",
1325 placeholder: __("e.g. Welcome to your trip", "yatra")
1326 }
1327 )
1328 ] }),
1329 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1330 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Description", "yatra") }),
1331 /* @__PURE__ */ jsxRuntimeExports.jsx(
1332 Input,
1333 {
1334 value: description ?? "",
1335 onChange: (e) => setDescription(e.target.value),
1336 readOnly: isSystem,
1337 className: isSystem ? "bg-gray-50 dark:bg-gray-900/40" : "",
1338 placeholder: __(
1339 "Short description shown in the template list",
1340 "yatra"
1341 )
1342 }
1343 )
1344 ] }),
1345 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1346 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300", children: __("Trigger Event", "yatra") }),
1347 isSystem ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1348 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1349 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium bg-indigo-100 text-indigo-800 dark:bg-indigo-900/30 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-800", children: [
1350 /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "w-4 h-4" }),
1351 (selectedEvent == null ? void 0 : selectedEvent.name) ?? eventKey
1352 ] }),
1353 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: __("System templates can't change events", "yatra") })
1354 ] }),
1355 (selectedEvent == null ? void 0 : selectedEvent.description) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg border border-indigo-200 dark:border-indigo-800", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-indigo-800 dark:text-indigo-300", children: selectedEvent.description }) })
1356 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
1357 /* @__PURE__ */ jsxRuntimeExports.jsxs(
1358 "button",
1359 {
1360 type: "button",
1361 onClick: () => setShowEventDropdown((v) => !v),
1362 className: "w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-left flex items-center justify-between hover:border-gray-400 dark:hover:border-gray-500 transition-colors",
1363 children: [
1364 selectedEvent ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1365 /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "w-4 h-4 text-blue-500" }),
1366 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-white", children: selectedEvent.name }),
1367 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 rounded", children: selectedEvent.key })
1368 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1369 /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "w-4 h-4 text-gray-400" }),
1370 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: __("Pick an event…", "yatra") })
1371 ] }),
1372 /* @__PURE__ */ jsxRuntimeExports.jsx(
1373 ChevronDown,
1374 {
1375 className: `w-4 h-4 text-gray-400 transition-transform ${showEventDropdown ? "rotate-180" : ""}`
1376 }
1377 )
1378 ]
1379 }
1380 ),
1381 showEventDropdown && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-80 overflow-y-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "py-1", children: events.map((event) => {
1382 const active = event.key === eventKey;
1383 return /* @__PURE__ */ jsxRuntimeExports.jsx(
1384 "button",
1385 {
1386 type: "button",
1387 onClick: () => {
1388 setEventKey(event.key);
1389 setShowEventDropdown(false);
1390 },
1391 className: `w-full px-3 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors border-b border-gray-100 dark:border-gray-700 last:border-b-0 ${active ? "bg-indigo-50 dark:bg-indigo-900/20" : ""}`,
1392 children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2", children: [
1393 /* @__PURE__ */ jsxRuntimeExports.jsx(
1394 Zap,
1395 {
1396 className: `w-4 h-4 mt-0.5 flex-shrink-0 ${active ? "text-blue-500" : "text-gray-400"}`
1397 }
1398 ),
1399 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
1400 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1401 /* @__PURE__ */ jsxRuntimeExports.jsx(
1402 "span",
1403 {
1404 className: `text-sm font-medium ${active ? "text-blue-800 dark:text-blue-400" : "text-gray-900 dark:text-white"}`,
1405 children: event.name
1406 }
1407 ),
1408 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-xs px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 rounded", children: event.key })
1409 ] }),
1410 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5 line-clamp-2", children: event.description })
1411 ] }),
1412 active && /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "w-4 h-4 text-blue-500 flex-shrink-0" })
1413 ] })
1414 },
1415 event.key
1416 );
1417 }) }) })
1418 ] }),
1419 selectedEvent && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1 italic", children: selectedEvent.description })
1420 ] }),
1421 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1422 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Recipient", "yatra") }),
1423 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
1424 /* @__PURE__ */ jsxRuntimeExports.jsxs(
1425 "label",
1426 {
1427 className: `flex-1 flex items-center gap-2 px-3 py-2 rounded-lg border cursor-pointer ${recipient === "customer" ? "border-blue-300 bg-blue-50 dark:border-blue-700 dark:bg-blue-900/20" : "border-gray-300 dark:border-gray-600"} ${isSystem ? "opacity-60 cursor-not-allowed" : ""}`,
1428 children: [
1429 /* @__PURE__ */ jsxRuntimeExports.jsx(
1430 "input",
1431 {
1432 type: "radio",
1433 checked: recipient === "customer",
1434 onChange: () => !isSystem && setRecipient("customer"),
1435 disabled: isSystem
1436 }
1437 ),
1438 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleUser, { className: "h-4 w-4 text-blue-600" }),
1439 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1440 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium", children: __("Customer", "yatra") }),
1441 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: __("Sends to the booking customer", "yatra") })
1442 ] })
1443 ]
1444 }
1445 ),
1446 /* @__PURE__ */ jsxRuntimeExports.jsxs(
1447 "label",
1448 {
1449 className: `flex-1 flex items-center gap-2 px-3 py-2 rounded-lg border cursor-pointer ${recipient === "admin" ? "border-orange-300 bg-orange-50 dark:border-orange-700 dark:bg-orange-900/20" : "border-gray-300 dark:border-gray-600"} ${isSystem ? "opacity-60 cursor-not-allowed" : ""}`,
1450 children: [
1451 /* @__PURE__ */ jsxRuntimeExports.jsx(
1452 "input",
1453 {
1454 type: "radio",
1455 checked: recipient === "admin",
1456 onChange: () => !isSystem && setRecipient("admin"),
1457 disabled: isSystem
1458 }
1459 ),
1460 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleUser, { className: "h-4 w-4 text-orange-600" }),
1461 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1462 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium", children: __("Admin", "yatra") }),
1463 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: __(
1464 "Sends to the admin phone in Delivery settings",
1465 "yatra"
1466 ) })
1467 ] })
1468 ]
1469 }
1470 )
1471 ] }),
1472 isSystem && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 italic", children: __(
1473 "System templates can't change recipient type.",
1474 "yatra"
1475 ) })
1476 ] }),
1477 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
1478 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1479 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Meta template name", "yatra") }),
1480 /* @__PURE__ */ jsxRuntimeExports.jsx(
1481 Input,
1482 {
1483 value: metaTemplateName,
1484 onChange: (e) => setMetaTemplateName(e.target.value),
1485 readOnly: isSystem,
1486 className: isSystem ? "bg-gray-50 dark:bg-gray-900/40" : "",
1487 placeholder: "yatra_booking_confirmed"
1488 }
1489 )
1490 ] }),
1491 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1492 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Language code", "yatra") }),
1493 /* @__PURE__ */ jsxRuntimeExports.jsx(
1494 Input,
1495 {
1496 value: languageCode,
1497 onChange: (e) => setLanguageCode(e.target.value),
1498 readOnly: isSystem,
1499 className: isSystem ? "bg-gray-50 dark:bg-gray-900/40" : "",
1500 placeholder: "en_US"
1501 }
1502 )
1503 ] })
1504 ] }),
1505 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1506 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300", children: __("Message body", "yatra") }),
1507 /* @__PURE__ */ jsxRuntimeExports.jsx(
1508 "textarea",
1509 {
1510 ref: bodyRef,
1511 value: body,
1512 onChange: (e) => setBody(e.target.value),
1513 rows: 12,
1514 readOnly: isSystem,
1515 className: `w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg font-mono text-sm text-gray-900 dark:text-white ${isSystem ? "bg-gray-50 dark:bg-gray-900/40" : "bg-white dark:bg-gray-800"}`,
1516 placeholder: __(
1517 "Use {{variable}} syntax — variables come from the trigger event.",
1518 "yatra"
1519 )
1520 }
1521 ),
1522 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: isSystem ? __(
1523 "System template body is locked. Create a custom template to author your own copy for this event.",
1524 "yatra"
1525 ) : __(
1526 "Click a variable on the right to copy, double-click to insert at the cursor.",
1527 "yatra"
1528 ) })
1529 ] })
1530 ] })
1531 ] }) }),
1532 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
1533 /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
1534 /* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "flex items-center gap-2", children: [
1535 /* @__PURE__ */ jsxRuntimeExports.jsx(Code, { className: "w-5 h-5 text-blue-500" }),
1536 __("Available Variables", "yatra")
1537 ] }) }),
1538 /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "space-y-3", children: !selectedEvent ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: __(
1539 "Pick a Trigger Event above to see the variables you can use here.",
1540 "yatra"
1541 ) }) : variables.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: __("This event has no variables.", "yatra") }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1542 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: __(
1543 "Click to copy, double-click to insert at cursor",
1544 "yatra"
1545 ) }),
1546 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1", children: variables.map((v) => {
1547 const placeholder = `{{${v.key}}}`;
1548 return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1549 "button",
1550 {
1551 type: "button",
1552 onClick: () => copyVariable(placeholder),
1553 onDoubleClick: () => insertVariable(placeholder),
1554 className: "w-full text-left px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors group",
1555 children: [
1556 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
1557 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-xs text-blue-600 dark:text-blue-400", children: placeholder }),
1558 copiedVar === placeholder ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "w-3 h-3 text-green-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "w-3 h-3 text-gray-400 opacity-0 group-hover:opacity-100" })
1559 ] }),
1560 v.description && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5", children: v.description })
1561 ]
1562 },
1563 v.key
1564 );
1565 }) })
1566 ] }) })
1567 ] }),
1568 !isCreate && /* @__PURE__ */ jsxRuntimeExports.jsx(
1569 TemplateTestSendCard,
1570 {
1571 templateId: existing.id,
1572 recipient
1573 }
1574 )
1575 ] })
1576 ] })
1577 ] });
1578 };
1579 const TemplateTestSendCard = ({ templateId, recipient }) => {
1580 const { showToast } = useToast();
1581 const [phone, setPhone] = reactExports.useState("");
1582 const [sending, setSending] = reactExports.useState(false);
1583 const [result, setResult] = reactExports.useState(
1584 null
1585 );
1586 const run = async () => {
1587 if (recipient === "customer" && phone.trim() === "") {
1588 showToast(__("Enter a destination phone number.", "yatra"), "error");
1589 return;
1590 }
1591 setSending(true);
1592 setResult(null);
1593 try {
1594 const res = await whatsappApi.testSendTemplate(templateId, {
1595 phone: phone.trim()
1596 });
1597 setResult({
1598 ok: res.ok,
1599 message: res.ok ? __("Sent — check your WhatsApp.", "yatra") + (res.provider_message_id ? ` (id: ${res.provider_message_id})` : "") : res.error || __("Send failed.", "yatra")
1600 });
1601 } catch (e) {
1602 setResult({ ok: false, message: extractError(e) });
1603 } finally {
1604 setSending(false);
1605 }
1606 };
1607 return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
1608 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
1609 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "flex items-center gap-2", children: [
1610 /* @__PURE__ */ jsxRuntimeExports.jsx(Send, { className: "h-5 w-5 text-blue-500" }),
1611 __("Send test message", "yatra")
1612 ] }),
1613 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: recipient === "admin" ? __(
1614 "Admin-recipient templates send to the admin phone in Delivery settings.",
1615 "yatra"
1616 ) : __("Sends this template to the phone you enter below.", "yatra") })
1617 ] }),
1618 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-3", children: [
1619 recipient === "customer" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1620 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Phone (E.164)", "yatra") }),
1621 /* @__PURE__ */ jsxRuntimeExports.jsx(
1622 Input,
1623 {
1624 value: phone,
1625 placeholder: "+9779800000000",
1626 onChange: (e) => setPhone(e.target.value)
1627 }
1628 )
1629 ] }),
1630 /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { onClick: run, disabled: sending, className: "w-full", children: [
1631 sending ? /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Send, { className: "mr-1.5 h-4 w-4" }),
1632 __("Send test", "yatra")
1633 ] }),
1634 result && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1635 "div",
1636 {
1637 className: `flex items-start gap-2 rounded-md p-3 text-sm ${result.ok ? "bg-green-50 text-green-800 dark:bg-green-900/20 dark:text-green-200" : "bg-red-50 text-red-800 dark:bg-red-900/20 dark:text-red-200"}`,
1638 children: [
1639 result.ok ? /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "mt-0.5 h-4 w-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "mt-0.5 h-4 w-4" }),
1640 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: result.message })
1641 ]
1642 }
1643 )
1644 ] })
1645 ] });
1646 };
1647 const WidgetSection = ({ settings, onSave, saving }) => {
1648 const w = settings.widget;
1649 const [enabled, setEnabled] = reactExports.useState(w.enabled);
1650 const [contactPhone, setContactPhone] = reactExports.useState(w.contact_phone);
1651 const [prefilled, setPrefilled] = reactExports.useState(w.prefilled_message);
1652 const [displayOn, setDisplayOn] = reactExports.useState(
1653 w.display_on
1654 );
1655 const [position, setPosition] = reactExports.useState(
1656 w.position
1657 );
1658 const [label, setLabel] = reactExports.useState(w.label);
1659 const [showOnSidebar, setShowOnSidebar] = reactExports.useState(w.show_on_trip_sidebar);
1660 React.useEffect(() => {
1661 setEnabled(w.enabled);
1662 setContactPhone(w.contact_phone);
1663 setPrefilled(w.prefilled_message);
1664 setDisplayOn(w.display_on);
1665 setPosition(w.position);
1666 setLabel(w.label);
1667 setShowOnSidebar(w.show_on_trip_sidebar);
1668 }, [
1669 w.enabled,
1670 w.contact_phone,
1671 w.prefilled_message,
1672 w.display_on,
1673 w.position,
1674 w.label,
1675 w.show_on_trip_sidebar
1676 ]);
1677 const dirty = enabled !== w.enabled || contactPhone !== w.contact_phone || prefilled !== w.prefilled_message || displayOn !== w.display_on || position !== w.position || label !== w.label || showOnSidebar !== w.show_on_trip_sidebar;
1678 const save = () => onSave({
1679 widget: {
1680 enabled,
1681 contact_phone: contactPhone,
1682 prefilled_message: prefilled,
1683 display_on: displayOn,
1684 position,
1685 label,
1686 show_on_trip_sidebar: showOnSidebar
1687 }
1688 });
1689 const previewUrl = reactExports.useMemo(() => {
1690 const digits = contactPhone.replace(/[^0-9]/g, "");
1691 const msg = prefilled.replace(/\{\{trip_name\}\}/g, "Demo Trip");
1692 return digits ? `https://wa.me/${digits}?text=${encodeURIComponent(msg)}` : "";
1693 }, [contactPhone, prefilled]);
1694 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [
1695 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [
1696 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { className: "flex items-center gap-2", children: [
1697 /* @__PURE__ */ jsxRuntimeExports.jsx(MessageCircle, { className: "h-5 w-5 text-green-500" }),
1698 __("Public WhatsApp widget", "yatra")
1699 ] }),
1700 /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __(
1701 'Adds a "Chat on WhatsApp" affordance to your public site. Free for the operator — uses wa.me deep linking, no per-tap charges. Independent of the Cloud API credentials you configured under Delivery.',
1702 "yatra"
1703 ) })
1704 ] }),
1705 /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-5", children: [
1706 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-start gap-3 cursor-pointer", children: [
1707 /* @__PURE__ */ jsxRuntimeExports.jsx(
1708 "input",
1709 {
1710 type: "checkbox",
1711 checked: enabled,
1712 onChange: (e) => setEnabled(e.target.checked),
1713 className: "mt-1"
1714 }
1715 ),
1716 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1717 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-white", children: __("Enable widget", "yatra") }),
1718 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: __(
1719 "Off by default. When on, renders per the display rules below.",
1720 "yatra"
1721 ) })
1722 ] })
1723 ] }),
1724 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1725 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Contact phone (E.164)", "yatra") }),
1726 /* @__PURE__ */ jsxRuntimeExports.jsx(
1727 Input,
1728 {
1729 value: contactPhone,
1730 placeholder: "+9779800000000",
1731 onChange: (e) => setContactPhone(e.target.value)
1732 }
1733 ),
1734 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: __(
1735 "Visitors land on a chat with THIS number. Can be any WhatsApp-enabled phone — doesn't have to be the Cloud-API phone you use for outbound automation.",
1736 "yatra"
1737 ) })
1738 ] }),
1739 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1740 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Pre-filled message", "yatra") }),
1741 /* @__PURE__ */ jsxRuntimeExports.jsx(
1742 "textarea",
1743 {
1744 className: "w-full rounded-md border border-gray-300 bg-white p-2 text-sm dark:border-gray-600 dark:bg-gray-800",
1745 rows: 3,
1746 value: prefilled,
1747 onChange: (e) => setPrefilled(e.target.value),
1748 placeholder: __(
1749 "Hi! I'd like to know more about {{trip_name}}.",
1750 "yatra"
1751 )
1752 }
1753 ),
1754 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: __(
1755 "Use {{trip_name}} to insert the current trip's name when the widget renders on a single-trip page. Replaced with the actual title at render time.",
1756 "yatra"
1757 ) })
1758 ] }),
1759 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
1760 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1761 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Show on", "yatra") }),
1762 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
1763 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer", children: [
1764 /* @__PURE__ */ jsxRuntimeExports.jsx(
1765 "input",
1766 {
1767 type: "radio",
1768 checked: displayOn === "trips_only",
1769 onChange: () => setDisplayOn("trips_only")
1770 }
1771 ),
1772 __("Single trip pages only", "yatra")
1773 ] }),
1774 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer", children: [
1775 /* @__PURE__ */ jsxRuntimeExports.jsx(
1776 "input",
1777 {
1778 type: "radio",
1779 checked: displayOn === "all",
1780 onChange: () => setDisplayOn("all")
1781 }
1782 ),
1783 __("Every frontend page", "yatra")
1784 ] })
1785 ] })
1786 ] }),
1787 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1788 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Position", "yatra") }),
1789 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
1790 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer", children: [
1791 /* @__PURE__ */ jsxRuntimeExports.jsx(
1792 "input",
1793 {
1794 type: "radio",
1795 checked: position === "bottom-right",
1796 onChange: () => setPosition("bottom-right")
1797 }
1798 ),
1799 __("Bottom right", "yatra")
1800 ] }),
1801 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer", children: [
1802 /* @__PURE__ */ jsxRuntimeExports.jsx(
1803 "input",
1804 {
1805 type: "radio",
1806 checked: position === "bottom-left",
1807 onChange: () => setPosition("bottom-left")
1808 }
1809 ),
1810 __("Bottom left", "yatra")
1811 ] })
1812 ] })
1813 ] })
1814 ] }),
1815 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1816 /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: __("Button label", "yatra") }),
1817 /* @__PURE__ */ jsxRuntimeExports.jsx(
1818 Input,
1819 {
1820 value: label,
1821 placeholder: __("Chat on WhatsApp", "yatra"),
1822 onChange: (e) => setLabel(e.target.value)
1823 }
1824 ),
1825 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: __("Leave blank to show only the WhatsApp icon.", "yatra") })
1826 ] }),
1827 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-start gap-3 cursor-pointer", children: [
1828 /* @__PURE__ */ jsxRuntimeExports.jsx(
1829 "input",
1830 {
1831 type: "checkbox",
1832 checked: showOnSidebar,
1833 onChange: (e) => setShowOnSidebar(e.target.checked),
1834 className: "mt-1"
1835 }
1836 ),
1837 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1838 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-white", children: __(
1839 "Also show a full-width CTA in the single-trip sidebar",
1840 "yatra"
1841 ) }),
1842 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: __(
1843 'Renders an "Ask about this trip on WhatsApp" button next to the booking form, separate from the floating button.',
1844 "yatra"
1845 ) })
1846 ] })
1847 ] }),
1848 previewUrl && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-md border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800/40", children: [
1849 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1 text-xs font-medium text-gray-600 dark:text-gray-300", children: __("Preview URL the button will open", "yatra") }),
1850 /* @__PURE__ */ jsxRuntimeExports.jsx(
1851 "a",
1852 {
1853 href: previewUrl,
1854 target: "_blank",
1855 rel: "noopener noreferrer",
1856 className: "text-xs text-blue-600 break-all hover:underline dark:text-blue-400",
1857 children: previewUrl
1858 }
1859 )
1860 ] }),
1861 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-blue-200 bg-blue-50/60 p-3 dark:border-blue-700 dark:bg-blue-900/20", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2", children: [
1862 /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { className: "mt-0.5 h-4 w-4 shrink-0 text-blue-600 dark:text-blue-400" }),
1863 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-blue-900 dark:text-blue-200", children: __(
1864 'Drop the button anywhere using the shortcode [yatra_whatsapp_button label="Chat with us"]. Supports label, message, and trip_name attributes.',
1865 "yatra"
1866 ) })
1867 ] }) })
1868 ] }),
1869 dirty && /* @__PURE__ */ jsxRuntimeExports.jsx(CardFooter, { className: "flex justify-end border-t border-gray-100 pt-4 dark:border-gray-700/50", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: save, disabled: saving, children: saving ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1870 /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-1.5 h-4 w-4 animate-spin" }),
1871 __("Saving…", "yatra")
1872 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1873 /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "mr-1.5 h-4 w-4" }),
1874 __("Save widget settings", "yatra")
1875 ] }) }) })
1876 ] }) });
1877 };
1878 const LogsList = () => {
1879 const [page, setPage] = reactExports.useState(1);
1880 const [statusFilter, setStatusFilter] = reactExports.useState("");
1881 const [phoneFilter, setPhoneFilter] = reactExports.useState("");
1882 const [phoneFilterApplied, setPhoneFilterApplied] = reactExports.useState("");
1883 const perPage = 20;
1884 React.useEffect(() => {
1885 const t = window.setTimeout(
1886 () => setPhoneFilterApplied(phoneFilter.trim()),
1887 350
1888 );
1889 return () => window.clearTimeout(t);
1890 }, [phoneFilter]);
1891 React.useEffect(() => {
1892 setPage(1);
1893 }, [statusFilter, phoneFilterApplied]);
1894 const { data, isLoading } = useQuery({
1895 queryKey: [
1896 "whatsapp-messages",
1897 page,
1898 perPage,
1899 statusFilter,
1900 phoneFilterApplied
1901 ],
1902 queryFn: () => whatsappApi.listMessages({
1903 page,
1904 per_page: perPage,
1905 ...statusFilter ? { status: statusFilter } : {},
1906 ...phoneFilterApplied ? { phone: phoneFilterApplied } : {}
1907 }),
1908 refetchInterval: 15e3,
1909 placeholderData: (prev) => prev
1910 // smoother pagination — keep last page visible while loading
1911 });
1912 const rows = (data == null ? void 0 : data.data) ?? [];
1913 const totalItems = (data == null ? void 0 : data.total) ?? 0;
1914 const totalPages = Math.max(1, Math.ceil(totalItems / perPage));
1915 if (isLoading) {
1916 return /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "p-12 flex justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-6 w-6 animate-spin text-gray-400" }) }) });
1917 }
1918 const hasFilters = !!(statusFilter || phoneFilter);
1919 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
1920 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1921 /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2", children: [
1922 /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "w-5 h-5 text-blue-500" }),
1923 __("Message logs", "yatra")
1924 ] }),
1925 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: __(
1926 "Every outbound send and inbound reply, filterable by status or phone. Updated every 15 seconds.",
1927 "yatra"
1928 ) })
1929 ] }),
1930 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "p-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col lg:flex-row lg:items-center gap-2", children: [
1931 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1932 Input,
1933 {
1934 type: "text",
1935 value: phoneFilter,
1936 onChange: (e) => setPhoneFilter(e.target.value),
1937 placeholder: __("Search by phone…", "yatra"),
1938 "aria-label": __("Search by phone", "yatra"),
1939 className: "w-full"
1940 }
1941 ) }),
1942 /* @__PURE__ */ jsxRuntimeExports.jsxs(
1943 Select,
1944 {
1945 value: statusFilter,
1946 onChange: (e) => setStatusFilter(e.target.value),
1947 "aria-label": __("Filter by status", "yatra"),
1948 className: "w-full lg:w-48 lg:flex-none",
1949 children: [
1950 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: __("All statuses", "yatra") }),
1951 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "queued", children: __("Queued", "yatra") }),
1952 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "sent", children: __("Sent", "yatra") }),
1953 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "delivered", children: __("Delivered", "yatra") }),
1954 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "read", children: __("Read", "yatra") }),
1955 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "failed", children: __("Failed", "yatra") }),
1956 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "received", children: __("Received (inbound)", "yatra") })
1957 ]
1958 }
1959 ),
1960 hasFilters && /* @__PURE__ */ jsxRuntimeExports.jsx(
1961 Button,
1962 {
1963 type: "button",
1964 variant: "outline",
1965 onClick: () => {
1966 setStatusFilter("");
1967 setPhoneFilter("");
1968 },
1969 className: "w-full lg:w-auto",
1970 children: __("Reset", "yatra")
1971 }
1972 )
1973 ] }) }) }),
1974 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "p-0", children: [
1975 rows.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-8 text-center text-gray-500 dark:text-gray-400", children: totalItems === 0 && !statusFilter && !phoneFilterApplied ? __("No messages sent yet.", "yatra") : __("No messages match your filters.", "yatra") }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(Table, { children: [
1976 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHeader, { className: "bg-gray-50 dark:bg-gray-800/50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(TableRow, { children: [
1977 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHead, { className: "w-28", children: __("Direction", "yatra") }),
1978 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHead, { className: "w-44", children: __("Phone", "yatra") }),
1979 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHead, { children: __("Body / Error", "yatra") }),
1980 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHead, { className: "w-48", children: __("Template", "yatra") }),
1981 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHead, { className: "w-28", children: __("Status", "yatra") }),
1982 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHead, { className: "w-44 whitespace-nowrap", children: __("Sent", "yatra") })
1983 ] }) }),
1984 /* @__PURE__ */ jsxRuntimeExports.jsx(TableBody, { children: rows.map((row) => /* @__PURE__ */ jsxRuntimeExports.jsxs(TableRow, { children: [
1985 /* @__PURE__ */ jsxRuntimeExports.jsx(TableCell, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1986 Badge,
1987 {
1988 className: row.direction === "inbound" ? "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
1989 children: row.direction
1990 }
1991 ) }),
1992 /* @__PURE__ */ jsxRuntimeExports.jsx(TableCell, { className: "font-mono text-gray-900 dark:text-white whitespace-nowrap", children: row.phone }),
1993 /* @__PURE__ */ jsxRuntimeExports.jsx(TableCell, { className: "text-gray-700 dark:text-gray-200", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1994 "div",
1995 {
1996 className: "max-w-md truncate",
1997 title: row.error_message || row.body || "",
1998 children: row.error_message || row.body || "—"
1999 }
2000 ) }),
2001 /* @__PURE__ */ jsxRuntimeExports.jsx(TableCell, { className: "text-gray-500 dark:text-gray-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2002 "span",
2003 {
2004 className: "truncate block max-w-[12rem]",
2005 title: row.template_key || "",
2006 children: row.template_key || "—"
2007 }
2008 ) }),
2009 /* @__PURE__ */ jsxRuntimeExports.jsx(TableCell, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2010 Badge,
2011 {
2012 className: row.status === "sent" || row.status === "delivered" || row.status === "read" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : row.status === "failed" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
2013 children: row.status
2014 }
2015 ) }),
2016 /* @__PURE__ */ jsxRuntimeExports.jsx(TableCell, { className: "text-gray-500 dark:text-gray-400 whitespace-nowrap", children: row.created_at ? new Date(row.created_at).toLocaleString() : "—" })
2017 ] }, row.id)) })
2018 ] }),
2019 rows.length > 0 && totalPages > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t border-gray-200 px-4 py-3 dark:border-gray-700", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2020 Pagination,
2021 {
2022 currentPage: page,
2023 totalPages,
2024 totalItems,
2025 itemsPerPage: perPage,
2026 onPageChange: setPage,
2027 itemName: __("messages", "yatra")
2028 }
2029 ) })
2030 ] }) })
2031 ] });
2032 };
2033 const OptInsList = () => {
2034 const [search, setSearch] = reactExports.useState("");
2035 const [filter, setFilter] = reactExports.useState("all");
2036 const { data, isLoading } = useQuery({
2037 queryKey: ["whatsapp-opt-ins"],
2038 queryFn: () => whatsappApi.listOptIns(),
2039 staleTime: 30 * 1e3
2040 });
2041 const rows = (data == null ? void 0 : data.data) ?? [];
2042 const filtered = reactExports.useMemo(() => {
2043 return rows.filter((r) => {
2044 if (filter === "in" && !r.opted_in) return false;
2045 if (filter === "out" && r.opted_in) return false;
2046 if (search.trim() !== "") {
2047 const needle = search.trim().toLowerCase();
2048 if (!r.phone.toLowerCase().includes(needle) && !r.source.toLowerCase().includes(needle))
2049 return false;
2050 }
2051 return true;
2052 });
2053 }, [rows, filter, search]);
2054 const inCount = reactExports.useMemo(() => rows.filter((r) => r.opted_in).length, [rows]);
2055 const outCount = rows.length - inCount;
2056 const exportCsv = () => {
2057 const escape = (v) => '"' + v.replace(/"/g, '""') + '"';
2058 const lines = [
2059 ["phone", "opted_in", "source", "updated_at"].map(escape).join(","),
2060 ...filtered.map(
2061 (r) => [r.phone, r.opted_in ? "yes" : "no", r.source, r.updated_at].map((v) => escape(String(v ?? ""))).join(",")
2062 )
2063 ];
2064 const csv = lines.join("\r\n");
2065 const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
2066 const url = URL.createObjectURL(blob);
2067 const a = document.createElement("a");
2068 a.href = url;
2069 a.download = `whatsapp-opt-ins-${todayYmd()}.csv`;
2070 document.body.appendChild(a);
2071 a.click();
2072 document.body.removeChild(a);
2073 URL.revokeObjectURL(url);
2074 };
2075 if (isLoading) {
2076 return /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "p-12 flex justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-6 w-6 animate-spin text-gray-400" }) }) });
2077 }
2078 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
2079 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
2080 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
2081 /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2", children: [
2082 /* @__PURE__ */ jsxRuntimeExports.jsx(Users, { className: "w-5 h-5 text-blue-500" }),
2083 __("Opt-in audit", "yatra")
2084 ] }),
2085 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: __(
2086 "Every phone number that's opted into WhatsApp messaging, plus those that opted out via inbound STOP keyword. Export this list for GDPR / privacy requests.",
2087 "yatra"
2088 ) })
2089 ] }),
2090 /* @__PURE__ */ jsxRuntimeExports.jsxs(
2091 Button,
2092 {
2093 type: "button",
2094 variant: "outline",
2095 onClick: exportCsv,
2096 disabled: filtered.length === 0,
2097 children: [
2098 /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "mr-1.5 h-4 w-4" }),
2099 __("Export CSV", "yatra")
2100 ]
2101 }
2102 )
2103 ] }),
2104 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "p-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col lg:flex-row lg:items-center gap-2", children: [
2105 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2106 Input,
2107 {
2108 type: "text",
2109 value: search,
2110 onChange: (e) => setSearch(e.target.value),
2111 placeholder: __("Search by phone or source…", "yatra"),
2112 "aria-label": __("Search opt-ins", "yatra"),
2113 className: "w-full"
2114 }
2115 ) }),
2116 /* @__PURE__ */ jsxRuntimeExports.jsxs(
2117 Select,
2118 {
2119 value: filter,
2120 onChange: (e) => setFilter(e.target.value),
2121 "aria-label": __("Filter opt-in status", "yatra"),
2122 className: "w-full lg:w-56 lg:flex-none",
2123 children: [
2124 /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: "all", children: [
2125 __("All", "yatra"),
2126 " (",
2127 rows.length,
2128 ")"
2129 ] }),
2130 /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: "in", children: [
2131 __("Opted in", "yatra"),
2132 " (",
2133 inCount,
2134 ")"
2135 ] }),
2136 /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: "out", children: [
2137 __("Opted out", "yatra"),
2138 " (",
2139 outCount,
2140 ")"
2141 ] })
2142 ]
2143 }
2144 ),
2145 (search || filter !== "all") && /* @__PURE__ */ jsxRuntimeExports.jsx(
2146 Button,
2147 {
2148 type: "button",
2149 variant: "outline",
2150 onClick: () => {
2151 setSearch("");
2152 setFilter("all");
2153 },
2154 className: "w-full lg:w-auto",
2155 children: __("Reset", "yatra")
2156 }
2157 )
2158 ] }) }) }),
2159 /* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "p-0", children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-8 text-center text-gray-500 dark:text-gray-400", children: rows.length === 0 ? __(
2160 "No opt-in records yet. Records appear here when customers tick the WhatsApp opt-in on a booking form, or reply STOP to opt out.",
2161 "yatra"
2162 ) : __("No records match your search.", "yatra") }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(Table, { children: [
2163 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHeader, { className: "bg-gray-50 dark:bg-gray-800/50", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(TableRow, { children: [
2164 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHead, { className: "w-44", children: __("Phone", "yatra") }),
2165 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHead, { className: "w-32", children: __("Status", "yatra") }),
2166 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHead, { children: __("Source", "yatra") }),
2167 /* @__PURE__ */ jsxRuntimeExports.jsx(TableHead, { className: "w-44 whitespace-nowrap", children: __("Updated", "yatra") })
2168 ] }) }),
2169 /* @__PURE__ */ jsxRuntimeExports.jsx(TableBody, { children: filtered.map((row) => /* @__PURE__ */ jsxRuntimeExports.jsxs(TableRow, { children: [
2170 /* @__PURE__ */ jsxRuntimeExports.jsx(TableCell, { className: "font-mono text-gray-900 dark:text-white whitespace-nowrap", children: row.phone }),
2171 /* @__PURE__ */ jsxRuntimeExports.jsx(TableCell, { children: row.opted_in ? /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { className: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400", children: [
2172 /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "mr-1 h-3 w-3" }),
2173 __("Opted in", "yatra")
2174 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { className: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-300", children: [
2175 /* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "mr-1 h-3 w-3" }),
2176 __("Opted out", "yatra")
2177 ] }) }),
2178 /* @__PURE__ */ jsxRuntimeExports.jsx(TableCell, { className: "text-gray-500 dark:text-gray-400", children: row.source || "—" }),
2179 /* @__PURE__ */ jsxRuntimeExports.jsx(TableCell, { className: "text-gray-500 dark:text-gray-400 whitespace-nowrap", children: row.updated_at })
2180 ] }, row.phone)) })
2181 ] }) }) })
2182 ] });
2183 };
2184 function extractError(e) {
2185 var _a;
2186 if (!e) return "Request failed.";
2187 const data = ((_a = e == null ? void 0 : e.response) == null ? void 0 : _a.data) ?? (e == null ? void 0 : e.data) ?? null;
2188 if (data && typeof data === "object" && typeof data.message === "string") {
2189 return data.message;
2190 }
2191 return (e == null ? void 0 : e.message) || "Request failed.";
2192 }
2193 export {
2194 Whatsapp as default
2195 };
2196 //# sourceMappingURL=Whatsapp.js.map
2197