@fayz-ai/plugin-automations 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,73 @@
1
- import React2 from 'react';
2
- import { startEventLog, getSupabaseClientOptional, getMessageDefaults, usePluginRuntimeOptional, resolveMessages, resolveMessage, renderMessageTemplate, loadEventRuns } from '@fayz-ai/core';
3
- import { resolveFayzAgentConnection, registerRailPage, getFayzSupabaseClientOptional, useConnectors, useAgentSurface, AskAIButton, SettingsTabs, adminNavigateTo, RightRailPage, useOrganizationStore, connectorRoute, ConnectorMark, ConnectorDeliveries } from '@fayz-ai/admin';
1
+ import * as React4 from 'react';
2
+ import React4__default from 'react';
3
+ import { getSupabaseClientOptional, startEventLog, getMessageDefaults, normalizeMessageActions, usePluginRuntimeOptional, resolveMessages, resolveMessage, renderMessageTemplate, loadEventRuns, getActiveTenantId } from '@fayz-ai/core';
4
+ import { resolveFayzAgentConnection, registerRailPage, getFayzSupabaseClientOptional, useConnectors, useAgentSurface, PageActions, askAI, SettingsTabs, adminNavigateTo, RightRailPage, AskAIButton, useOrganizationStore, connectorRoute, ConnectorMark, ConnectorDeliveries } from '@fayz-ai/admin';
4
5
  import * as LucideIcons from 'lucide-react';
5
- import { Zap, Receipt, Megaphone, MessageSquare, Smartphone, MessageCircle, Mail, ChevronRight, RotateCcw, Clock, Monitor, RefreshCw, History, AlertTriangle, CircleCheck, CircleSlash } from 'lucide-react';
6
- import { PageHeaderActions, PageTabs, Card, Switch, SubpageHeader, Button, Badge, Hint, HeaderTabs, toast } from '@fayz-ai/ui';
6
+ import { WandSparkles, Zap, Receipt, Megaphone, MessageSquare, Smartphone, Mail, ChevronRight, RotateCcw, Check, Clock, Monitor, RefreshCw, History, Send, AlertTriangle, CircleCheck, CircleSlash } from 'lucide-react';
7
+ import { PageHeaderActions, PageTabs, Card, WhatsAppIcon, Switch, SubpageHeader, Button, Badge, Hint, HeaderTabs, toast, PhoneField } from '@fayz-ai/ui';
7
8
  import { createFayzAutomationsClient, FayzAutomationsError } from '@fayz-ai/sdk/automations';
8
9
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
10
 
10
11
  // src/index.ts
12
+ var PREFLIGHT_FUNCTION = "tyxter-payment-preflight";
13
+ async function readReadiness() {
14
+ const supabase = getSupabaseClientOptional();
15
+ const tenantId = getActiveTenantId();
16
+ if (!supabase || !tenantId) return null;
17
+ const { data, error } = await supabase.functions.invoke(PREFLIGHT_FUNCTION, {
18
+ body: { action: "status", tenantId }
19
+ });
20
+ if (error) return null;
21
+ const payments = data?.payments;
22
+ return payments ?? null;
23
+ }
24
+ function canOfferPayment(readiness) {
25
+ return Boolean(readiness?.ready) && readiness?.mode !== null && readiness?.mode !== void 0;
26
+ }
27
+ function usePaymentReadiness() {
28
+ const [readiness, setReadiness] = React4.useState(null);
29
+ const [loading, setLoading] = React4.useState(true);
30
+ React4.useEffect(() => {
31
+ let cancelled = false;
32
+ void readReadiness().then((value) => {
33
+ if (!cancelled) setReadiness(value);
34
+ }).catch(() => {
35
+ }).finally(() => {
36
+ if (!cancelled) setLoading(false);
37
+ });
38
+ return () => {
39
+ cancelled = true;
40
+ };
41
+ }, []);
42
+ return { readiness, loading };
43
+ }
44
+ var SEND_FUNCTION = "messaging-send";
45
+ function readFunctionError(error, data) {
46
+ const payload = data ?? null;
47
+ if (payload?.error) return { ok: false, error: payload.error, message: payload.message };
48
+ const context = error?.context;
49
+ if (context?.error) return { ok: false, error: context.error, message: context.message };
50
+ return { ok: false, error: "send_failed", message: error instanceof Error ? error.message : void 0 };
51
+ }
52
+ async function sendTestMessage(input) {
53
+ const supabase = getSupabaseClientOptional();
54
+ const tenantId = getActiveTenantId();
55
+ if (!supabase || !tenantId) return { ok: false, error: "not_available" };
56
+ const { data, error } = await supabase.functions.invoke(SEND_FUNCTION, {
57
+ body: {
58
+ test: true,
59
+ tenantId,
60
+ channel: input.channel,
61
+ eventKey: input.eventKey,
62
+ to: input.to,
63
+ variables: input.variables,
64
+ subject: { type: "test", id: crypto.randomUUID() },
65
+ ...input.template ? { template: input.template } : {}
66
+ }
67
+ });
68
+ if (error || data?.error) return readFunctionError(error, data);
69
+ return { ok: true };
70
+ }
11
71
  function useOrganizationStoreLike() {
12
72
  return useOrganizationStore((s) => s.currentOrg?.id);
13
73
  }
@@ -27,6 +87,11 @@ function toTemplate(automation) {
27
87
  // The automation name belongs to the platform, not a user rename: the
28
88
  // catalogue title still owns the screen.
29
89
  name: null,
90
+ // The platform's automation action carries a subject and a body and nothing
91
+ // else, so there is nowhere for a customised button to live yet. NULL, not
92
+ // [], because the catalogue default is what still applies — an empty list
93
+ // would read as "this tenant removed the buttons".
94
+ actions: null,
30
95
  updatedAt: automation.updatedAt
31
96
  };
32
97
  }
@@ -183,7 +248,7 @@ function when(iso) {
183
248
  return new Date(iso).toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
184
249
  }
