@adatechnology/notification-ui 0.1.0-rc.3 → 0.1.0-rc.5

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.
@@ -48,7 +48,26 @@ var pt_BR_default = {
48
48
  "settings.saving": "Salvando\u2026",
49
49
  "settings.unchanged": "Nada mudou ainda.",
50
50
  "settings.close": "Fechar",
51
- "settings.version": "v"
51
+ "settings.version": "v",
52
+ "settings.newTemplate": "Nova mensagem",
53
+ "settings.remove": "Remover",
54
+ "settings.key": "Chave da notifica\xE7\xE3o",
55
+ "settings.keyHint": "N\xE3o muda depois de criada \u2014 \xE9 por ela que o envio acha a mensagem.",
56
+ "settings.channel": "Canal",
57
+ "settings.locale": "Idioma",
58
+ "settings.variablesTitle": "Vari\xE1veis",
59
+ "settings.variablesHint": "Clique para inserir no ponto onde o cursor estava.",
60
+ "settings.unknownVariables": "Vari\xE1veis que n\xE3o existem nesta notifica\xE7\xE3o:",
61
+ "settings.viewport.desktop": "Computador",
62
+ "settings.viewport.mobile": "Celular",
63
+ "settings.constraint.title": "Assunto passa do limite do canal:",
64
+ "settings.constraint.body": "Texto passa do limite do canal:",
65
+ "settings.tab.messages": "Mensagens",
66
+ "settings.tab.routing": "Roteamento",
67
+ "settings.policiesTitle": "Canais permitidos por assunto",
68
+ "settings.policiesHint": "Decis\xE3o da empresa. Canal desligado aqui n\xE3o sai, mesmo que o destinat\xE1rio tenha pedido.",
69
+ "settings.savePolicies": "Salvar roteamento",
70
+ "settings.discard": "Descartar"
52
71
  };
53
72
 
54
73
  // src/locales/en.json
@@ -98,7 +117,26 @@ var en_default = {
98
117
  "settings.saving": "Saving\u2026",
99
118
  "settings.unchanged": "Nothing changed yet.",
100
119
  "settings.close": "Close",
101
- "settings.version": "v"
120
+ "settings.version": "v",
121
+ "settings.newTemplate": "New message",
122
+ "settings.remove": "Remove",
123
+ "settings.key": "Notification key",
124
+ "settings.keyHint": "Fixed once created \u2014 sending looks the message up by it.",
125
+ "settings.channel": "Channel",
126
+ "settings.locale": "Language",
127
+ "settings.variablesTitle": "Variables",
128
+ "settings.variablesHint": "Click to insert where the cursor was.",
129
+ "settings.unknownVariables": "Variables this notification does not have:",
130
+ "settings.viewport.desktop": "Desktop",
131
+ "settings.viewport.mobile": "Mobile",
132
+ "settings.constraint.title": "Subject exceeds the channel limit:",
133
+ "settings.constraint.body": "Body exceeds the channel limit:",
134
+ "settings.tab.messages": "Messages",
135
+ "settings.tab.routing": "Routing",
136
+ "settings.policiesTitle": "Allowed channels per topic",
137
+ "settings.policiesHint": "Company decision. A channel turned off here never sends, even if the recipient opted in.",
138
+ "settings.savePolicies": "Save routing",
139
+ "settings.discard": "Discard"
102
140
  };
103
141
 
104
142
  // src/locales/index.ts
@@ -149,7 +187,9 @@ var NOTIFICATION_QUERY_KEYS = {
149
187
  list: (filters) => ["notifications", "list", filters],
150
188
  unreadCount: () => ["notifications", "unread-count"],
151
189
  preferences: () => ["notifications", "preferences"],
152
- templates: () => ["notifications", "templates"]
190
+ templates: () => ["notifications", "templates"],
191
+ templateVariables: () => ["notifications", "template-variables"],
192
+ categoryPolicies: () => ["notifications", "category-policies"]
153
193
  };
154
194
 
155
195
  // src/hooks/useNotifications.ts
@@ -256,19 +296,83 @@ function useNotificationStream(options = {}) {
256
296
  }, [client, queryClient, enabled, onEvent]);
257
297
  }
258
298
 
