@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.
@@ -4,7 +4,7 @@ import * as _adatechnology_notification_client from '@adatechnology/notification
4
4
  import { NotificationClient } from '@adatechnology/notification-client';
5
5
  import * as _tanstack_react_query from '@tanstack/react-query';
6
6
  import * as _adatechnology_notification_contracts from '@adatechnology/notification-contracts';
7
- import { NotificationSummary, NotificationPreference, NotificationTemplate, RenderedTemplatePreview } from '@adatechnology/notification-contracts';
7
+ import { NotificationSummary, NotificationPreference, PreviewViewportSpec, RenderedTemplatePreview, NotificationTemplate, TemplateVariableDefinition, TemplateVariableDiff } from '@adatechnology/notification-contracts';
8
8
 
9
9
  var ptBR = {
10
10
  "bell.label": "Notificações",
@@ -52,7 +52,26 @@ var ptBR = {
52
52
  "settings.saving": "Salvando…",
53
53
  "settings.unchanged": "Nada mudou ainda.",
54
54
  "settings.close": "Fechar",
55
- "settings.version": "v"
55
+ "settings.version": "v",
56
+ "settings.newTemplate": "Nova mensagem",
57
+ "settings.remove": "Remover",
58
+ "settings.key": "Chave da notificação",
59
+ "settings.keyHint": "Não muda depois de criada — é por ela que o envio acha a mensagem.",
60
+ "settings.channel": "Canal",
61
+ "settings.locale": "Idioma",
62
+ "settings.variablesTitle": "Variáveis",
63
+ "settings.variablesHint": "Clique para inserir no ponto onde o cursor estava.",
64
+ "settings.unknownVariables": "Variáveis que não existem nesta notificação:",
65
+ "settings.viewport.desktop": "Computador",
66
+ "settings.viewport.mobile": "Celular",
67
+ "settings.constraint.title": "Assunto passa do limite do canal:",
68
+ "settings.constraint.body": "Texto passa do limite do canal:",
69
+ "settings.tab.messages": "Mensagens",
70
+ "settings.tab.routing": "Roteamento",
71
+ "settings.policiesTitle": "Canais permitidos por assunto",
72
+ "settings.policiesHint": "Decisão da empresa. Canal desligado aqui não sai, mesmo que o destinatário tenha pedido.",
73
+ "settings.savePolicies": "Salvar roteamento",
74
+ "settings.discard": "Descartar"
56
75
  };
57
76
 
58
77
  /**
@@ -133,6 +152,37 @@ type UseNotificationStreamOptions = {
133
152
  */
134
153
  declare function useNotificationStream(options?: UseNotificationStreamOptions): void;
135
154
 
155
+ /**
156
+ * Copyright (c) 2026 Ada Technology. MIT License.
157
+ *
158
+ * O roteamento: por quais canais cada categoria PODE sair, decidido pela empresa.
159
+ *
160
+ * É a camada que faltava. O chamador já podia enumerar canais em cada `sendNotification`, e quem
161
+ * recebe já tinha preferência — mas "cobrança nunca sai por SMS" não cabia em nenhum dos dois:
162
+ * o primeiro espalha a decisão por todo o código, o segundo deixa ela na mão do destinatário.
163
+ *
164
+ * O modelo aqui é de teto, não de piso: desligar barra o canal para todo mundo; ligar apenas
165
+ * permite, e a preferência de quem recebe continua valendo por cima.
166
+ */
167
+ type UseCategoryPoliciesResult = {
168
+ readonly isLoading: boolean;
169
+ readonly isSaving: boolean;
170
+ readonly error: string | undefined;
171
+ readonly isDirty: boolean;
172
+ /** Linha ausente é permitido — o mesmo default do servidor, para a tela não mentir. */
173
+ isAllowed: (params: {
174
+ category: string;
175
+ channel: string;
176
+ }) => boolean;
177
+ toggle: (params: {
178
+ category: string;
179
+ channel: string;
180
+ }) => void;
181
+ save: () => void;
182
+ reset: () => void;
183
+ };
184
+ declare function useCategoryPolicies(): UseCategoryPoliciesResult;
185
+
136
186
  declare function usePreferences(): _tanstack_react_query.UseQueryResult<NoInfer<readonly NotificationPreference[]>, Error>;
137
187
  declare function useUpdatePreferences(): _tanstack_react_query.UseMutationResult<readonly NotificationPreference[], Error, readonly NotificationPreference[], unknown>;
138
188
  declare function useTemplates(): _tanstack_react_query.UseQueryResult<NoInfer<readonly _adatechnology_notification_contracts.NotificationTemplate[]>, Error>;
@@ -153,6 +203,17 @@ declare function useUpsertTemplate(): _tanstack_react_query.UseMutationResult<_a
153
203
  subject?: string | undefined;
154
204
  whatsappTemplateName?: string | undefined;
155
205
  }, unknown>;