185
250
  function RunRow({ run }) {
186
- const [open, setOpen] = React2.useState(false);
251
+ const [open, setOpen] = React4__default.useState(false);
187
252
  const cfg = STATUS[run.status] ?? STATUS.recorded;
188
253
  const { Icon } = cfg;
189
254
  return /* @__PURE__ */ jsxs("div", { className: "border-b border-border last:border-b-0", children: [
@@ -217,14 +282,14 @@ function RunRow({ run }) {
217
282
  ] });
218
283
  }
219
284
  function RunLog({ eventKey, tenantId, enabled }) {
220
- const [runs, setRuns] = React2.useState(null);
221
- const [loading, setLoading] = React2.useState(false);
222
- const load = React2.useCallback(async () => {
285
+ const [runs, setRuns] = React4__default.useState(null);
286
+ const [loading, setLoading] = React4__default.useState(false);
287
+ const load = React4__default.useCallback(async () => {
223
288
  setLoading(true);
224
289
  setRuns(await loadEventRuns(eventKey, { tenantId, limit: 20 }));
225
290
  setLoading(false);
226
291
  }, [eventKey, tenantId]);
227
- React2.useEffect(() => {
292
+ React4__default.useEffect(() => {
228
293
  void load();
229
294
  }, [load]);
230
295
  return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
@@ -260,9 +325,9 @@ function ExecutionRow({ execution }) {
260
325
  ] });
261
326
  }
262
327
  function FayzRunLog({ connection, eventKey, tenantId, enabled }) {
263
- const [executions, setExecutions] = React2.useState(null);
264
- const [loading, setLoading] = React2.useState(false);
265
- const load = React2.useCallback(async () => {
328
+ const [executions, setExecutions] = React4__default.useState(null);
329
+ const [loading, setLoading] = React4__default.useState(false);
330
+ const load = React4__default.useCallback(async () => {
266
331
  if (!tenantId) return;
267
332
  setLoading(true);
268
333
  try {
@@ -274,7 +339,7 @@ function FayzRunLog({ connection, eventKey, tenantId, enabled }) {
274
339
  setLoading(false);
275
340
  }
276
341
  }, [connection, eventKey, tenantId]);
277
- React2.useEffect(() => {
342
+ React4__default.useEffect(() => {
278
343
  void load();
279
344
  }, [load]);
280
345
  return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
@@ -292,6 +357,183 @@ function FayzRunLog({ connection, eventKey, tenantId, enabled }) {
292
357
  ] }) : /* @__PURE__ */ jsx("div", { children: executions.map((e) => /* @__PURE__ */ jsx(ExecutionRow, { execution: e }, e.id)) })
293
358
  ] });
294
359
  }
360
+
361
+ // src/views/whatsapp-preview-bg.ts
362
+ var WHATSAPP_PREVIEW_BG = "https://assets.fayz.ai/connectors/whatsapp-background.jpg";
363
+ var PROVIDER_TEMPLATES_TABLE = "plg_automations_provider_templates";
364
+ function rowToProviderTemplate(row) {
365
+ return {
366
+ id: row.id,
367
+ tenantId: row.tenant_id ?? null,
368
+ eventKey: row.event_key,
369
+ channel: row.channel ?? "whatsapp",
370
+ provider: row.provider ?? "tyxter",
371
+ templateName: row.template_name ?? "",
372
+ status: row.status ?? "draft",
373
+ rejectionReason: row.rejection_reason || null,
374
+ updatedAt: row.updated_at ?? row.created_at ?? null
375
+ };
376
+ }
377
+ async function loadProviderTemplates() {
378
+ const client = getSupabaseClientOptional();
379
+ if (!client) return [];
380
+ try {
381
+ const { data, error } = await client.from(PROVIDER_TEMPLATES_TABLE).select("*");
382
+ if (error) throw error;
383
+ return (data ?? []).map(rowToProviderTemplate);
384
+ } catch (err) {
385
+ console.warn("[automations] could not load provider template status", err);
386
+ return [];
387
+ }
388
+ }
389
+ function providerTemplateFor(rows, eventKey, channel) {
390
+ const matching = rows.filter((r) => r.eventKey === eventKey && r.channel === channel);
391
+ return {
392
+ own: matching.find((r) => r.tenantId !== null) ?? null,
393
+ fallback: matching.find((r) => r.tenantId === null) ?? null
394
+ };
395
+ }
396
+ function providerApprovalFor(rows, eventKey, channel) {
397
+ if (channel !== "whatsapp") return null;
398
+ const { own, fallback } = providerTemplateFor(rows, eventKey, channel);
399
+ const row = own ?? fallback;
400
+ if (!row) return null;
401
+ const fallbackApproved = fallback?.status === "approved";
402
+ switch (row.status) {
403
+ case "approved":
404
+ return { tone: "ok", label: "Aprovado pela Meta" };
405
+ case "rejected":
406
+ return {
407
+ tone: fallbackApproved && own ? "waiting" : "error",
408
+ label: fallbackApproved && own ? "Seu texto foi recusado" : "Recusado pela Meta",
409
+ detail: row.rejectionReason ? `Motivo: ${row.rejectionReason}` : "A Meta n\xE3o disse o motivo."
410
+ };
411
+ case "paused":
412
+ return {
413
+ tone: "error",
414
+ label: "Pausado pela Meta",
415
+ detail: row.rejectionReason ?? "Costuma ser excesso de marca\xE7\xF5es de spam pelos clientes."
416
+ };
417
+ case "disabled":
418
+ return { tone: "error", label: "Desativado pela Meta", detail: row.rejectionReason ?? void 0 };
419
+ case "submitted":
420
+ return {
421
+ tone: "waiting",
422
+ label: "Aguardando aprova\xE7\xE3o da Meta",
423
+ detail: fallbackApproved && own ? "At\xE9 l\xE1 a mensagem sai com o texto padr\xE3o." : "A revis\xE3o costuma levar algumas horas."
424
+ };
425
+ default:
426
+ return { tone: "waiting", label: "Ainda n\xE3o enviado para aprova\xE7\xE3o" };
427
+ }
428
+ }
429
+ var TONE_VARIANT = {
430
+ ok: "success",
431
+ waiting: "warning",
432
+ error: "destructive"
433
+ };
434
+ function ProviderTemplateBadge({ approval, showDetail }) {
435
+ if (!approval) return null;
436
+ if (approval.tone === "ok" && !showDetail) return null;
437
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex flex-wrap items-center gap-1.5", children: [
438
+ /* @__PURE__ */ jsx(Badge, { variant: TONE_VARIANT[approval.tone], children: approval.label }),
439
+ showDetail && approval.detail && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: approval.detail })
440
+ ] });
441
+ }
442
+ function useProviderTemplates() {
443
+ const [rows, setRows] = React4__default.useState([]);
444
+ React4__default.useEffect(() => {
445
+ let cancelled = false;
446
+ void loadProviderTemplates().then((data) => {
447
+ if (!cancelled) setRows(data);
448
+ });
449
+ return () => {
450
+ cancelled = true;
451
+ };
452
+ }, []);
453
+ return rows;
454
+ }
455
+ var ACTION_CATALOG = [
456
+ { id: "confirm", label: "Confirma", kind: "reply", hint: "O cliente confirma o hor\xE1rio no toque." },
457
+ { id: "cancel", label: "Cancela", kind: "reply", hint: "O cliente desmarca sem precisar ligar." },
458
+ { id: "pay", label: "Pagar", kind: "payment", hint: "O cliente recebe o Pix na conversa." }
459
+ ];
460
+ function nextActions(current, id, on) {
461
+ const kept = new Set(current.filter((a) => a.id !== id).map((a) => a.id));
462
+ if (on) kept.add(id);
463
+ const labels = new Map(current.map((a) => [a.id, a.label]));
464
+ return ACTION_CATALOG.filter((a) => kept.has(a.id)).map((a) => ({ id: a.id, label: labels.get(a.id) ?? a.label, kind: a.kind }));
465
+ }
466
+ var TEST_ERROR = {
467
+ template_not_approved: "O modelo ainda n\xE3o foi aprovado pela Meta \u2014 sincronize os modelos e tente de novo.",
468
+ no_sender: "Este app ainda n\xE3o tem um n\xFAmero de WhatsApp ativo. Veja Conversas \u2192 Tyxter.",
469
+ not_connected: "O WhatsApp ainda n\xE3o est\xE1 conectado neste app. Veja Conversas \u2192 Tyxter.",
470
+ contact_opted_out: "Este contato pediu para n\xE3o receber mensagens.",
471
+ invalid_recipient: "Esse destino n\xE3o parece v\xE1lido.",
472
+ not_a_member: "A sua sess\xE3o n\xE3o pertence a este neg\xF3cio.",
473
+ not_available: "O envio de teste n\xE3o est\xE1 dispon\xEDvel neste ambiente.",
474
+ email_not_configured: "O envio de e-mail n\xE3o est\xE1 configurado neste app.",
475
+ template_not_found: "Escreva a mensagem antes de testar.",
476
+ email_rejected: "O provedor de e-mail recusou a mensagem.",
477
+ email_credential_rejected: "A chave de e-mail do app foi recusada pelo provedor.",
478
+ email_rate_limited: "O provedor pediu uma pausa \u2014 tente de novo em instantes."
479
+ };
480
+ function TestSendStrip({ eventKey, channel, placeholders, template }) {
481
+ const isEmail = channel === "email";
482
+ const [recipient, setRecipient] = React4__default.useState("");
483
+ const [sending, setSending] = React4__default.useState(false);
484
+ const valid = isEmail ? /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipient.trim()) : recipient.replace(/\D/g, "").length >= 10;
485
+ const send = async () => {
486
+ setSending(true);
487
+ try {
488
+ const variables = Object.fromEntries(placeholders.map((p) => {
489
+ const key = p.replace(/[{}]/g, "").trim();
490
+ return [key, SAMPLE[key] ?? key];
491
+ }));
492
+ const result = await sendTestMessage({ eventKey, channel, to: recipient.trim(), variables, template });
493
+ if (result.ok) {
494
+ toast.success(isEmail ? "Teste enviado \u2014 confira a caixa de entrada desse e-mail" : "Teste enviado \u2014 confira o WhatsApp desse n\xFAmero");
495
+ } else {
496
+ toast.error(TEST_ERROR[result.error ?? ""] ?? result.message ?? "N\xE3o foi poss\xEDvel enviar o teste");
497
+ }
498
+ } finally {
499
+ setSending(false);
500
+ }
501
+ };
502
+ return (
503
+ // Sem moldura própria: o strip vive dentro do Card da aba Testar, e uma
504
+ // borda aqui era o anel duplicado de sempre.
505
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-end gap-2", children: [
506
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
507
+ /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: isEmail ? "E-mail que recebe o teste" : "N\xFAmero que recebe o teste" }),
508
+ isEmail ? /* @__PURE__ */ jsx(
509
+ "input",
510
+ {
511
+ type: "email",
512
+ value: recipient,
513
+ onChange: (e) => setRecipient(e.target.value),
514
+ placeholder: "voce@exemplo.com",
515
+ disabled: sending,
516
+ "aria-label": "E-mail que recebe o teste",
517
+ className: "w-full rounded-input border border-input bg-card px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
518
+ }
519
+ ) : /* @__PURE__ */ jsx(
520
+ PhoneField,
521
+ {
522
+ value: recipient,
523
+ onChange: setRecipient,
524
+ placeholder: "(21) 99999-9999",
525
+ disabled: sending,
526
+ "aria-label": "N\xFAmero que recebe o teste"
527
+ }
528
+ )
529
+ ] }),
530
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: () => void send(), disabled: !valid || sending, children: [
531
+ /* @__PURE__ */ jsx(Send, { className: "mr-2 h-3.5 w-3.5" }),
532
+ sending ? "Enviando\u2026" : "Enviar teste"
533
+ ] })
534
+ ] })
535
+ );
536
+ }
295
537
  var TABS = [
296
538
  { value: "preview", label: "Pr\xE9via" },
297
539
  { value: "code", label: "C\xF3digo" },
@@ -299,6 +541,7 @@ var TABS = [
299
541
  // é a mesma pergunta em dois tempos: o que vai sair, e o que saiu.
300
542
  { value: "runs", label: "Execu\xE7\xF5es" }
301
543
  ];
544
+ var TEST_TAB = { value: "test", label: "Testar" };
302
545
  var CHANNEL_LABEL = {
303
546
  email: "E-mail",
304
547
  whatsapp: "WhatsApp",
@@ -306,6 +549,13 @@ var CHANNEL_LABEL = {
306
549
  push: "Push",
307
550
  webchat: "Chat"
308
551
  };
552
+ var CHANNEL_ICON = {
553
+ email: Mail,
554
+ whatsapp: WhatsAppIcon,
555
+ sms: Smartphone,
556
+ push: MessageSquare,
557
+ webchat: MessageSquare
558
+ };
309
559
  var WRITE_MESSAGE_TOOL = {
310
560
  id: "automations.writeMessage",
311
561
  name: "writeAutomationMessage",
@@ -341,27 +591,25 @@ var SAMPLE = {
341
591
  appointment_date: "12/08/2026",
342
592
  appointment_time: "14:30"
343
593
  };
344
- function iconFor(name) {
345
- if (!name) return Zap;
346
- const icons = LucideIcons;
347
- return icons[name] ?? Zap;
348
- }
349
594
  function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations", embedded, onDone }) {
350
595
  const tenantId = useOrganizationStoreLike();
351
596
  const runtime = usePluginRuntimeOptional();
352
- const fayzConnection = React2.useMemo(() => resolveFayzAgentConnection(), []);
353
- const [message, setMessage] = React2.useState(void 0);
354
- const [tab, setTab] = React2.useState("preview");
355
- const [device, setDevice] = React2.useState("desktop");
356
- const [draft, setDraft] = React2.useState(null);
357
- const [saving, setSaving] = React2.useState(false);
358
- const back = React2.useCallback(
597
+ const fayzConnection = React4__default.useMemo(() => resolveFayzAgentConnection(), []);
598
+ const [message, setMessage] = React4__default.useState(void 0);
599
+ const [tab, setTab] = React4__default.useState("preview");
600
+ const [device, setDevice] = React4__default.useState("desktop");
601
+ const [draft, setDraft] = React4__default.useState(null);
602
+ const [labelDrafts, setLabelDrafts] = React4__default.useState({});
603
+ const [saving, setSaving] = React4__default.useState(false);
604
+ const providerTemplates = useProviderTemplates();
605
+ const { readiness, loading: readinessLoading } = usePaymentReadiness();
606
+ const back = React4__default.useCallback(
359
607
  () => onDone ? onDone() : adminNavigateTo(backTo),
360
608
  [onDone, backTo]
361
609
  );
362
- const surfaceRef = React2.useRef({ message, draft });
610
+ const surfaceRef = React4__default.useRef({ message, draft });
363
611
  surfaceRef.current = { message, draft };
364
- const agentSurface = React2.useMemo(() => ({
612
+ const agentSurface = React4__default.useMemo(() => ({
365
613
  id: "automations.message",
366
614
  pluginId: "automations",
367
615
  instructions: "The user is editing one automation message. `placeholders` in the surface state are the ONLY variables that resolve \u2014 copy them verbatim, braces included. Call writeAutomationMessage once with the whole text; the editor fills and the user reviews before saving.",
@@ -397,7 +645,7 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
397
645
  }]
398
646
  }), []);
399
647
  useAgentSurface(agentSurface);
400
- const reload = React2.useCallback(async () => {
648
+ const reload = React4__default.useCallback(async () => {
401
649
  if (!eventKey || !channel) {
402
650
  setMessage(null);
403
651
  return;
@@ -412,7 +660,7 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
412
660
  setMessage(found);
413
661
  if (found) setDraft({ subject: found.subject, body: found.body });
414
662
  }, [eventKey, channel, store, tenantId, runtime]);
415
- React2.useEffect(() => {
663
+ React4__default.useEffect(() => {
416
664
  void reload();
417
665
  }, [reload]);
418
666
  const header = embedded ? null : /* @__PURE__ */ jsx(
@@ -447,7 +695,8 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
447
695
  }
448
696
  const event = message.event;
449
697
  const fallback = { subject: message.override ? void 0 : message.subject, body: message.body };
450
- const Icon = iconFor(event.icon);
698
+ const ChannelIcon = CHANNEL_ICON[message.channel] ?? Zap;
699
+ const isWhatsapp = message.channel === "whatsapp";
451
700
  const current = draft ?? { subject: message.subject, body: message.body };
452
701
  const dirty = current.subject !== message.subject || current.body !== message.body;
453
702
  const hasSubject = message.channel === "email";
@@ -490,14 +739,27 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
490
739
  header,
491
740
  /* @__PURE__ */ jsxs("div", { className: embedded ? "space-y-3" : "flex flex-wrap items-start gap-3", children: [
492
741
  /* @__PURE__ */ jsxs("div", { className: embedded ? "flex items-start gap-3" : "contents", children: [
493
- /* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) }),
742
+ /* @__PURE__ */ jsx(
743
+ "span",
744
+ {
745
+ className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border ${isWhatsapp ? "border-[#25D366]/30 bg-[#25D366]/10 text-[#1faf55]" : "border-border text-muted-foreground"}`,
746
+ children: /* @__PURE__ */ jsx(ChannelIcon, { className: "h-4 w-4" })
747
+ }
748
+ ),
494
749
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
495
750
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
496
751
  /* @__PURE__ */ jsx(Badge, { variant: "outline", children: CHANNEL_LABEL[message.channel] }),
497
752
  /* @__PURE__ */ jsx(Badge, { variant: "outline", children: message.kind === "marketing" ? "Marketing" : "Transacional" }),
498
753
  message.customized && /* @__PURE__ */ jsx(Badge, { variant: "info", children: "Personalizado" })
499
754
  ] }),
500
- /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: event.description })
755
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: event.description }),
756
+ /* @__PURE__ */ jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsx(
757
+ ProviderTemplateBadge,
758
+ {
759
+ approval: providerApprovalFor(providerTemplates, event.name, message.channel),
760
+ showDetail: true
761
+ }
762
+ ) })
501
763
  ] })
502
764
  ] }),
503
765
  /* @__PURE__ */ jsxs("div", { className: embedded ? "flex items-center gap-2" : "flex shrink-0 flex-wrap items-center gap-2", children: [
@@ -545,11 +807,22 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
545
807
  }
546
808
  }
547
809
  ),
548
- /* @__PURE__ */ jsx(Button, { onClick: save, disabled: !dirty || saving, children: saving ? "Salvando\u2026" : dirty ? "Salvar" : "Salvo" })
810
+ dirty || saving ? /* @__PURE__ */ jsx(Button, { onClick: save, disabled: saving, children: saving ? "Salvando\u2026" : "Salvar" }) : /* @__PURE__ */ jsxs("span", { className: "inline-flex h-9 items-center gap-1 px-2 text-xs font-medium text-muted-foreground", children: [
811
+ /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
812
+ "Salvo"
813
+ ] })
549
814
  ] })
550
815
  ] })
551
816
  ] }),
552
- /* @__PURE__ */ jsx(HeaderTabs, { tabs: TABS, value: tab, onChange: setTab, "aria-label": "Visualiza\xE7\xE3o" }),
817
+ /* @__PURE__ */ jsx(
818
+ HeaderTabs,
819
+ {
820
+ tabs: isWhatsapp || message.channel === "email" ? [...TABS, TEST_TAB] : TABS,
821
+ value: tab,
822
+ onChange: setTab,
823
+ "aria-label": "Visualiza\xE7\xE3o"
824
+ }
825
+ ),
553
826
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2 border-b border-border pb-4 text-xs text-muted-foreground", children: [
554
827
  /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
555
828
  /* @__PURE__ */ jsx(Zap, { className: "h-3.5 w-3.5" }),
@@ -570,9 +843,9 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
570
843
  // é ela quem executa. Fora dele, o coletor local continua valendo.
571
844
  fayzConnection ? /* @__PURE__ */ jsx(FayzRunLog, { connection: fayzConnection, eventKey: event.name, tenantId, enabled: message.enabled }) : /* @__PURE__ */ jsx(RunLog, { eventKey: event.name, tenantId, enabled: message.enabled })
572
845
  ) : tab === "preview" ? /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
573
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
846
+ !embedded && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
574
847
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Pr\xE9via" }),
575
- /* @__PURE__ */ jsx("div", { className: embedded ? "hidden" : "flex items-center gap-1", children: ["desktop", "mobile"].map((d) => /* @__PURE__ */ jsx(
848
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: ["desktop", "mobile"].map((d) => /* @__PURE__ */ jsx(
576
849
  "button",
577
850
  {
578
851
  type: "button",
@@ -588,19 +861,58 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
588
861
  /* @__PURE__ */ jsx(
589
862
  "div",
590
863
  {
591
- className: `mx-auto rounded-card border border-border bg-card shadow-sm transition-[max-width] ${device === "mobile" ? "max-w-[380px]" : hasSubject ? "max-w-[600px]" : "max-w-[420px]"}`,
864
+ className: `mx-auto overflow-hidden rounded-card bg-card shadow-sm transition-[max-width] ${device === "mobile" ? "max-w-[380px]" : hasSubject ? "max-w-[600px]" : "max-w-[420px]"}`,
592
865
  children: hasSubject ? /* @__PURE__ */ jsxs(Fragment, { children: [
593
- /* @__PURE__ */ jsx("div", { className: "border-b border-border px-6 py-4 text-sm font-semibold", children: SAMPLE.store_name }),
594
- /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-b border-border px-6 py-3", children: [
595
- /* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: "Assunto" }),
596
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: previewSubject })
866
+ /* @__PURE__ */ jsxs("div", { "aria-hidden": true, className: "flex items-center gap-1.5 border-b border-border bg-muted/40 px-4 py-2.5", children: [
867
+ /* @__PURE__ */ jsx("span", { className: "h-3 w-3 rounded-full bg-[#ff5f57]" }),
868
+ /* @__PURE__ */ jsx("span", { className: "h-3 w-3 rounded-full bg-[#febc2e]" }),
869
+ /* @__PURE__ */ jsx("span", { className: "h-3 w-3 rounded-full bg-[#28c840]" })
870
+ ] }),
871
+ /* @__PURE__ */ jsxs("div", { className: "px-6 pb-4 pt-5", children: [
872
+ /* @__PURE__ */ jsx("p", { className: "text-base font-semibold leading-snug", children: previewSubject }),
873
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-3", children: [
874
+ /* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-primary/10 text-sm font-semibold text-primary", children: (SAMPLE.store_name ?? "S").charAt(0) }),
875
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
876
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold", children: SAMPLE.store_name }),
877
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "para mim" })
878
+ ] }),
879
+ /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 text-xs text-muted-foreground", children: "09:41" })
880
+ ] })
597
881
  ] }),
598
- /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap px-6 py-5 text-sm leading-relaxed", children: previewBody })
599
- ] }) : /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap rounded-2xl rounded-tl-sm bg-[#dcf8c6] px-3 py-2 text-sm leading-relaxed text-[#111b21]", children: previewBody }) })
882
+ /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap border-t border-border px-6 py-5 text-sm leading-relaxed", children: previewBody })
883
+ ] }) : /* @__PURE__ */ jsxs(
884
+ "div",
885
+ {
886
+ className: "p-4",
887
+ style: isWhatsapp ? { backgroundImage: `url(${WHATSAPP_PREVIEW_BG})`, backgroundSize: "cover", backgroundPosition: "center" } : void 0,
888
+ children: [
889
+ /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap rounded-2xl rounded-tl-sm bg-[#dcf8c6] px-3 py-2 text-sm leading-relaxed text-[#111b21] shadow-sm", children: previewBody }),
890
+ message.actions.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-1 space-y-1", children: message.actions.map((action) => /* @__PURE__ */ jsx(
891
+ "div",
892
+ {
893
+ className: "rounded-lg bg-white px-3 py-1.5 text-center text-sm font-medium text-[#1e7fd6] shadow-sm",
894
+ children: action.label
895
+ },
896
+ action.id
897
+ )) })
898
+ ]
899
+ }
900
+ )
600
901
  }
601
902
  ),
602
903
  /* @__PURE__ */ jsx("p", { className: "mt-4 text-center text-xs text-muted-foreground", children: "Dados de exemplo \u2014 nenhum registro real foi usado." })
603
904
  ] })
905
+ ] }) : tab === "test" ? /* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
906
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: isWhatsapp ? "Envio real, pelo mesmo caminho da automa\xE7\xE3o \u2014 modelo aprovado, n\xFAmero ativo e entrega de verdade, com os dados de exemplo." : "Envio real, pelo mesmo caminho da automa\xE7\xE3o \u2014 vai o texto que est\xE1 na tela, salvo ou n\xE3o, com os dados de exemplo." }),
907
+ /* @__PURE__ */ jsx(
908
+ TestSendStrip,
909
+ {
910
+ eventKey: event.name,
911
+ channel: message.channel,
912
+ placeholders: message.placeholders,
913
+ template: message.channel === "email" ? { subject: current.subject, body: current.body } : void 0
914
+ }
915
+ )
604
916
  ] }) : /* @__PURE__ */ jsxs(Card, { className: "space-y-4 p-4", children: [
605
917
  hasSubject && /* @__PURE__ */ jsxs("div", { children: [
606
918
  /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Assunto" }),
@@ -625,6 +937,65 @@ function AutomationDetailPage({ eventKey, channel, store, backTo = "/automations
625
937
  }
626
938
  )
627
939
  ] }),
940
+ message.channel === "whatsapp" && /* @__PURE__ */ jsxs("div", { children: [
941
+ /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: "Bot\xF5es \u2014 o cliente responde no toque, sem digitar" }),
942
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: ACTION_CATALOG.map((action) => {
943
+ const active = message.actions.find((a) => a.id === action.id);
944
+ const on = Boolean(active);
945
+ const gated = action.id === "pay" && !canOfferPayment(readiness);
946
+ const blocked = gated && !readinessLoading;
947
+ const labelValue = labelDrafts[action.id] ?? active?.label ?? action.label;
948
+ const commitLabel = () => {
949
+ const next = labelValue.trim();
950
+ setLabelDrafts((d) => {
951
+ const { [action.id]: _, ...rest } = d;
952
+ return rest;
953
+ });
954
+ if (!active || !next || next === active.label) return;
955
+ void patch({
956
+ eventKey: event.name,
957
+ channel: message.channel,
958
+ actions: message.actions.map((a) => a.id === action.id ? { ...a, label: next } : a)
959
+ });
960
+ };
961
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1", children: [
962
+ /* @__PURE__ */ jsx(
963
+ Switch,
964
+ {
965
+ size: "md",
966
+ checked: on,
967
+ disabled: saving || gated,
968
+ onCheckedChange: (next) => void patch({
969
+ eventKey: event.name,
970
+ channel: message.channel,
971
+ actions: nextActions(message.actions, action.id, next)
972
+ }),
973
+ label: on ? "" : action.label
974
+ }
975
+ ),
976
+ on && // O texto do botão como a cliente vê. 25 caracteres é o
977
+ // teto da Meta para QUICK_REPLY; mudar o texto de um
978
+ // modelo já aprovado dispara nova aprovação — a
979
+ // pastilha de status acima conta essa história.
980
+ /* @__PURE__ */ jsx(
981
+ "input",
982
+ {
983
+ value: labelValue,
984
+ maxLength: 25,
985
+ disabled: saving,
986
+ "aria-label": `Texto do bot\xE3o ${action.label}`,
987
+ onChange: (e) => setLabelDrafts((d) => ({ ...d, [action.id]: e.target.value })),
988
+ onBlur: commitLabel,
989
+ onKeyDown: (e) => {
990
+ if (e.key === "Enter") e.target.blur();
991
+ },
992
+ className: "w-36 rounded-input border border-input bg-card px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
993
+ }
994
+ ),
995
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: blocked ? "Para cobrar por aqui, configure a conta de pagamento em Conversas \u2192 Tyxter." : action.hint })
996
+ ] }, action.id);
997
+ }) })
998
+ ] }),
628
999
  /* @__PURE__ */ jsxs("div", { children: [
629
1000
  /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: "Campos deste evento \u2014 o envio substitui pelo dado real" }),
630
1001
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: message.placeholders.map((p) => /* @__PURE__ */ jsx("span", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-[11px] text-muted-foreground", children: p }, p)) })
@@ -696,7 +1067,7 @@ function AutomationCard({
696
1067
  group,
697
1068
  href
698
1069
  }) {
699
- const [open, setOpen] = React2.useState(false);
1070
+ const [open, setOpen] = React4__default.useState(false);
700
1071
  return /* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
701
1072
  /* @__PURE__ */ jsxs(
702
1073
  "div",
@@ -783,9 +1154,26 @@ var CONFIGURE_TOOL = {
783
1154
  required: ["event", "channel"]
784
1155
  }
785
1156
  };
786
- var CHANNEL_ICON = {
1157
+ var ASK_AI_ACTION = {
1158
+ label: "Criar com IA",
1159
+ hint: "O que revisar nas automa\xE7\xF5es\u2026",
1160
+ intro: {
1161
+ role: "setup",
1162
+ persona: "Redator das automa\xE7\xF5es",
1163
+ pitch: "Diga o que revisar nas automa\xE7\xF5es.",
1164
+ description: "Vejo todas as automa\xE7\xF5es deste app, quais est\xE3o ligadas e o texto de cada uma. Posso ligar, desligar e reescrever \u2014 em uma ou em v\xE1rias de uma vez. Voc\xEA confirma cada mudan\xE7a.",
1165
+ suggestions: [
1166
+ { label: "Revise as mensagens de pedido no tom da minha marca" },
1167
+ { label: "Ligue as automa\xE7\xF5es de entrega" },
1168
+ { label: "Deixe as mensagens de WhatsApp mais curtas" }
1169
+ ]
1170
+ }
1171
+ };
1172
+ var CHANNEL_ICON2 = {
787
1173
  email: Mail,
788
- whatsapp: MessageCircle,
1174
+ // The brand glyph, not a generic bubble: in a list of channels, WhatsApp
1175
+ // is recognized by its mark before its label.
1176
+ whatsapp: WhatsAppIcon,
789
1177
  sms: Smartphone,
790
1178
  push: MessageSquare,
791
1179
  webchat: MessageSquare
@@ -807,7 +1195,7 @@ var KIND_HINT = {
807
1195
  transactional: "Disparados por uma a\xE7\xE3o da pessoa. N\xE3o precisam de opt-in.",
808
1196
  marketing: "Iniciativa da empresa. S\xF3 para quem aceitou receber."
809
1197
  };
810
- function iconFor2(name) {
1198
+ function iconFor(name) {
811
1199
  if (!name) return Zap;
812
1200
  const icons = LucideIcons;
813
1201
  return icons[name] ?? Zap;
@@ -822,18 +1210,19 @@ function readSelection(basePath) {
822
1210
  function AutomationsList({ store, variant = "page", basePath = "/automations" }) {
823
1211
  const tenantId = useOrganizationStoreLike();
824
1212
  const runtime = usePluginRuntimeOptional();
825
- const [messages, setMessages] = React2.useState(null);
826
- const [kind, setKind] = React2.useState("all");
1213
+ const [messages, setMessages] = React4__default.useState(null);
1214
+ const [kind, setKind] = React4__default.useState("all");
827
1215
  const { connectors } = useConnectors({ includeGrants: false, includeCatalog: false });
828
- const owned = React2.useMemo(() => connectorAutomationRows(connectors ?? []), [connectors]);
829
- const [selected, setSelected] = React2.useState(() => readSelection(basePath));
830
- React2.useEffect(() => {
1216
+ const owned = React4__default.useMemo(() => connectorAutomationRows(connectors ?? []), [connectors]);
1217
+ const providerTemplates = useProviderTemplates();
1218
+ const [selected, setSelected] = React4__default.useState(() => readSelection(basePath));
1219
+ React4__default.useEffect(() => {
831
1220
  const sync = () => setSelected(readSelection(basePath));
832
1221
  sync();
833
1222
  window.addEventListener("hashchange", sync);
834
1223
  return () => window.removeEventListener("hashchange", sync);
835
1224
  }, [basePath]);
836
- const reload = React2.useCallback(async () => {
1225
+ const reload = React4__default.useCallback(async () => {
837
1226
  let overrides = [];
838
1227
  try {
839
1228
  overrides = await store.list(tenantId);
@@ -842,12 +1231,12 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
842
1231
  }
843
1232
  setMessages(resolveMessages(overrides, runtime?.events ?? []));
844
1233
  }, [store, tenantId, runtime]);
845
- React2.useEffect(() => {
1234
+ React4__default.useEffect(() => {
846
1235
  void reload();
847
1236
  }, [reload]);
848
- const surfaceRef = React2.useRef({ messages, store, tenantId, reload });
849
- surfaceRef.current = { messages, store, tenantId, reload };
850
- const agentSurface = React2.useMemo(() => ({
1237
+ const surfaceRef = React4__default.useRef({ messages, store, tenantId, reload, basePath });
1238
+ surfaceRef.current = { messages, store, tenantId, reload, basePath };
1239
+ const agentSurface = React4__default.useMemo(() => ({
851
1240
  id: "automations.list",
852
1241
  pluginId: "automations",
853
1242
  instructions: "The user is on the automations list. `automations` in the surface state is the complete set \u2014 use each entry's `event` and `channel` verbatim, and each entry's own `placeholders` when rewriting its text. Call configureAutomation once per automation you change; say afterwards which ones you touched.",
@@ -894,7 +1283,16 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
894
1283
  ...args.body === void 0 ? {} : { body: String(args.body) }
895
1284
  }, o.tenantId);
896
1285
  await o.reload();
897
- return { ok: true, event: found.event.name, channel: found.channel };
1286
+ return {
1287
+ ok: true,
1288
+ event: found.event.name,
1289
+ channel: found.channel,
1290
+ ref: {
1291
+ id: `${found.event.name}/${found.channel}`,
1292
+ label: `${found.event.title ?? found.event.name} \u2014 ${CHANNEL_LABEL2[found.channel] ?? found.channel}`,
1293
+ path: `${o.basePath}/${encodeURIComponent(found.event.name)}/${found.channel}`
1294
+ }
1295
+ };
898
1296
  } catch (e) {
899
1297
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
900
1298
  }
@@ -934,20 +1332,16 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
934
1332
  ] });
935
1333
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
936
1334
  variant !== "panel" ? /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(
937
- AskAIButton,
1335
+ PageActions,
938
1336
  {
939
- hint: "O que revisar nas automa\xE7\xF5es\u2026",
940
- intro: {
941
- role: "setup",
942
- persona: "Redator das automa\xE7\xF5es",
943
- pitch: "Diga o que revisar nas automa\xE7\xF5es.",
944
- description: "Vejo todas as automa\xE7\xF5es deste app, quais est\xE3o ligadas e o texto de cada uma. Posso ligar, desligar e reescrever \u2014 em uma ou em v\xE1rias de uma vez. Voc\xEA confirma cada mudan\xE7a.",
945
- suggestions: [
946
- { label: "Revise as mensagens de pedido no tom da minha marca" },
947
- { label: "Ligue as automa\xE7\xF5es de entrega" },
948
- { label: "Deixe as mensagens de WhatsApp mais curtas" }
949
- ]
950
- }
1337
+ actions: [{
1338
+ id: "automations-ai",
1339
+ label: ASK_AI_ACTION.label,
1340
+ icon: WandSparkles,
1341
+ intent: "primary",
1342
+ creates: true,
1343
+ onSelect: () => askAI({ hint: ASK_AI_ACTION.hint, intro: ASK_AI_ACTION.intro })
1344
+ }]
951
1345
  }
952
1346
  ) }) : null,
953
1347
  variant === "panel" ? /* @__PURE__ */ jsx(
@@ -984,14 +1378,16 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
984
1378
  ] }) : groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
985
1379
  /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: group.label }),
986
1380
  group.items.map((m) => {
987
- const Icon = iconFor2(m.event.icon ?? m.event.pluginIcon);
988
- const ChannelIcon = CHANNEL_ICON[m.channel];
1381
+ const Icon = iconFor(m.event.icon ?? m.event.pluginIcon);
1382
+ const ChannelIcon = CHANNEL_ICON2[m.channel];
989
1383
  const href = `${basePath}/${encodeURIComponent(m.event.name)}/${m.channel}`;
1384
+ const isOpen = selected != null && decodeURIComponent(selected.eventKey) === m.event.name && selected.channel === m.channel;
990
1385
  return /* @__PURE__ */ jsx(
991
1386
  Card,
992
1387
  {
993
1388
  role: "button",
994
1389
  tabIndex: 0,
1390
+ "aria-current": isOpen || void 0,
995
1391
  onClick: () => adminNavigateTo(href),
996
1392
  onKeyDown: (e) => {
997
1393
  if (e.key === "Enter" || e.key === " ") {
@@ -999,7 +1395,8 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
999
1395
  adminNavigateTo(href);
1000
1396
  }
1001
1397
  },
1002
- className: "cursor-pointer overflow-hidden transition-colors hover:border-primary/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1398
+ style: isOpen ? { outline: "2px solid hsl(var(--primary) / 0.65)", outlineOffset: "-2px" } : void 0,
1399
+ className: `cursor-pointer overflow-hidden transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring ${isOpen ? "" : "hover:border-primary/40"}`,
1003
1400
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-4 p-4", children: [
1004
1401
  /* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) }),
1005
1402
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
@@ -1009,7 +1406,13 @@ function AutomationsList({ store, variant = "page", basePath = "/automations" })
1009
1406
  /* @__PURE__ */ jsx(ChannelIcon, { className: "h-3 w-3" }),
1010
1407
  CHANNEL_LABEL2[m.channel]
1011
1408
  ] }),
1012
- m.customized && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: "personalizado" })
1409
+ m.customized && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: "personalizado" }),
1410
+ /* @__PURE__ */ jsx(
1411
+ ProviderTemplateBadge,
1412
+ {
1413
+ approval: providerApprovalFor(providerTemplates, m.event.name, m.channel)
1414
+ }
1415
+ )
1013
1416
  ] }),
1014
1417
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: m.event.description }),
1015
1418
  /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
@@ -1081,6 +1484,9 @@ function rowToTemplate(row) {
1081
1484
  subject: row.subject || null,
1082
1485
  body: row.body || null,
1083
1486
  name: row.name || null,
1487
+ // NULL fica NULL pelo mesmo motivo do `enabled`; lista vazia é uma escolha
1488
+ // ("sem botões"), não a ausência de uma.
1489
+ actions: normalizeMessageActions(row.actions),
1084
1490
  updatedAt: row.updated_at ?? row.created_at
1085
1491
  };
1086
1492
  }
@@ -1108,6 +1514,7 @@ function createSupabaseTemplateStore() {
1108
1514
  if (input.subject !== void 0) payload.subject = input.subject;
1109
1515
  if (input.body !== void 0) payload.body = input.body;
1110
1516
  if (input.name !== void 0) payload.name = input.name;
1517
+ if (input.actions !== void 0) payload.actions = input.actions;
1111
1518
  const { data, error } = await db().from(TABLE).upsert(payload, { onConflict: "tenant_id,event_key,channel" }).select().single();
1112
1519
  if (error) throw error;
1113
1520
  return rowToTemplate(data);
@@ -1127,11 +1534,11 @@ function createAutomationsPlugin(options) {
1127
1534
  const store = options?.store ?? (connection ? createFayzTemplateStore(connection) : createSupabaseTemplateStore());
1128
1535
  startEventLog();
1129
1536
  const inSettings = options?.placement === "settings";
1130
- const ListPage = () => React2.createElement(AutomationsList, { store });
1537
+ const ListPage = () => React4__default.createElement(AutomationsList, { store });
1131
1538
  ListPage.displayName = "AutomationsList";
1132
- const SettingsListPage = () => React2.createElement(AutomationsList, { store, variant: "panel", basePath: "/settings/automations" });
1539
+ const SettingsListPage = () => React4__default.createElement(AutomationsList, { store, variant: "panel", basePath: "/settings/automations" });
1133
1540
  SettingsListPage.displayName = "AutomationsSettings";
1134
- const RailDetail = ({ params, onClose }) => React2.createElement(AutomationDetailPage, {
1541
+ const RailDetail = ({ params, onClose }) => React4__default.createElement(AutomationDetailPage, {
1135
1542
  eventKey: params.eventKey,
1136
1543
  channel: params.channel,
1137
1544
  store,
@@ -1181,6 +1588,6 @@ function createAutomationsPlugin(options) {
1181
1588
  };
1182
1589
  }
1183
1590
 
1184
- export { createAutomationsPlugin, createFayzTemplateStore, createSupabaseTemplateStore };
1591
+ export { PROVIDER_TEMPLATES_TABLE, createAutomationsPlugin, createFayzTemplateStore, createSupabaseTemplateStore, loadProviderTemplates, providerApprovalFor, providerTemplateFor };
1185
1592
  //# sourceMappingURL=index.js.map
1186
1593
  //# sourceMappingURL=index.js.map