259
- // src/hooks/usePreferences.ts
299
+ // src/hooks/useCategoryPolicies.ts
300
+ import { useMemo as useMemo2, useState } from "react";
260
301
  import { useMutation as useMutation2, useQuery as useQuery2, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
302
+ function identityOf(params) {
303
+ return `${params.category}:${params.channel}`;
304
+ }
305
+ function useCategoryPolicies() {
306
+ const { client } = useNotificationContext();
307
+ const queryClient = useQueryClient3();
308
+ const policiesQuery = useQuery2({
309
+ queryKey: NOTIFICATION_QUERY_KEYS.categoryPolicies(),
310
+ queryFn: () => client.getCategoryPolicies()
311
+ });
312
+ const mutation = useMutation2({
313
+ mutationFn: (policies) => client.updateCategoryPolicies(policies),
314
+ onSuccess() {
315
+ void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.categoryPolicies() });
316
+ }
317
+ });
318
+ const [touched, setTouched] = useState({});
319
+ const saved = useMemo2(() => {
320
+ const byIdentity = /* @__PURE__ */ new Map();
321
+ for (const policy of policiesQuery.data ?? []) byIdentity.set(identityOf(policy), policy.enabled);
322
+ return byIdentity;
323
+ }, [policiesQuery.data]);
324
+ function isAllowed(params) {
325
+ const identity = identityOf(params);
326
+ return touched[identity] ?? saved.get(identity) ?? true;
327
+ }
328
+ return {
329
+ isLoading: policiesQuery.isLoading,
330
+ isSaving: mutation.isPending,
331
+ error: mutation.error?.message,
332
+ isDirty: Object.keys(touched).length > 0,
333
+ isAllowed,
334
+ toggle(params) {
335
+ const identity = identityOf(params);
336
+ setTouched((current) => ({ ...current, [identity]: !isAllowed(params) }));
337
+ },
338
+ save() {
339
+ const categories = new Set(Object.keys(touched).map((identity) => identity.split(":")[0] ?? ""));
340
+ const policies = [];
341
+ for (const category of categories) {
342
+ const channels = /* @__PURE__ */ new Set([
343
+ ...[...saved.keys()].filter((identity) => identity.startsWith(`${category}:`)),
344
+ ...Object.keys(touched).filter((identity) => identity.startsWith(`${category}:`))
345
+ ]);
346
+ for (const identity of channels) {
347
+ const channel = identity.slice(category.length + 1);
348
+ policies.push({
349
+ category,
350
+ channel,
351
+ enabled: isAllowed({ category, channel })
352
+ });
353
+ }
354
+ }
355
+ mutation.mutate(policies, { onSuccess: () => setTouched({}) });
356
+ },
357
+ reset() {
358
+ setTouched({});
359
+ }
360
+ };
361
+ }
362
+
363
+ // src/hooks/usePreferences.ts
364
+ import { useMutation as useMutation3, useQuery as useQuery3, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
261
365
  function usePreferences() {
262
366
  const { client } = useNotificationContext();
263
- return useQuery2({
367
+ return useQuery3({
264
368
  queryKey: NOTIFICATION_QUERY_KEYS.preferences(),
265
369
  queryFn: () => client.getPreferences()
266
370
  });
267
371
  }
268
372
  function useUpdatePreferences() {
269
373
  const { client } = useNotificationContext();
270
- const queryClient = useQueryClient3();
271
- return useMutation2({
374
+ const queryClient = useQueryClient4();
375
+ return useMutation3({
272
376
  mutationFn: (preferences) => client.updatePreferences(preferences),
273
377
  onSuccess(saved) {
274
378
  queryClient.setQueryData(NOTIFICATION_QUERY_KEYS.preferences(), saved);
@@ -277,21 +381,39 @@ function useUpdatePreferences() {
277
381
  }
278
382
  function useTemplates() {
279
383
  const { client } = useNotificationContext();
280
- return useQuery2({
384
+ return useQuery3({
281
385
  queryKey: NOTIFICATION_QUERY_KEYS.templates(),
282
386
  queryFn: () => client.listTemplates()
283
387
  });
284
388
  }
285
389
  function useUpsertTemplate() {
286
390
  const { client } = useNotificationContext();
287
- const queryClient = useQueryClient3();
288
- return useMutation2({
391
+ const queryClient = useQueryClient4();
392
+ return useMutation3({
289
393
  mutationFn: (input) => client.upsertTemplate(input),
290
394
  onSuccess() {
291
395
  void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.templates() });
292
396
  }
293
397
  });
294
398
  }
399
+ function useTemplateVariables() {
400
+ const { client } = useNotificationContext();
401
+ return useQuery3({
402
+ queryKey: NOTIFICATION_QUERY_KEYS.templateVariables(),
403
+ queryFn: () => client.listTemplateVariables(),
404
+ staleTime: Number.POSITIVE_INFINITY
405
+ });
406
+ }
407
+ function useDeactivateTemplate() {
408
+ const { client } = useNotificationContext();
409
+ const queryClient = useQueryClient4();
410
+ return useMutation3({
411
+ mutationFn: (id) => client.deactivateTemplate(id),
412
+ onSuccess() {
413
+ void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.templates() });
414
+ }
415
+ });
416
+ }
295
417
 
296
418
  // src/format/notificationTime.ts
297
419
  var SECOND = 1e3;
@@ -323,8 +445,13 @@ function formatNotificationTimestamp(isoTimestamp, locale) {
323
445
  }
324
446
 
325
447
  // src/hooks/useTemplateEditor.ts
326
- import { useMemo as useMemo2, useState } from "react";
327
- import { renderTemplate } from "@adatechnology/notification-contracts";
448
+ import { useMemo as useMemo3, useState as useState2 } from "react";
449
+ import {
450
+ PREVIEW_VIEWPORT_BY_CHANNEL,
451
+ buildPreviewPayload,
452
+ diffTemplateVariables,
453
+ renderTemplate
454
+ } from "@adatechnology/notification-contracts";
328
455
  function toDraft(template) {
329
456
  return {
330
457
  key: template.key,
@@ -335,12 +462,18 @@ function toDraft(template) {
335
462
  whatsappTemplateName: template.whatsappTemplateName ?? ""
336
463
  };
337
464
  }
465
+ function buildEmptyDraft(params) {
466
+ return { key: "", channel: params.channel, locale: params.locale, subject: "", body: "", whatsappTemplateName: "" };
467
+ }
338
468
  function useTemplateEditor(params = {}) {
339
469
  const templatesQuery = useTemplates();
470
+ const variablesQuery = useTemplateVariables();
340
471
  const upsert = useUpsertTemplate();
341
- const [selectedId, setSelectedId] = useState(void 0);
342
- const [draft, setDraft] = useState(void 0);
343
- const templates = useMemo2(() => {
472
+ const deactivateMutation = useDeactivateTemplate();
473
+ const [selectedId, setSelectedId] = useState2(void 0);
474
+ const [draft, setDraft] = useState2(void 0);
475
+ const [isNew, setIsNew] = useState2(false);
476
+ const templates = useMemo3(() => {
344
477
  const activeByIdentity = /* @__PURE__ */ new Map();
345
478
  for (const template of templatesQuery.data ?? []) {
346
479
  if (!template.active) continue;
@@ -352,37 +485,75 @@ function useTemplateEditor(params = {}) {
352
485
  (left, right) => left.key.localeCompare(right.key) || left.channel.localeCompare(right.channel)
353
486
  );
354
487
  }, [templatesQuery.data]);
355
- const selected = useMemo2(() => templates.find((template) => template.id === selectedId), [templates, selectedId]);
356
- const preview = useMemo2(() => {
357
- if (!draft) return void 0;
358
- return renderTemplate({
488
+ const selected = useMemo3(() => templates.find((template) => template.id === selectedId), [templates, selectedId]);
489
+ const variables = useMemo3(() => draft ? variablesQuery.data?.[draft.key] ?? [] : [], [variablesQuery.data, draft]);
490
+ const variableDiff = useMemo3(
491
+ () => diffTemplateVariables({
492
+ body: draft?.body ?? "",
493
+ subject: draft?.subject || void 0,
494
+ // Chave sem entrada no catálogo é catálogo não declarado, não catálogo vazio — o servidor
495
+ // aceita qualquer variável nesse caso, e a tela não pode acusar o que a rota vai gravar.
496
+ variables: draft && variablesQuery.data?.[draft.key] ? variables : void 0
497
+ }),
498
+ [draft, variables, variablesQuery.data]
499
+ );
500
+ const previews = useMemo3(() => {
501
+ if (!draft) return [];
502
+ const payload = { ...buildPreviewPayload(variables), ...params.previewPayload ?? {} };
503
+ const rendered = renderTemplate({
359
504
  channel: draft.channel,
360
505
  subject: draft.subject || void 0,
361
506
  body: draft.body,
362
- payload: params.previewPayload ?? {}
507
+ payload
363
508
  });
364
- }, [draft, params.previewPayload]);
365
- const isDirty = useMemo2(() => {
366
- if (!draft || !selected) return false;
509
+ const viewports = PREVIEW_VIEWPORT_BY_CHANNEL[draft.channel] ?? [];
510
+ return viewports.map((viewport) => ({ ...viewport, rendered }));
511
+ }, [draft, variables, params.previewPayload]);
512
+ const isDirty = useMemo3(() => {
513
+ if (!draft) return false;
514
+ if (isNew) return draft.key.length > 0 || draft.body.length > 0;
515
+ if (!selected) return false;
367
516
  const original = toDraft(selected);
368
517
  return Object.keys(original).some((field) => original[field] !== draft[field]);
369
- }, [draft, selected]);
518
+ }, [draft, selected, isNew]);
370
519
  return {
371
520
  templates,
372
521
  isLoading: templatesQuery.isLoading,
373
522
  isSaving: upsert.isPending,
374
- error: upsert.error?.message,
523
+ isDeactivating: deactivateMutation.isPending,
524
+ error: upsert.error?.message ?? deactivateMutation.error?.message,
375
525
  selected,
376
526
  draft,
377
- preview,
527
+ isNew,
528
+ isIdentityLocked: !isNew,
529
+ previews,
530
+ variables,
531
+ variableDiff,
378
532
  isDirty,
533
+ // A variável desconhecida é recusada pelo servidor com 400; bloquear o botão evita a ida
534
+ // perdida, sem virar a única defesa — a validação continua sendo a da rota.
535
+ canSave: Boolean(draft?.key && draft.body) && variableDiff.unknown.length === 0,
379
536
  select(template) {
380
537
  setSelectedId(template.id);
381
538
  setDraft(toDraft(template));
539
+ setIsNew(false);
540
+ },
541
+ startNew() {
542
+ setSelectedId(void 0);
543
+ setIsNew(true);
544
+ setDraft(buildEmptyDraft({ channel: params.defaultChannel ?? "email", locale: params.defaultLocale ?? "pt-BR" }));
382
545
  },
383
546
  update(patch) {
384
547
  setDraft((current) => current ? { ...current, ...patch } : current);
385
548
  },
549
+ insertVariable({ name, field, cursorIndex }) {
550
+ setDraft((current) => {
551
+ if (!current) return current;
552
+ const text = current[field];
553
+ const at = Math.min(Math.max(cursorIndex, 0), text.length);
554
+ return { ...current, [field]: `${text.slice(0, at)}{{${name}}}${text.slice(at)}` };
555
+ });
556
+ },
386
557
  save() {
387
558
  if (!draft) return;
388
559
  const body = {
@@ -391,15 +562,30 @@ function useTemplateEditor(params = {}) {
391
562
  locale: draft.locale,
392
563
  active: true,
393
564
  body: draft.body,
394
- // Decisão 3: chave omitida quando vazia.
395
565
  ...draft.subject ? { subject: draft.subject } : {},
396
566
  ...draft.whatsappTemplateName ? { whatsappTemplateName: draft.whatsappTemplateName } : {}
397
567
  };
398
- upsert.mutate(body, { onSuccess: () => setSelectedId(void 0) });
568
+ upsert.mutate(body, {
569
+ onSuccess: () => {
570
+ setSelectedId(void 0);
571
+ setDraft(void 0);
572
+ setIsNew(false);
573
+ }
574
+ });
575
+ },
576
+ deactivate(id) {
577
+ deactivateMutation.mutate(id, {
578
+ onSuccess: () => {
579
+ if (selectedId !== id) return;
580
+ setSelectedId(void 0);
581
+ setDraft(void 0);
582
+ }
583
+ });
399
584
  },
400
585
  clear() {
401
586
  setSelectedId(void 0);
402
587
  setDraft(void 0);
588
+ setIsNew(false);
403
589
  }
404
590
  };
405
591
  }
@@ -417,12 +603,15 @@ export {
417
603
  useDeleteNotification,
418
604
  flattenNotificationPages,
419
605
  useNotificationStream,
606
+ useCategoryPolicies,
420
607
  usePreferences,
421
608
  useUpdatePreferences,
422
609
  useTemplates,
423
610
  useUpsertTemplate,
611
+ useTemplateVariables,
612
+ useDeactivateTemplate,
424
613
  formatNotificationTime,
425
614
  formatNotificationTimestamp,
426
615
  useTemplateEditor
427
616
  };
428
- //# sourceMappingURL=chunk-I6WQKOCP.js.map
617
+ //# sourceMappingURL=chunk-QBQ62VZ3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/NotificationProvider.tsx","../src/locales/pt-BR.json","../src/locales/en.json","../src/locales/index.ts","../src/hooks/useNotifications.ts","../src/hooks/queryKeys.ts","../src/hooks/useNotificationStream.ts","../src/hooks/useCategoryPolicies.ts","../src/hooks/usePreferences.ts","../src/format/notificationTime.ts","../src/hooks/useTemplateEditor.ts"],"sourcesContent":["/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * Provider do pacote. **Não instancia `QueryClient`** — usa o do host (regra de módulos\n * plugáveis §4.2). Instanciar o próprio criaria dois caches na mesma página: a inbox invalidaria\n * um, e a tela do produto continuaria lendo o outro.\n */\n\nimport { createContext, useContext, useMemo, type ReactNode } from 'react'\nimport type { NotificationClient } from '@adatechnology/notification-client'\n\nimport { DEFAULT_LOCALE, resolveMessages, type NotificationLocale, type NotificationMessages } from './locales'\n\nexport type NotificationTheme = {\n /** Classe aplicada na raiz de cada componente — é por onde o host injeta os tokens dele. */\n readonly rootClassName?: string\n readonly components?: Partial<Record<string, string>>\n}\n\nexport type NotificationContextValue = {\n readonly client: NotificationClient\n /**\n * O idioma resolvido, e não só os textos dele: data e número não vivem no arquivo de locale —\n * quem os formata é `Intl`, e `Intl` precisa da tag do idioma. Sem isto o item da lista teria de\n * chutar um locale fixo, e a inbox mostraria \"5 minutes ago\" numa tela em português.\n */\n readonly locale: NotificationLocale\n readonly messages: NotificationMessages\n readonly theme: NotificationTheme\n /** Polling de fallback quando o SSE não está disponível (segundos); `0` desliga. */\n readonly pollIntervalSeconds: number\n}\n\nconst NotificationContext = createContext<NotificationContextValue | null>(null)\n\nexport type NotificationProviderProps = {\n readonly client: NotificationClient\n readonly locale?: NotificationLocale\n /** Sobrescreve textos pontuais sem trocar o locale inteiro. */\n readonly messageOverrides?: Partial<NotificationMessages>\n readonly theme?: NotificationTheme\n readonly pollIntervalSeconds?: number\n readonly children: ReactNode\n}\n\nexport function NotificationProvider({\n client,\n locale = DEFAULT_LOCALE,\n messageOverrides,\n theme = {},\n pollIntervalSeconds = 60,\n children,\n}: NotificationProviderProps) {\n const value = useMemo<NotificationContextValue>(\n () => ({\n client,\n locale,\n messages: { ...resolveMessages(locale), ...messageOverrides },\n theme,\n pollIntervalSeconds,\n }),\n [client, locale, messageOverrides, theme, pollIntervalSeconds],\n )\n\n return <NotificationContext.Provider value={value}>{children}</NotificationContext.Provider>\n}\n\nexport function useNotificationContext(): NotificationContextValue {\n const context = useContext(NotificationContext)\n if (!context) throw new Error('notification-ui: componente usado fora de <NotificationProvider>')\n return context\n}\n","{\n \"bell.label\": \"Notificações\",\n \"bell.unreadSuffix\": \"não lidas\",\n \"list.title\": \"Caixa de entrada\",\n \"list.unread\": \"não lidas\",\n \"list.empty\": \"Nenhuma notificação por aqui.\",\n \"list.loading\": \"Carregando…\",\n \"list.error\": \"Não foi possível carregar suas notificações.\",\n \"list.retry\": \"Tentar de novo\",\n \"list.loadMore\": \"Carregar mais\",\n \"list.markAllAsRead\": \"Marcar todas como lidas\",\n \"item.markAsRead\": \"Marcar como lida\",\n \"item.delete\": \"Excluir\",\n \"item.unread\": \"Não lida\",\n \"preferences.title\": \"Preferências de notificação\",\n \"preferences.description\": \"Escolha por onde você quer ser avisado em cada assunto.\",\n \"preferences.save\": \"Salvar preferências\",\n \"preferences.saved\": \"Preferências salvas.\",\n \"preferences.error\": \"Não foi possível salvar. Tente de novo.\",\n \"preferences.empty\": \"Nenhuma preferência configurada ainda.\",\n \"preferences.quietHours\": \"Silêncio entre\",\n \"preferences.quietHoursTo\": \"e\",\n \"channel.inbox\": \"No app\",\n \"channel.push\": \"Push\",\n \"channel.email\": \"E-mail\",\n \"channel.whatsapp\": \"WhatsApp\",\n \"channel.sms\": \"SMS\",\n \"workspace.title\": \"Notificações\",\n \"workspace.description\": \"O que foi avisado, e por quais canais.\",\n \"workspace.settingsLink\": \"Configurações\",\n \"settings.title\": \"Configurações de notificação\",\n \"settings.description\": \"Por onde avisar, e o texto exato que chega.\",\n \"settings.channelsTitle\": \"Canais\",\n \"settings.channelsHint\": \"Desligar um canal não apaga o histórico — o aviso deixa de sair por ali daqui em diante.\",\n \"settings.templatesTitle\": \"Mensagens\",\n \"settings.templatesHint\": \"Uma versão nova é criada a cada gravação; a anterior fica no histórico.\",\n \"settings.templatesEmpty\": \"Nenhuma mensagem cadastrada.\",\n \"settings.pickTemplate\": \"Escolha uma mensagem para editar.\",\n \"settings.subject\": \"Título\",\n \"settings.subjectHint\": \"O que aparece na lista. Vazio, o título é derivado da primeira linha do texto.\",\n \"settings.body\": \"Texto\",\n \"settings.previewTitle\": \"Como o destinatário vê\",\n \"settings.save\": \"Salvar nova versão\",\n \"settings.saving\": \"Salvando…\",\n \"settings.unchanged\": \"Nada mudou ainda.\",\n \"settings.close\": \"Fechar\",\n \"settings.version\": \"v\",\n \"settings.newTemplate\": \"Nova mensagem\",\n \"settings.remove\": \"Remover\",\n \"settings.key\": \"Chave da notificação\",\n \"settings.keyHint\": \"Não muda depois de criada — é por ela que o envio acha a mensagem.\",\n \"settings.channel\": \"Canal\",\n \"settings.locale\": \"Idioma\",\n \"settings.variablesTitle\": \"Variáveis\",\n \"settings.variablesHint\": \"Clique para inserir no ponto onde o cursor estava.\",\n \"settings.unknownVariables\": \"Variáveis que não existem nesta notificação:\",\n \"settings.viewport.desktop\": \"Computador\",\n \"settings.viewport.mobile\": \"Celular\",\n \"settings.constraint.title\": \"Assunto passa do limite do canal:\",\n \"settings.constraint.body\": \"Texto passa do limite do canal:\",\n \"settings.tab.messages\": \"Mensagens\",\n \"settings.tab.routing\": \"Roteamento\",\n \"settings.policiesTitle\": \"Canais permitidos por assunto\",\n \"settings.policiesHint\": \"Decisão da empresa. Canal desligado aqui não sai, mesmo que o destinatário tenha pedido.\",\n \"settings.savePolicies\": \"Salvar roteamento\",\n \"settings.discard\": \"Descartar\"\n}\n","{\n \"bell.label\": \"Notifications\",\n \"bell.unreadSuffix\": \"unread\",\n \"list.title\": \"Inbox\",\n \"list.unread\": \"unread\",\n \"list.empty\": \"Nothing here yet.\",\n \"list.loading\": \"Loading…\",\n \"list.error\": \"We couldn't load your notifications.\",\n \"list.retry\": \"Try again\",\n \"list.loadMore\": \"Load more\",\n \"list.markAllAsRead\": \"Mark all as read\",\n \"item.markAsRead\": \"Mark as read\",\n \"item.delete\": \"Delete\",\n \"item.unread\": \"Unread\",\n \"preferences.title\": \"Notification preferences\",\n \"preferences.description\": \"Choose how you want to be notified about each topic.\",\n \"preferences.save\": \"Save preferences\",\n \"preferences.saved\": \"Preferences saved.\",\n \"preferences.error\": \"Couldn't save. Try again.\",\n \"preferences.empty\": \"No preferences configured yet.\",\n \"preferences.quietHours\": \"Quiet between\",\n \"preferences.quietHoursTo\": \"and\",\n \"channel.inbox\": \"In app\",\n \"channel.push\": \"Push\",\n \"channel.email\": \"Email\",\n \"channel.whatsapp\": \"WhatsApp\",\n \"channel.sms\": \"SMS\",\n \"workspace.title\": \"Notifications\",\n \"workspace.description\": \"What was sent, and through which channels.\",\n \"workspace.settingsLink\": \"Settings\",\n \"settings.title\": \"Notification settings\",\n \"settings.description\": \"Where to notify, and the exact text that arrives.\",\n \"settings.channelsTitle\": \"Channels\",\n \"settings.channelsHint\": \"Turning a channel off does not erase history — notices stop going out through it from now on.\",\n \"settings.templatesTitle\": \"Messages\",\n \"settings.templatesHint\": \"Each save creates a new version; the previous one stays in history.\",\n \"settings.templatesEmpty\": \"No messages registered.\",\n \"settings.pickTemplate\": \"Pick a message to edit.\",\n \"settings.subject\": \"Title\",\n \"settings.subjectHint\": \"Shown in the list. Empty, the title is derived from the first line of the body.\",\n \"settings.body\": \"Body\",\n \"settings.previewTitle\": \"How the recipient sees it\",\n \"settings.save\": \"Save new version\",\n \"settings.saving\": \"Saving…\",\n \"settings.unchanged\": \"Nothing changed yet.\",\n \"settings.close\": \"Close\",\n \"settings.version\": \"v\",\n \"settings.newTemplate\": \"New message\",\n \"settings.remove\": \"Remove\",\n \"settings.key\": \"Notification key\",\n \"settings.keyHint\": \"Fixed once created — sending looks the message up by it.\",\n \"settings.channel\": \"Channel\",\n \"settings.locale\": \"Language\",\n \"settings.variablesTitle\": \"Variables\",\n \"settings.variablesHint\": \"Click to insert where the cursor was.\",\n \"settings.unknownVariables\": \"Variables this notification does not have:\",\n \"settings.viewport.desktop\": \"Desktop\",\n \"settings.viewport.mobile\": \"Mobile\",\n \"settings.constraint.title\": \"Subject exceeds the channel limit:\",\n \"settings.constraint.body\": \"Body exceeds the channel limit:\",\n \"settings.tab.messages\": \"Messages\",\n \"settings.tab.routing\": \"Routing\",\n \"settings.policiesTitle\": \"Allowed channels per topic\",\n \"settings.policiesHint\": \"Company decision. A channel turned off here never sends, even if the recipient opted in.\",\n \"settings.savePolicies\": \"Save routing\",\n \"settings.discard\": \"Discard\"\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * Nenhum texto visível vive em tag ou prop (`web.md` §6). O host sobrescreve chaves pontuais via\n * `messageOverrides` sem precisar fornecer um locale inteiro.\n */\n\nimport ptBR from './pt-BR.json'\nimport en from './en.json'\n\nexport type NotificationMessages = typeof ptBR\nexport type NotificationLocale = 'pt-BR' | 'en'\n\nexport const DEFAULT_LOCALE: NotificationLocale = 'pt-BR'\n\nconst MESSAGES_BY_LOCALE: Record<NotificationLocale, NotificationMessages> = {\n 'pt-BR': ptBR,\n en: en as NotificationMessages,\n}\n\nexport function resolveMessages(locale: NotificationLocale): NotificationMessages {\n return MESSAGES_BY_LOCALE[locale] ?? MESSAGES_BY_LOCALE[DEFAULT_LOCALE]\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * Camada headless — a válvula de escape do item 4 da §4 da regra de módulos plugáveis: quando a\n * tela default não serve, o produto monta a própria sobre estes mesmos hooks e continua herdando\n * upgrades de lógica.\n */\n\nimport { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { NotificationSummary } from '@adatechnology/notification-contracts'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { NOTIFICATION_QUERY_KEYS } from './queryKeys'\n\nexport type UseNotificationsOptions = {\n readonly category?: string\n readonly read?: boolean\n readonly perPage?: number\n}\n\nexport function useNotifications(options: UseNotificationsOptions = {}) {\n const { client } = useNotificationContext()\n\n return useInfiniteQuery({\n queryKey: NOTIFICATION_QUERY_KEYS.list({ category: options.category, read: options.read }),\n initialPageParam: undefined as string | undefined,\n queryFn: ({ pageParam }) =>\n client.listNotifications({\n category: options.category,\n read: options.read,\n cursor: pageParam,\n perPage: options.perPage,\n }),\n getNextPageParam: (lastPage) => lastPage.nextCursor,\n })\n}\n\nexport function useUnreadCount() {\n const { client, pollIntervalSeconds } = useNotificationContext()\n\n return useQuery({\n queryKey: NOTIFICATION_QUERY_KEYS.unreadCount(),\n queryFn: () => client.countUnread(),\n // Polling é o fallback de quem não ligou o SSE. Com o stream ativo,\n // `useNotificationStream` invalida esta chave e o refetch acontece na hora.\n refetchInterval: pollIntervalSeconds > 0 ? pollIntervalSeconds * 1000 : false,\n })\n}\n\nexport function useMarkAsRead() {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: (id: string) => client.markAsRead(id),\n async onMutate(id: string) {\n // Atualização otimista: marcar como lida é ação de um clique, e esperar o round-trip para\n // o badge cair faz a interface parecer travada.\n await queryClient.cancelQueries({ queryKey: NOTIFICATION_QUERY_KEYS.all })\n const previousCount = queryClient.getQueryData<number>(NOTIFICATION_QUERY_KEYS.unreadCount())\n\n queryClient.setQueryData<number>(NOTIFICATION_QUERY_KEYS.unreadCount(), (current) =>\n current === undefined ? current : Math.max(0, current - 1),\n )\n return { previousCount, id }\n },\n onError(_error, _id, context) {\n // Rollback: sem isto o badge ficaria mentindo até o próximo refetch.\n if (context?.previousCount !== undefined) {\n queryClient.setQueryData(NOTIFICATION_QUERY_KEYS.unreadCount(), context.previousCount)\n }\n },\n onSettled() {\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.all })\n },\n })\n}\n\nexport function useMarkAllAsRead() {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: () => client.markAllAsRead(),\n onSettled() {\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.all })\n },\n })\n}\n\nexport function useDeleteNotification() {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: (id: string) => client.deleteNotification(id),\n onSettled() {\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.all })\n },\n })\n}\n\n/** Achata as páginas do infinite query — a lista quase sempre quer o array direto. */\nexport function flattenNotificationPages(\n pages: readonly { data: readonly NotificationSummary[] }[] | undefined,\n): readonly NotificationSummary[] {\n return pages?.flatMap((page) => page.data) ?? []\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * Chaves centralizadas: o sino e a lista leem o mesmo `unreadCount`, e uma invalidação precisa\n * atingir os dois. Chave montada inline em cada hook é como badge e lista passam a mostrar\n * números diferentes.\n */\n\nexport const NOTIFICATION_QUERY_KEYS = {\n all: ['notifications'] as const,\n list: (filters: { category?: string; read?: boolean }) => ['notifications', 'list', filters] as const,\n unreadCount: () => ['notifications', 'unread-count'] as const,\n preferences: () => ['notifications', 'preferences'] as const,\n templates: () => ['notifications', 'templates'] as const,\n templateVariables: () => ['notifications', 'template-variables'] as const,\n categoryPolicies: () => ['notifications', 'category-policies'] as const,\n} as const\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n */\n\nimport { useEffect } from 'react'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { subscribeToNotificationStream } from '@adatechnology/notification-client'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { NOTIFICATION_QUERY_KEYS } from './queryKeys'\n\nexport type UseNotificationStreamOptions = {\n /** Desligado por omissão: abrir SSE é decisão do host, que sabe se o backend expõe a rota. */\n readonly enabled?: boolean\n readonly onEvent?: (event: { event: string; data: unknown }) => void\n}\n\n/**\n * Liga o SSE do inbox ao cache do TanStack: cada evento invalida as chaves, e o badge e a lista\n * atualizam juntos. `useEffect` aqui é o uso correto do hook — sincronizar com um sistema externo\n * (`react.md`, \"useEffect só para sincronizar com sistemas externos\"), não transformar dado.\n */\nexport function useNotificationStream(options: UseNotificationStreamOptions = {}): void {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n const enabled = options.enabled ?? false\n const onEvent = options.onEvent\n\n useEffect(() => {\n if (!enabled) return\n\n let subscription: { close(): void } | undefined\n let cancelled = false\n\n void client.resolveStreamRequest().then(({ url, headers }) => {\n // A resolução do token é assíncrona; se o componente desmontou nesse meio-tempo, não abre\n // conexão nenhuma — senão sobraria um stream órfão a cada navegação rápida.\n if (cancelled) return\n\n subscription = subscribeToNotificationStream({\n url,\n headers,\n onEvent: (event) => {\n onEvent?.(event)\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.all })\n },\n })\n })\n\n return () => {\n cancelled = true\n subscription?.close()\n }\n }, [client, queryClient, enabled, onEvent])\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * O roteamento: por quais canais cada categoria PODE sair, decidido pela empresa.\n *\n * É a camada que faltava. O chamador já podia enumerar canais em cada `sendNotification`, e quem\n * recebe já tinha preferência — mas \"cobrança nunca sai por SMS\" não cabia em nenhum dos dois:\n * o primeiro espalha a decisão por todo o código, o segundo deixa ela na mão do destinatário.\n *\n * O modelo aqui é de teto, não de piso: desligar barra o canal para todo mundo; ligar apenas\n * permite, e a preferência de quem recebe continua valendo por cima.\n */\n\nimport { useMemo, useState } from 'react'\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { NotificationCategoryPolicy } from '@adatechnology/notification-contracts'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { NOTIFICATION_QUERY_KEYS } from './queryKeys'\n\nexport type UseCategoryPoliciesResult = {\n readonly isLoading: boolean\n readonly isSaving: boolean\n readonly error: string | undefined\n readonly isDirty: boolean\n /** Linha ausente é permitido — o mesmo default do servidor, para a tela não mentir. */\n isAllowed: (params: { category: string; channel: string }) => boolean\n toggle: (params: { category: string; channel: string }) => void\n save: () => void\n reset: () => void\n}\n\nfunction identityOf(params: { category: string; channel: string }): string {\n return `${params.category}:${params.channel}`\n}\n\nexport function useCategoryPolicies(): UseCategoryPoliciesResult {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n const policiesQuery = useQuery({\n queryKey: NOTIFICATION_QUERY_KEYS.categoryPolicies(),\n queryFn: () => client.getCategoryPolicies(),\n })\n\n const mutation = useMutation({\n mutationFn: (policies: readonly NotificationCategoryPolicy[]) => client.updateCategoryPolicies(policies),\n onSuccess() {\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.categoryPolicies() })\n },\n })\n\n /**\n * O rascunho guarda só o que foi TOCADO, não uma cópia do conjunto inteiro. Com a cópia, salvar\n * gravaria linha para toda combinação exibida na tela — e a ausência de linha, que é o default\n * \"permitido\", viraria centenas de registros de `enabled: true` sem significado.\n */\n const [touched, setTouched] = useState<Record<string, boolean>>({})\n\n const saved = useMemo(() => {\n const byIdentity = new Map<string, boolean>()\n for (const policy of policiesQuery.data ?? []) byIdentity.set(identityOf(policy), policy.enabled)\n return byIdentity\n }, [policiesQuery.data])\n\n function isAllowed(params: { category: string; channel: string }): boolean {\n const identity = identityOf(params)\n return touched[identity] ?? saved.get(identity) ?? true\n }\n\n return {\n isLoading: policiesQuery.isLoading,\n isSaving: mutation.isPending,\n error: mutation.error?.message,\n isDirty: Object.keys(touched).length > 0,\n isAllowed,\n\n toggle(params) {\n const identity = identityOf(params)\n setTouched((current) => ({ ...current, [identity]: !isAllowed(params) }))\n },\n\n save() {\n // Só as categorias tocadas vão no corpo: o servidor substitui categoria a categoria, e\n // mandar o conjunto inteiro apagaria e reescreveria política que ninguém editou.\n const categories = new Set(Object.keys(touched).map((identity) => identity.split(':')[0] ?? ''))\n const policies: NotificationCategoryPolicy[] = []\n for (const category of categories) {\n const channels = new Set([\n ...[...saved.keys()].filter((identity) => identity.startsWith(`${category}:`)),\n ...Object.keys(touched).filter((identity) => identity.startsWith(`${category}:`)),\n ])\n for (const identity of channels) {\n const channel = identity.slice(category.length + 1)\n policies.push({\n category,\n channel: channel as NotificationCategoryPolicy['channel'],\n enabled: isAllowed({ category, channel }),\n })\n }\n }\n mutation.mutate(policies, { onSuccess: () => setTouched({}) })\n },\n\n reset() {\n setTouched({})\n },\n }\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { NotificationPreference, UpsertTemplateBody } from '@adatechnology/notification-contracts'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { NOTIFICATION_QUERY_KEYS } from './queryKeys'\n\nexport function usePreferences() {\n const { client } = useNotificationContext()\n\n return useQuery({\n queryKey: NOTIFICATION_QUERY_KEYS.preferences(),\n queryFn: () => client.getPreferences(),\n })\n}\n\nexport function useUpdatePreferences() {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: (preferences: readonly NotificationPreference[]) => client.updatePreferences(preferences),\n onSuccess(saved) {\n // A resposta já é o estado persistido — usar direto evita um refetch e o piscar do painel\n // entre o valor otimista e o confirmado.\n queryClient.setQueryData(NOTIFICATION_QUERY_KEYS.preferences(), saved)\n },\n })\n}\n\nexport function useTemplates() {\n const { client } = useNotificationContext()\n\n return useQuery({\n queryKey: NOTIFICATION_QUERY_KEYS.templates(),\n queryFn: () => client.listTemplates(),\n })\n}\n\n/**\n * Salva uma VERSÃO nova do template. É o que a tela de configuração usa para o lojista mudar a copy\n * sem deploy.\n *\n * Invalida em vez de escrever no cache: o servidor decide o número da versão, então o objeto salvo\n * traz um campo que o otimista não teria como prever — e mostrar \"versão 3\" quando o banco gravou 4\n * confunde exatamente quem está tentando conferir o que salvou.\n */\nexport function useUpsertTemplate() {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: (input: UpsertTemplateBody) => client.upsertTemplate(input),\n onSuccess() {\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.templates() })\n },\n })\n}\n\n/**\n * O catálogo é config do host: muda com deploy, não com uso. `staleTime: Infinity` evita refetch a\n * cada foco de janela por um dado que não muda durante a sessão.\n */\nexport function useTemplateVariables() {\n const { client } = useNotificationContext()\n\n return useQuery({\n queryKey: NOTIFICATION_QUERY_KEYS.templateVariables(),\n queryFn: () => client.listTemplateVariables(),\n staleTime: Number.POSITIVE_INFINITY,\n })\n}\n\n/**\n * Desativa o template. Invalida a lista pelo mesmo motivo do upsert: o servidor derruba TODAS as\n * versões ativas da identidade, e um cache otimista que removesse só a linha clicada mostraria a\n * versão anterior de volta na tela.\n */\nexport function useDeactivateTemplate() {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: (id: string) => client.deactivateTemplate(id),\n onSuccess() {\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.templates() })\n },\n })\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * Quando o aviso chegou, dito do jeito que se diz em voz alta.\n *\n * `Intl` faz o trabalho de idioma — o pacote não guarda \"há\", \"ago\" nem nome de mês em lugar nenhum,\n * o que manteria `web.md` §6 de pé e ainda assim quebraria no primeiro locale novo. O que é decisão\n * nossa são as faixas: segundo, minuto, hora, dia, e a virada para data absoluta.\n */\n\nconst SECOND = 1_000\nconst MINUTE = 60 * SECOND\nconst HOUR = 60 * MINUTE\nconst DAY = 24 * HOUR\n\n/**\n * Passada uma semana, o relativo para de informar e passa a exigir conta: \"há 43 dias\" é um convite\n * a abrir o calendário. A data, o leitor lê.\n */\nconst RELATIVE_HORIZON = 7 * DAY\n\nfunction parseInstant(isoTimestamp: string): Date | undefined {\n const instant = new Date(isoTimestamp)\n return Number.isNaN(instant.getTime()) ? undefined : instant\n}\n\nexport function formatNotificationTime(isoTimestamp: string, locale: string, now: Date): string {\n const instant = parseInstant(isoTimestamp)\n if (!instant) return ''\n\n // Relógio do navegador adiantado é rotina; anunciar \"daqui a 2 minutos\" para algo que já chegou\n // não é. O piso em zero transforma o desvio em \"agora\", que é o que de fato aconteceu.\n const elapsed = Math.max(0, now.getTime() - instant.getTime())\n\n if (elapsed >= RELATIVE_HORIZON) {\n return new Intl.DateTimeFormat(locale, { day: '2-digit', month: '2-digit', year: 'numeric' }).format(instant)\n }\n\n // `numeric: 'auto'` é o que troca \"há 1 dia\" por \"ontem\" e \"há 0 segundos\" por \"agora\".\n const relative = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' })\n\n if (elapsed < MINUTE) return relative.format(0, 'second')\n if (elapsed < HOUR) return relative.format(-Math.floor(elapsed / MINUTE), 'minute')\n if (elapsed < DAY) return relative.format(-Math.floor(elapsed / HOUR), 'hour')\n return relative.format(-Math.floor(elapsed / DAY), 'day')\n}\n\n/** O instante inteiro, para o `title` — é a hora que o texto relativo esconde. */\nexport function formatNotificationTimestamp(isoTimestamp: string, locale: string): string {\n const instant = parseInstant(isoTimestamp)\n if (!instant) return ''\n\n return new Intl.DateTimeFormat(locale, { dateStyle: 'short', timeStyle: 'short' }).format(instant)\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * A lógica do editor de template, que estava reimplementada no host.\n *\n * Ela veio de `quickcart/modules/notifications/hooks/useNotificationSettings.hook.ts` — 207 linhas\n * que o segundo produto reescreveria, com as mesmas quatro decisões sutis para redescobrir. Elas\n * são o motivo de isto ser pacote e não exemplo:\n *\n * 1. **Versão ativa mais alta.** A rota devolve o histórico inteiro, correto para auditoria; na tela\n * isso faz a mesma mensagem aparecer três vezes e ninguém sabe qual está no ar.\n * 2. **Seleção pelo id, não pelo objeto.** Salvar cria versão nova no servidor; segurando o objeto,\n * o editor mostra a versão que já não é a ativa e a gravação seguinte parte de um texto velho.\n * 3. **Campo vazio é ausência.** Gravar `subject: ''` faz o renderer tratar como subject existente e\n * derivar título vazio, em vez de cair no fallback que usa o corpo.\n * 4. **Preview pelo `renderTemplate` do contracts**, o mesmo do envio. Reimplementar a interpolação\n * dá um preview que confere hoje e mente quando o renderer mudar.\n *\n * A criação de template novo trouxe uma quinta: **a identidade é imutável depois de criada**.\n * `key`+`channel`+`locale` é o que o `sendNotification` procura; deixar editar num template\n * existente não seria edição, seria criar outro e deixar o antigo órfão no ar.\n */\n\nimport { useMemo, useState } from 'react'\nimport {\n PREVIEW_VIEWPORT_BY_CHANNEL,\n buildPreviewPayload,\n diffTemplateVariables,\n renderTemplate,\n} from '@adatechnology/notification-contracts'\nimport type {\n NotificationChannel,\n NotificationTemplate,\n PreviewViewportSpec,\n RenderedTemplatePreview,\n TemplateVariableDefinition,\n TemplateVariableDiff,\n UpsertTemplateBody,\n} from '@adatechnology/notification-contracts'\n\nimport { useDeactivateTemplate, useTemplateVariables, useTemplates, useUpsertTemplate } from './usePreferences'\n\nexport type TemplateDraft = {\n readonly key: string\n readonly channel: string\n readonly locale: string\n readonly subject: string\n readonly body: string\n readonly whatsappTemplateName: string\n}\n\nexport type TemplateDraftField = 'subject' | 'body'\n\nexport type TemplatePreviewFrame = PreviewViewportSpec & {\n readonly rendered: RenderedTemplatePreview\n}\n\nfunction toDraft(template: NotificationTemplate): TemplateDraft {\n return {\n key: template.key,\n channel: template.channel,\n locale: template.locale,\n subject: template.subject ?? '',\n body: template.body,\n whatsappTemplateName: template.whatsappTemplateName ?? '',\n }\n}\n\nfunction buildEmptyDraft(params: { channel: string; locale: string }): TemplateDraft {\n return { key: '', channel: params.channel, locale: params.locale, subject: '', body: '', whatsappTemplateName: '' }\n}\n\nexport type UseTemplateEditorParams = {\n /**\n * Valores de exemplo do preview. Hoje o padrão vem do catálogo de variáveis do servidor; isto\n * continua existindo para o produto sobrescrever um exemplo pontual sem mexer no catálogo.\n */\n readonly previewPayload?: Readonly<Record<string, unknown>>\n /** Canal e locale de um template novo, antes de o operador escolher. */\n readonly defaultChannel?: string\n readonly defaultLocale?: string\n}\n\nexport type UseTemplateEditorResult = {\n readonly templates: readonly NotificationTemplate[]\n readonly isLoading: boolean\n readonly isSaving: boolean\n readonly isDeactivating: boolean\n readonly error: string | undefined\n readonly selected: NotificationTemplate | undefined\n readonly draft: TemplateDraft | undefined\n /** `true` enquanto o rascunho é um template que ainda não existe no servidor. */\n readonly isNew: boolean\n /** Identidade travada — `false` só durante a criação. */\n readonly isIdentityLocked: boolean\n /** Os dois quadros do preview, na ordem em que a tela desenha. */\n readonly previews: readonly TemplatePreviewFrame[]\n /** As variáveis que esta `key` declara. Vazio quando o catálogo não conhece a chave. */\n readonly variables: readonly TemplateVariableDefinition[]\n readonly variableDiff: TemplateVariableDiff\n readonly isDirty: boolean\n readonly canSave: boolean\n select: (template: NotificationTemplate) => void\n startNew: () => void\n update: (patch: Partial<TemplateDraft>) => void\n /** Insere `{{nome}}` na posição do cursor. O operador nunca digita o nome à mão. */\n insertVariable: (params: { name: string; field: TemplateDraftField; cursorIndex: number }) => void\n save: () => void\n deactivate: (id: string) => void\n clear: () => void\n}\n\nexport function useTemplateEditor(params: UseTemplateEditorParams = {}): UseTemplateEditorResult {\n const templatesQuery = useTemplates()\n const variablesQuery = useTemplateVariables()\n const upsert = useUpsertTemplate()\n const deactivateMutation = useDeactivateTemplate()\n\n const [selectedId, setSelectedId] = useState<string | undefined>(undefined)\n const [draft, setDraft] = useState<TemplateDraft | undefined>(undefined)\n const [isNew, setIsNew] = useState(false)\n\n /** Decisão 1: só a ativa mais alta por `key`+`channel`+`locale` — a mesma que o envio faz. */\n const templates = useMemo(() => {\n const activeByIdentity = new Map<string, NotificationTemplate>()\n for (const template of templatesQuery.data ?? []) {\n if (!template.active) continue\n const identity = `${template.key}:${template.channel}:${template.locale}`\n const current = activeByIdentity.get(identity)\n if (!current || template.version > current.version) activeByIdentity.set(identity, template)\n }\n return [...activeByIdentity.values()].sort(\n (left, right) => left.key.localeCompare(right.key) || left.channel.localeCompare(right.channel),\n )\n }, [templatesQuery.data])\n\n /** Decisão 2: derivado do id. */\n const selected = useMemo(() => templates.find((template) => template.id === selectedId), [templates, selectedId])\n\n const variables = useMemo(() => (draft ? (variablesQuery.data?.[draft.key] ?? []) : []), [variablesQuery.data, draft])\n\n const variableDiff = useMemo(\n () =>\n diffTemplateVariables({\n body: draft?.body ?? '',\n subject: draft?.subject || undefined,\n // Chave sem entrada no catálogo é catálogo não declarado, não catálogo vazio — o servidor\n // aceita qualquer variável nesse caso, e a tela não pode acusar o que a rota vai gravar.\n variables: draft && variablesQuery.data?.[draft.key] ? variables : undefined,\n }),\n [draft, variables, variablesQuery.data],\n )\n\n /** Decisão 4: o mesmo render do envio, uma vez por viewport do canal. */\n const previews = useMemo<readonly TemplatePreviewFrame[]>(() => {\n if (!draft) return []\n const payload = { ...buildPreviewPayload(variables), ...(params.previewPayload ?? {}) }\n const rendered = renderTemplate({\n channel: draft.channel,\n subject: draft.subject || undefined,\n body: draft.body,\n payload,\n })\n const viewports = PREVIEW_VIEWPORT_BY_CHANNEL[draft.channel as NotificationChannel] ?? []\n return viewports.map((viewport) => ({ ...viewport, rendered }))\n }, [draft, variables, params.previewPayload])\n\n const isDirty = useMemo(() => {\n if (!draft) return false\n if (isNew) return draft.key.length > 0 || draft.body.length > 0\n if (!selected) return false\n const original = toDraft(selected)\n return (Object.keys(original) as (keyof TemplateDraft)[]).some((field) => original[field] !== draft[field])\n }, [draft, selected, isNew])\n\n return {\n templates,\n isLoading: templatesQuery.isLoading,\n isSaving: upsert.isPending,\n isDeactivating: deactivateMutation.isPending,\n error: upsert.error?.message ?? deactivateMutation.error?.message,\n selected,\n draft,\n isNew,\n isIdentityLocked: !isNew,\n previews,\n variables,\n variableDiff,\n isDirty,\n // A variável desconhecida é recusada pelo servidor com 400; bloquear o botão evita a ida\n // perdida, sem virar a única defesa — a validação continua sendo a da rota.\n canSave: Boolean(draft?.key && draft.body) && variableDiff.unknown.length === 0,\n\n select(template) {\n setSelectedId(template.id)\n setDraft(toDraft(template))\n setIsNew(false)\n },\n\n startNew() {\n setSelectedId(undefined)\n setIsNew(true)\n setDraft(buildEmptyDraft({ channel: params.defaultChannel ?? 'email', locale: params.defaultLocale ?? 'pt-BR' }))\n },\n\n update(patch) {\n setDraft((current) => (current ? { ...current, ...patch } : current))\n },\n\n insertVariable({ name, field, cursorIndex }) {\n setDraft((current) => {\n if (!current) return current\n const text = current[field]\n const at = Math.min(Math.max(cursorIndex, 0), text.length)\n return { ...current, [field]: `${text.slice(0, at)}{{${name}}}${text.slice(at)}` }\n })\n },\n\n save() {\n if (!draft) return\n const body: UpsertTemplateBody = {\n key: draft.key,\n channel: draft.channel as UpsertTemplateBody['channel'],\n locale: draft.locale,\n active: true,\n body: draft.body,\n ...(draft.subject ? { subject: draft.subject } : {}),\n ...(draft.whatsappTemplateName ? { whatsappTemplateName: draft.whatsappTemplateName } : {}),\n }\n upsert.mutate(body, {\n onSuccess: () => {\n setSelectedId(undefined)\n setDraft(undefined)\n setIsNew(false)\n },\n })\n },\n\n deactivate(id) {\n deactivateMutation.mutate(id, {\n onSuccess: () => {\n if (selectedId !== id) return\n setSelectedId(undefined)\n setDraft(undefined)\n },\n })\n },\n\n clear() {\n setSelectedId(undefined)\n setDraft(undefined)\n setIsNew(false)\n },\n }\n}\n"],"mappings":"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","names":["useQueryClient","useQueryClient","useMemo","useMutation","useQuery","useQueryClient","useQueryClient","useQuery","useMutation","useMemo","useMutation","useQuery","useQueryClient","useQuery","useQueryClient","useMutation","useMemo","useState","useState","useMemo"]}