206
+ /**
207
+ * O catálogo é config do host: muda com deploy, não com uso. `staleTime: Infinity` evita refetch a
208
+ * cada foco de janela por um dado que não muda durante a sessão.
209
+ */
210
+ declare function useTemplateVariables(): _tanstack_react_query.UseQueryResult<NoInfer<Readonly<Record<string, readonly _adatechnology_notification_contracts.TemplateVariableDefinition[]>>>, Error>;
211
+ /**
212
+ * Desativa o template. Invalida a lista pelo mesmo motivo do upsert: o servidor derruba TODAS as
213
+ * versões ativas da identidade, e um cache otimista que removesse só a linha clicada mostraria a
214
+ * versão anterior de volta na tela.
215
+ */
216
+ declare function useDeactivateTemplate(): _tanstack_react_query.UseMutationResult<void, Error, string, unknown>;
156
217
 
157
218
  /**
158
219
  * Copyright (c) 2026 Ada Technology. MIT License.
@@ -173,6 +234,8 @@ declare const NOTIFICATION_QUERY_KEYS: {
173
234
  readonly unreadCount: () => readonly ["notifications", "unread-count"];
174
235
  readonly preferences: () => readonly ["notifications", "preferences"];
175
236
  readonly templates: () => readonly ["notifications", "templates"];
237
+ readonly templateVariables: () => readonly ["notifications", "template-variables"];
238
+ readonly categoryPolicies: () => readonly ["notifications", "category-policies"];
176
239
  };
177
240
 
178
241
  /**
@@ -205,6 +268,10 @@ declare function formatNotificationTimestamp(isoTimestamp: string, locale: strin
205
268
  * derivar título vazio, em vez de cair no fallback que usa o corpo.
206
269
  * 4. **Preview pelo `renderTemplate` do contracts**, o mesmo do envio. Reimplementar a interpolação
207
270
  * dá um preview que confere hoje e mente quando o renderer mudar.
271
+ *
272
+ * A criação de template novo trouxe uma quinta: **a identidade é imutável depois de criada**.
273
+ * `key`+`channel`+`locale` é o que o `sendNotification` procura; deixar editar num template
274
+ * existente não seria edição, seria criar outro e deixar o antigo órfão no ar.
208
275
  */
209
276
 
210
277
  type TemplateDraft = {
@@ -215,27 +282,52 @@ type TemplateDraft = {
215
282
  readonly body: string;
216
283
  readonly whatsappTemplateName: string;
217
284
  };
285
+ type TemplateDraftField = 'subject' | 'body';
286
+ type TemplatePreviewFrame = PreviewViewportSpec & {
287
+ readonly rendered: RenderedTemplatePreview;
288
+ };
218
289
  type UseTemplateEditorParams = {
219
290
  /**
220
- * Valores de exemplo do preview. É do produto: só ele sabe que `{{shortCode}}` é "QC-1042".
221
- * Ausente, o preview mostra os campos vazios — que é o que o destinatário veria mesmo.
291
+ * Valores de exemplo do preview. Hoje o padrão vem do catálogo de variáveis do servidor; isto
292
+ * continua existindo para o produto sobrescrever um exemplo pontual sem mexer no catálogo.
222
293
  */
223
294
  readonly previewPayload?: Readonly<Record<string, unknown>>;
295
+ /** Canal e locale de um template novo, antes de o operador escolher. */
296
+ readonly defaultChannel?: string;
297
+ readonly defaultLocale?: string;
224
298
  };
225
299
  type UseTemplateEditorResult = {
226
300
  readonly templates: readonly NotificationTemplate[];
227
301
  readonly isLoading: boolean;
228
302
  readonly isSaving: boolean;
303
+ readonly isDeactivating: boolean;
229
304
  readonly error: string | undefined;
230
305
  readonly selected: NotificationTemplate | undefined;
231
306
  readonly draft: TemplateDraft | undefined;
232
- readonly preview: RenderedTemplatePreview | undefined;
307
+ /** `true` enquanto o rascunho é um template que ainda não existe no servidor. */
308
+ readonly isNew: boolean;
309
+ /** Identidade travada — `false` só durante a criação. */
310
+ readonly isIdentityLocked: boolean;
311
+ /** Os dois quadros do preview, na ordem em que a tela desenha. */
312
+ readonly previews: readonly TemplatePreviewFrame[];
313
+ /** As variáveis que esta `key` declara. Vazio quando o catálogo não conhece a chave. */
314
+ readonly variables: readonly TemplateVariableDefinition[];
315
+ readonly variableDiff: TemplateVariableDiff;
233
316
  readonly isDirty: boolean;
317
+ readonly canSave: boolean;
234
318
  select: (template: NotificationTemplate) => void;
319
+ startNew: () => void;
235
320
  update: (patch: Partial<TemplateDraft>) => void;
321
+ /** Insere `{{nome}}` na posição do cursor. O operador nunca digita o nome à mão. */
322
+ insertVariable: (params: {
323
+ name: string;
324
+ field: TemplateDraftField;
325
+ cursorIndex: number;
326
+ }) => void;
236
327
  save: () => void;
328
+ deactivate: (id: string) => void;
237
329
  clear: () => void;
238
330
  };
239
331
  declare function useTemplateEditor(params?: UseTemplateEditorParams): UseTemplateEditorResult;
240
332
 
241
- export { DEFAULT_LOCALE, NOTIFICATION_QUERY_KEYS, type NotificationContextValue, type NotificationLocale, type NotificationMessages, NotificationProvider, type NotificationProviderProps, type NotificationTheme, type TemplateDraft, type UseNotificationStreamOptions, type UseNotificationsOptions, type UseTemplateEditorParams, type UseTemplateEditorResult, flattenNotificationPages, formatNotificationTime, formatNotificationTimestamp, resolveMessages, useDeleteNotification, useMarkAllAsRead, useMarkAsRead, useNotificationContext, useNotificationStream, useNotifications, usePreferences, useTemplateEditor, useTemplates, useUnreadCount, useUpdatePreferences, useUpsertTemplate };
333
+ export { DEFAULT_LOCALE, NOTIFICATION_QUERY_KEYS, type NotificationContextValue, type NotificationLocale, type NotificationMessages, NotificationProvider, type NotificationProviderProps, type NotificationTheme, type TemplateDraft, type TemplateDraftField, type TemplatePreviewFrame, type UseCategoryPoliciesResult, type UseNotificationStreamOptions, type UseNotificationsOptions, type UseTemplateEditorParams, type UseTemplateEditorResult, flattenNotificationPages, formatNotificationTime, formatNotificationTimestamp, resolveMessages, useCategoryPolicies, useDeactivateTemplate, useDeleteNotification, useMarkAllAsRead, useMarkAsRead, useNotificationContext, useNotificationStream, useNotifications, usePreferences, useTemplateEditor, useTemplateVariables, useTemplates, useUnreadCount, useUpdatePreferences, useUpsertTemplate };
package/dist/headless.js CHANGED
@@ -6,6 +6,8 @@ import {
6
6
  formatNotificationTime,
7
7
  formatNotificationTimestamp,
8
8
  resolveMessages,
9
+ useCategoryPolicies,
10
+ useDeactivateTemplate,
9
11
  useDeleteNotification,
10
12
  useMarkAllAsRead,
11
13
  useMarkAsRead,
@@ -14,11 +16,12 @@ import {
14
16
  useNotifications,
15
17
  usePreferences,
16
18
  useTemplateEditor,
19
+ useTemplateVariables,
17
20
  useTemplates,
18
21
  useUnreadCount,
19
22
  useUpdatePreferences,
20
23
  useUpsertTemplate
21
- } from "./chunk-I6WQKOCP.js";
24
+ } from "./chunk-QBQ62VZ3.js";
22
25
  export {
23
26
  DEFAULT_LOCALE,
24
27
  NOTIFICATION_QUERY_KEYS,
@@ -27,6 +30,8 @@ export {
27
30
  formatNotificationTime,
28
31
  formatNotificationTimestamp,
29
32
  resolveMessages,
33
+ useCategoryPolicies,
34
+ useDeactivateTemplate,
30
35
  useDeleteNotification,
31
36
  useMarkAllAsRead,
32
37
  useMarkAsRead,
@@ -35,6 +40,7 @@ export {
35
40
  useNotifications,
36
41
  usePreferences,
37
42
  useTemplateEditor,
43
+ useTemplateVariables,
38
44
  useTemplates,
39
45
  useUnreadCount,
40
46
  useUpdatePreferences,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { DEFAULT_LOCALE, NOTIFICATION_QUERY_KEYS, NotificationContextValue, NotificationLocale, NotificationMessages, NotificationProvider, NotificationProviderProps, NotificationTheme, TemplateDraft, UseNotificationStreamOptions, UseNotificationsOptions, UseTemplateEditorParams, UseTemplateEditorResult, flattenNotificationPages, formatNotificationTime, formatNotificationTimestamp, resolveMessages, useDeleteNotification, useMarkAllAsRead, useMarkAsRead, useNotificationContext, useNotificationStream, useNotifications, usePreferences, useTemplateEditor, useTemplates, useUnreadCount, useUpdatePreferences, useUpsertTemplate } from './headless.js';
1
+ export { DEFAULT_LOCALE, NOTIFICATION_QUERY_KEYS, NotificationContextValue, NotificationLocale, NotificationMessages, NotificationProvider, NotificationProviderProps, NotificationTheme, TemplateDraft, TemplateDraftField, TemplatePreviewFrame, UseCategoryPoliciesResult, UseNotificationStreamOptions, UseNotificationsOptions, UseTemplateEditorParams, UseTemplateEditorResult, flattenNotificationPages, formatNotificationTime, formatNotificationTimestamp, resolveMessages, useCategoryPolicies, useDeactivateTemplate, useDeleteNotification, useMarkAllAsRead, useMarkAsRead, useNotificationContext, useNotificationStream, useNotifications, usePreferences, useTemplateEditor, useTemplateVariables, useTemplates, useUnreadCount, useUpdatePreferences, useUpsertTemplate } from './headless.js';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
4
  import { NotificationSummary, NotificationTemplate } from '@adatechnology/notification-contracts';
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ import {
6
6
  formatNotificationTime,
7
7
  formatNotificationTimestamp,
8
8
  resolveMessages,
9
+ useCategoryPolicies,
10
+ useDeactivateTemplate,
9
11
  useDeleteNotification,
10
12
  useMarkAllAsRead,
11
13
  useMarkAsRead,
@@ -14,11 +16,12 @@ import {
14
16
  useNotifications,
15
17
  usePreferences,
16
18
  useTemplateEditor,
19
+ useTemplateVariables,
17
20
  useTemplates,
18
21
  useUnreadCount,
19
22
  useUpdatePreferences,
20
23
  useUpsertTemplate
21
- } from "./chunk-I6WQKOCP.js";
24
+ } from "./chunk-QBQ62VZ3.js";
22
25
 
23
26
  // src/components/NotificationBell.tsx
24
27
  import { Bell } from "lucide-react";
@@ -295,6 +298,7 @@ function NotificationsWorkspace({
295
298
  }
296
299
 
297
300
  // src/components/NotificationSettingsWorkspace.tsx
301
+ import { useState as useState2 } from "react";
298
302
  import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
299
303
  function NotificationSettingsWorkspace({
300
304
  labels: labelsOverride,
@@ -311,7 +315,19 @@ function NotificationSettingsWorkspace({
311
315
  const label = (key) => labelsOverride?.[key] ?? messages[key] ?? key;
312
316
  const preferencesQuery = usePreferences();
313
317
  const updatePreferences = useUpdatePreferences();
314
- const editor = useTemplateEditor(previewPayload ? { previewPayload } : {});
318
+ const editor = useTemplateEditor({
319
+ ...previewPayload ? { previewPayload } : {},
320
+ ...channels[0] ? { defaultChannel: channels[0].id } : {}
321
+ });
322
+ const [cursor, setCursor] = useState2({
323
+ field: "body",
324
+ cursorIndex: 0
325
+ });
326
+ function rememberCursor(field, cursorIndex) {
327
+ setCursor({ field, cursorIndex: cursorIndex ?? 0 });
328
+ }
329
+ const policies = useCategoryPolicies();
330
+ const [tab, setTab] = useState2("messages");
315
331
  function isEnabled(category, channel) {
316
332
  const row = (preferencesQuery.data ?? []).find(
317
333
  (preference) => preference.category === category && preference.channel === channel
@@ -336,7 +352,42 @@ function NotificationSettingsWorkspace({
336
352
  ] }),
337
353
  renderHeaderActions?.()
338
354
  ] }),
339
- /* @__PURE__ */ jsxs6("section", { className: "adn-settings__channels", children: [
355
+ /* @__PURE__ */ jsx6("div", { className: "adn-settings__tabs", role: "tablist", children: ["messages", "routing"].map((id) => /* @__PURE__ */ jsx6(
356
+ "button",
357
+ {
358
+ type: "button",
359
+ role: "tab",
360
+ "aria-selected": tab === id,
361
+ className: tab === id ? "adn-settings__tab adn-settings__tab--active" : "adn-settings__tab",
362
+ onClick: () => setTab(id),
363
+ children: label(`settings.tab.${id}`)
364
+ },
365
+ id
366
+ )) }),
367
+ tab === "routing" && /* @__PURE__ */ jsxs6("section", { className: "adn-settings__policies", children: [
368
+ /* @__PURE__ */ jsx6("h2", { className: "adn-settings__section-title", children: label("settings.policiesTitle") }),
369
+ /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.policiesHint") }),
370
+ categories.map((category) => /* @__PURE__ */ jsxs6("div", { className: "adn-settings__category", children: [
371
+ /* @__PURE__ */ jsx6("p", { className: "adn-settings__category-title", children: category.label }),
372
+ /* @__PURE__ */ jsx6("div", { className: "adn-settings__category-channels", children: channels.map((channel) => /* @__PURE__ */ jsxs6("label", { className: "adn-settings__toggle", children: [
373
+ /* @__PURE__ */ jsx6(
374
+ "input",
375
+ {
376
+ type: "checkbox",
377
+ checked: policies.isAllowed({ category: category.id, channel: channel.id }),
378
+ onChange: () => policies.toggle({ category: category.id, channel: channel.id })
379
+ }
380
+ ),
381
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__channel-label", children: channel.label })
382
+ ] }, channel.id)) })
383
+ ] }, category.id)),
384
+ policies.error && /* @__PURE__ */ jsx6("p", { className: "adn-settings__error", children: policies.error }),
385
+ /* @__PURE__ */ jsxs6("div", { className: "adn-settings__actions", children: [
386
+ /* @__PURE__ */ jsx6("button", { type: "button", onClick: policies.save, disabled: !policies.isDirty || policies.isSaving, children: policies.isSaving ? label("settings.saving") : label("settings.savePolicies") }),
387
+ policies.isDirty && /* @__PURE__ */ jsx6("button", { type: "button", onClick: policies.reset, children: label("settings.discard") })
388
+ ] })
389
+ ] }),
390
+ tab === "routing" && /* @__PURE__ */ jsxs6("section", { className: "adn-settings__channels", children: [
340
391
  /* @__PURE__ */ jsx6("h2", { className: "adn-settings__section-title", children: label("settings.channelsTitle") }),
341
392
  /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.channelsHint") }),
342
393
  categories.map((category) => /* @__PURE__ */ jsxs6("div", { className: "adn-settings__category", children: [
@@ -359,33 +410,49 @@ function NotificationSettingsWorkspace({
359
410
  ] }, channel.id)) })
360
411
  ] }, category.id))
361
412
  ] }),
362
- /* @__PURE__ */ jsxs6("div", { className: "adn-settings__templates", children: [
413
+ tab === "messages" && /* @__PURE__ */ jsxs6("div", { className: "adn-settings__templates", children: [
363
414
  /* @__PURE__ */ jsxs6("section", { className: "adn-settings__template-list", children: [
364
- /* @__PURE__ */ jsxs6("header", { children: [
365
- /* @__PURE__ */ jsx6("h2", { className: "adn-settings__section-title", children: label("settings.templatesTitle") }),
366
- /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.templatesHint") })
415
+ /* @__PURE__ */ jsxs6("header", { className: "adn-settings__list-header", children: [
416
+ /* @__PURE__ */ jsxs6("div", { children: [
417
+ /* @__PURE__ */ jsx6("h2", { className: "adn-settings__section-title", children: label("settings.templatesTitle") }),
418
+ /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.templatesHint") })
419
+ ] }),
420
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "adn-settings__new", onClick: editor.startNew, children: label("settings.newTemplate") })
367
421
  ] }),
368
422
  editor.isLoading && /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("list.loading") }),
369
423
  !editor.isLoading && editor.templates.length === 0 && /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.templatesEmpty") }),
370
- /* @__PURE__ */ jsx6("ul", { children: editor.templates.map((template) => /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsxs6(
371
- "button",
372
- {
373
- type: "button",
374
- onClick: () => editor.select(template),
375
- className: editor.selected?.id === template.id ? "adn-settings__row--active" : void 0,
376
- children: [
377
- /* @__PURE__ */ jsxs6("span", { className: "adn-settings__row-main", children: [
378
- /* @__PURE__ */ jsx6("span", { className: "adn-settings__row-title", children: templateLabelOf?.(template) ?? template.key }),
379
- /* @__PURE__ */ jsx6("span", { className: "adn-settings__row-body", children: template.body })
380
- ] }),
381
- /* @__PURE__ */ jsx6("span", { className: `adn-settings__badge adn-settings__badge--${template.channel}`, children: label(`channel.${template.channel}`) }),
382
- /* @__PURE__ */ jsxs6("span", { className: "adn-settings__version", children: [
383
- label("settings.version"),
384
- template.version
385
- ] })
386
- ]
387
- }
388
- ) }, template.id)) })
424
+ /* @__PURE__ */ jsx6("ul", { children: editor.templates.map((template) => /* @__PURE__ */ jsxs6("li", { className: "adn-settings__row", children: [
425
+ /* @__PURE__ */ jsxs6(
426
+ "button",
427
+ {
428
+ type: "button",
429
+ onClick: () => editor.select(template),
430
+ className: editor.selected?.id === template.id ? "adn-settings__row--active" : void 0,
431
+ children: [
432
+ /* @__PURE__ */ jsxs6("span", { className: "adn-settings__row-main", children: [
433
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__row-title", children: templateLabelOf?.(template) ?? template.key }),
434
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__row-body", children: template.body })
435
+ ] }),
436
+ /* @__PURE__ */ jsx6("span", { className: `adn-settings__badge adn-settings__badge--${template.channel}`, children: label(`channel.${template.channel}`) }),
437
+ /* @__PURE__ */ jsxs6("span", { className: "adn-settings__version", children: [
438
+ label("settings.version"),
439
+ template.version
440
+ ] })
441
+ ]
442
+ }
443
+ ),
444
+ /* @__PURE__ */ jsx6(
445
+ "button",
446
+ {
447
+ type: "button",
448
+ className: "adn-settings__remove",
449
+ "aria-label": `${label("settings.remove")} ${template.key}`,
450
+ disabled: editor.isDeactivating,
451
+ onClick: () => editor.deactivate(template.id),
452
+ children: label("settings.remove")
453
+ }
454
+ )
455
+ ] }, template.id)) })
389
456
  ] }),
390
457
  /* @__PURE__ */ jsxs6("section", { className: "adn-settings__editor", children: [
391
458
  !editor.draft && /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.pickTemplate") }),
@@ -404,6 +471,63 @@ function NotificationSettingsWorkspace({
404
471
  ] }),
405
472
  /* @__PURE__ */ jsx6("button", { type: "button", onClick: editor.clear, children: label("settings.close") })
406
473
  ] }),
474
+ !editor.isIdentityLocked && /* @__PURE__ */ jsxs6("div", { className: "adn-settings__identity", children: [
475
+ /* @__PURE__ */ jsxs6("label", { className: "adn-settings__field", children: [
476
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__field-label", children: label("settings.key") }),
477
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__hint", children: label("settings.keyHint") }),
478
+ /* @__PURE__ */ jsx6("input", { value: editor.draft.key, onChange: (event) => editor.update({ key: event.target.value }) })
479
+ ] }),
480
+ /* @__PURE__ */ jsxs6("label", { className: "adn-settings__field", children: [
481
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__field-label", children: label("settings.channel") }),
482
+ /* @__PURE__ */ jsx6(
483
+ "select",
484
+ {
485
+ value: editor.draft.channel,
486
+ onChange: (event) => editor.update({ channel: event.target.value }),
487
+ children: channels.map((channel) => /* @__PURE__ */ jsx6("option", { value: channel.id, children: channel.label }, channel.id))
488
+ }
489
+ )
490
+ ] }),
491
+ /* @__PURE__ */ jsxs6("label", { className: "adn-settings__field", children: [
492
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__field-label", children: label("settings.locale") }),
493
+ /* @__PURE__ */ jsx6(
494
+ "input",
495
+ {
496
+ value: editor.draft.locale,
497
+ onChange: (event) => editor.update({ locale: event.target.value })
498
+ }
499
+ )
500
+ ] })
501
+ ] }),
502
+ editor.variables.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "adn-settings__variables", children: [
503
+ /* @__PURE__ */ jsx6("p", { className: "adn-settings__field-label", children: label("settings.variablesTitle") }),
504
+ /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.variablesHint") }),
505
+ /* @__PURE__ */ jsx6("ul", { children: editor.variables.map((variable) => {
506
+ const used = editor.variableDiff.used.includes(variable.name);
507
+ const missing = editor.variableDiff.missingRequired.includes(variable.name);
508
+ return /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsxs6(
509
+ "button",
510
+ {
511
+ type: "button",
512
+ className: [
513
+ "adn-settings__variable",
514
+ used ? "adn-settings__variable--used" : "",
515
+ missing ? "adn-settings__variable--missing" : ""
516
+ ].filter(Boolean).join(" "),
517
+ onClick: () => editor.insertVariable({ ...cursor, name: variable.name }),
518
+ children: [
519
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__variable-name", children: `{{${variable.name}}}` }),
520
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__hint", children: variable.example })
521
+ ]
522
+ }
523
+ ) }, variable.name);
524
+ }) })
525
+ ] }),
526
+ editor.variableDiff.unknown.length > 0 && /* @__PURE__ */ jsxs6("p", { className: "adn-settings__error", children: [
527
+ label("settings.unknownVariables"),
528
+ " ",
529
+ editor.variableDiff.unknown.join(", ")
530
+ ] }),
407
531
  /* @__PURE__ */ jsxs6("label", { className: "adn-settings__field", children: [
408
532
  /* @__PURE__ */ jsx6("span", { className: "adn-settings__field-label", children: label("settings.subject") }),
409
533
  /* @__PURE__ */ jsx6("span", { className: "adn-settings__hint", children: label("settings.subjectHint") }),
@@ -411,7 +535,8 @@ function NotificationSettingsWorkspace({
411
535
  "input",
412
536
  {
413
537
  value: editor.draft.subject,
414
- onChange: (event) => editor.update({ subject: event.target.value })
538
+ onChange: (event) => editor.update({ subject: event.target.value }),
539
+ onSelect: (event) => rememberCursor("subject", event.currentTarget.selectionStart)
415
540
  }
416
541
  )
417
542
  ] }),
@@ -422,7 +547,8 @@ function NotificationSettingsWorkspace({
422
547
  {
423
548
  rows: 4,
424
549
  value: editor.draft.body,
425
- onChange: (event) => editor.update({ body: event.target.value })
550
+ onChange: (event) => editor.update({ body: event.target.value }),
551
+ onSelect: (event) => rememberCursor("body", event.currentTarget.selectionStart)
426
552
  }
427
553
  )
428
554
  ] }),
@@ -431,14 +557,31 @@ function NotificationSettingsWorkspace({
431
557
  value: editor.draft.whatsappTemplateName,
432
558
  onChange: (value) => editor.update({ whatsappTemplateName: value })
433
559
  }),
434
- editor.preview && /* @__PURE__ */ jsxs6("div", { className: "adn-settings__preview", children: [
560
+ editor.previews.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "adn-settings__previews", children: [
435
561
  /* @__PURE__ */ jsx6("p", { className: "adn-settings__preview-title", children: label("settings.previewTitle") }),
436
- /* @__PURE__ */ jsx6("p", { className: "adn-settings__preview-subject", children: editor.preview.title }),
437
- /* @__PURE__ */ jsx6("p", { className: "adn-settings__preview-body", children: editor.preview.body })
562
+ /* @__PURE__ */ jsx6("div", { className: "adn-settings__preview-frames", children: editor.previews.map((frame) => /* @__PURE__ */ jsxs6("figure", { className: "adn-settings__preview", children: [
563
+ /* @__PURE__ */ jsxs6("figcaption", { className: "adn-settings__hint", children: [
564
+ label(`settings.viewport.${frame.viewport}`),
565
+ " \xB7 ",
566
+ frame.width,
567
+ "px"
568
+ ] }),
569
+ /* @__PURE__ */ jsxs6("div", { className: "adn-settings__preview-frame", style: { width: frame.width }, children: [
570
+ /* @__PURE__ */ jsx6("p", { className: "adn-settings__preview-subject", children: frame.rendered.title }),
571
+ /* @__PURE__ */ jsx6("p", { className: "adn-settings__preview-body", children: frame.rendered.body })
572
+ ] }),
573
+ frame.rendered.constraints.filter((constraint) => constraint.exceeded).map((constraint) => /* @__PURE__ */ jsxs6("p", { className: "adn-settings__preview-warning", children: [
574
+ label(`settings.constraint.${constraint.field}`),
575
+ " ",
576
+ constraint.actual,
577
+ "/",
578
+ constraint.limit
579
+ ] }, constraint.field))
580
+ ] }, frame.viewport)) })
438
581
  ] }),
439
582
  editor.error && /* @__PURE__ */ jsx6("p", { className: "adn-settings__error", children: editor.error }),
440
583
  /* @__PURE__ */ jsxs6("div", { className: "adn-settings__actions", children: [
441
- /* @__PURE__ */ jsx6("button", { type: "button", onClick: editor.save, disabled: !editor.isDirty || editor.isSaving, children: editor.isSaving ? label("settings.saving") : label("settings.save") }),
584
+ /* @__PURE__ */ jsx6("button", { type: "button", onClick: editor.save, disabled: !editor.isDirty || !editor.canSave || editor.isSaving, children: editor.isSaving ? label("settings.saving") : label("settings.save") }),
442
585
  !editor.isDirty && /* @__PURE__ */ jsx6("span", { className: "adn-settings__hint", children: label("settings.unchanged") })
443
586
  ] })
444
587
  ] })
@@ -460,6 +603,8 @@ export {
460
603
  formatNotificationTime,
461
604
  formatNotificationTimestamp,
462
605
  resolveMessages,
606
+ useCategoryPolicies,
607
+ useDeactivateTemplate,
463
608
  useDeleteNotification,
464
609
  useMarkAllAsRead,
465
610
  useMarkAsRead,
@@ -468,6 +613,7 @@ export {
468
613
  useNotifications,
469
614
  usePreferences,
470
615
  useTemplateEditor,
616
+ useTemplateVariables,
471
617
  useTemplates,
472
618
  useUnreadCount,
473
619
  useUpdatePreferences,