@adatechnology/notification-ui 0.1.0-rc.2 → 0.1.0-rc.4

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
@@ -3,7 +3,11 @@ import {
3
3
  NOTIFICATION_QUERY_KEYS,
4
4
  NotificationProvider,
5
5
  flattenNotificationPages,
6
+ formatNotificationTime,
7
+ formatNotificationTimestamp,
6
8
  resolveMessages,
9
+ useCategoryPolicies,
10
+ useDeactivateTemplate,
7
11
  useDeleteNotification,
8
12
  useMarkAllAsRead,
9
13
  useMarkAsRead,
@@ -12,18 +16,23 @@ import {
12
16
  useNotifications,
13
17
  usePreferences,
14
18
  useTemplateEditor,
19
+ useTemplateVariables,
15
20
  useTemplates,
16
21
  useUnreadCount,
17
22
  useUpdatePreferences,
18
23
  useUpsertTemplate
19
- } from "./chunk-FNUYTJ36.js";
24
+ } from "./chunk-QBQ62VZ3.js";
20
25
 
21
26
  // src/components/NotificationBell.tsx
22
27
  import { Bell } from "lucide-react";
23
28
  import clsx from "clsx";
24
29
  import { jsx, jsxs } from "react/jsx-runtime";
25
30
  var DEFAULT_MAX_BADGE_COUNT = 99;
26
- function NotificationBell({ onClick, className, maxBadgeCount = DEFAULT_MAX_BADGE_COUNT }) {
31
+ function NotificationBell({
32
+ onClick,
33
+ className,
34
+ maxBadgeCount = DEFAULT_MAX_BADGE_COUNT
35
+ }) {
27
36
  const { messages, theme } = useNotificationContext();
28
37
  const { data: unreadCount = 0 } = useUnreadCount();
29
38
  const hasUnread = unreadCount > 0;
@@ -57,7 +66,7 @@ import { Check, Trash2 } from "lucide-react";
57
66
  import clsx2 from "clsx";
58
67
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
59
68
  function NotificationItem({ notification, onSelect, className }) {
60
- const { messages } = useNotificationContext();
69
+ const { locale, messages } = useNotificationContext();
61
70
  const markAsRead = useMarkAsRead();
62
71
  const deleteNotification = useDeleteNotification();
63
72
  const isUnread = notification.readAt === void 0;
@@ -67,9 +76,20 @@ function NotificationItem({ notification, onSelect, className }) {
67
76
  }
68
77
  return /* @__PURE__ */ jsxs2("li", { className: clsx2("adn-item", isUnread && "adn-item--unread", className), children: [
69
78
  /* @__PURE__ */ jsxs2("button", { type: "button", className: "adn-item__main", onClick: handleSelect, children: [
70
- /* @__PURE__ */ jsx2("span", { className: "adn-item__title", children: notification.title }),
71
- /* @__PURE__ */ jsx2("span", { className: "adn-item__body", children: notification.body }),
72
- isUnread ? /* @__PURE__ */ jsx2("span", { className: "adn-item__unread-dot", "aria-label": messages["item.unread"] }) : null
79
+ /* @__PURE__ */ jsxs2("span", { className: "adn-item__heading", children: [
80
+ isUnread ? /* @__PURE__ */ jsx2("span", { className: "adn-item__unread-dot", "aria-label": messages["item.unread"] }) : null,
81
+ /* @__PURE__ */ jsx2("span", { className: "adn-item__title", children: notification.title }),
82
+ /* @__PURE__ */ jsx2(
83
+ "time",
84
+ {
85
+ className: "adn-item__time",
86
+ dateTime: notification.createdAt,
87
+ title: formatNotificationTimestamp(notification.createdAt, locale),
88
+ children: formatNotificationTime(notification.createdAt, locale, /* @__PURE__ */ new Date())
89
+ }
90
+ )
91
+ ] }),
92
+ /* @__PURE__ */ jsx2("span", { className: "adn-item__body", children: notification.body })
73
93
  ] }),
74
94
  /* @__PURE__ */ jsxs2("div", { className: "adn-item__actions", children: [
75
95
  isUnread ? /* @__PURE__ */ jsx2(
@@ -102,16 +122,21 @@ function NotificationItem({ notification, onSelect, className }) {
102
122
 
103
123
  // src/components/NotificationList.tsx
104
124
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
105
- function NotificationList({ category, onSelect, className, components }) {
125
+ var SKELETON_ROWS = [0, 1, 2];
126
+ function NotificationList({ category, onSelect, className, renderEmpty, components }) {
106
127
  const { messages, theme } = useNotificationContext();
107
128
  const query = useNotifications({ category });
108
129
  const { data: unreadCount = 0 } = useUnreadCount();
109
130
  const markAllAsRead = useMarkAllAsRead();
110
131
  const Item = components?.Item ?? NotificationItem;
111
132
  const notifications = flattenNotificationPages(query.data?.pages);
133
+ const isEmpty = !query.isPending && !query.isError && notifications.length === 0;
112
134
  return /* @__PURE__ */ jsxs3("section", { className: clsx3("adn-list", theme.rootClassName, className), "aria-label": messages["list.title"], children: [
113
135
  /* @__PURE__ */ jsxs3("header", { className: "adn-list__header", children: [
114
- /* @__PURE__ */ jsx3("h2", { className: "adn-list__title", children: messages["list.title"] }),
136
+ /* @__PURE__ */ jsxs3("div", { className: "adn-list__heading", children: [
137
+ /* @__PURE__ */ jsx3("h2", { className: "adn-list__title", children: messages["list.title"] }),
138
+ unreadCount > 0 ? /* @__PURE__ */ jsx3("span", { className: "adn-list__count", role: "status", "aria-label": `${unreadCount} ${messages["list.unread"]}`, children: unreadCount }) : null
139
+ ] }),
115
140
  unreadCount > 0 ? /* @__PURE__ */ jsxs3(
116
141
  "button",
117
142
  {
@@ -126,20 +151,20 @@ function NotificationList({ category, onSelect, className, components }) {
126
151
  }
127
152
  ) : null
128
153
  ] }),
129
- query.isPending ? /* @__PURE__ */ jsx3("p", { className: "adn-list__state", children: messages["list.loading"] }) : null,
130
- query.isError ? /* @__PURE__ */ jsxs3("div", { className: "adn-list__state adn-list__state--error", children: [
154
+ query.isPending ? /* @__PURE__ */ jsx3("div", { className: "adn-list__skeleton", role: "status", "aria-label": messages["list.loading"], children: SKELETON_ROWS.map((row) => /* @__PURE__ */ jsx3("span", { className: "adn-list__skeleton-row" }, row)) }) : null,
155
+ query.isError ? /* @__PURE__ */ jsxs3("div", { className: "adn-list__state adn-list__state--error", role: "alert", children: [
131
156
  /* @__PURE__ */ jsx3("p", { children: messages["list.error"] }),
132
157
  /* @__PURE__ */ jsxs3("button", { type: "button", className: "adn-list__retry", onClick: () => void query.refetch(), children: [
133
158
  /* @__PURE__ */ jsx3(RotateCw, { className: "adn-list__retry-icon", "aria-hidden": "true" }),
134
159
  messages["list.retry"]
135
160
  ] })
136
161
  ] }) : null,
137
- !query.isPending && !query.isError && notifications.length === 0 ? /* @__PURE__ */ jsxs3("div", { className: "adn-list__state adn-list__state--empty", children: [
162
+ isEmpty ? renderEmpty?.() ?? /* @__PURE__ */ jsxs3("div", { className: "adn-list__state adn-list__state--empty", children: [
138
163
  /* @__PURE__ */ jsx3(Inbox, { className: "adn-list__empty-icon", "aria-hidden": "true" }),
139
164
  /* @__PURE__ */ jsx3("p", { children: messages["list.empty"] })
140
165
  ] }) : null,
141
166
  notifications.length > 0 ? /* @__PURE__ */ jsx3("ul", { className: "adn-list__items", children: notifications.map((notification) => /* @__PURE__ */ jsx3(Item, { notification, onSelect }, notification.id)) }) : null,
142
- query.hasNextPage ? /* @__PURE__ */ jsx3(
167
+ query.hasNextPage ? /* @__PURE__ */ jsx3("div", { className: "adn-list__footer", children: /* @__PURE__ */ jsx3(
143
168
  "button",
144
169
  {
145
170
  type: "button",
@@ -148,7 +173,7 @@ function NotificationList({ category, onSelect, className, components }) {
148
173
  disabled: query.isFetchingNextPage,
149
174
  children: messages["list.loadMore"]
150
175
  }
151
- ) : null
176
+ ) }) : null
152
177
  ] });
153
178
  }
154
179
 
@@ -238,6 +263,7 @@ function NotificationsWorkspace({
238
263
  showPreferences = true,
239
264
  settingsHref,
240
265
  onSelectNotification,
266
+ renderHeader,
241
267
  renderHeaderActions,
242
268
  renderAboveList,
243
269
  renderEmpty,
@@ -246,7 +272,7 @@ function NotificationsWorkspace({
246
272
  const { messages } = useNotificationContext();
247
273
  const label = (key) => labelsOverride?.[key] ?? messages[key] ?? key;
248
274
  return /* @__PURE__ */ jsxs5("div", { className: `adn-workspace ${className ?? ""}`, children: [
249
- /* @__PURE__ */ jsxs5("header", { className: "adn-workspace__header", children: [
275
+ renderHeader ? renderHeader() : /* @__PURE__ */ jsxs5("header", { className: "adn-workspace__header", children: [
250
276
  /* @__PURE__ */ jsxs5("div", { children: [
251
277
  /* @__PURE__ */ jsx5("h1", { className: "adn-workspace__title", children: label("workspace.title") }),
252
278
  /* @__PURE__ */ jsx5("p", { className: "adn-workspace__description", children: label("workspace.description") })
@@ -258,22 +284,21 @@ function NotificationsWorkspace({
258
284
  ] }),
259
285
  renderAboveList?.(),
260
286
  /* @__PURE__ */ jsxs5("div", { className: showPreferences ? "adn-workspace__grid" : "adn-workspace__grid--single", children: [
261
- /* @__PURE__ */ jsxs5("section", { className: "adn-workspace__inbox", children: [
262
- /* @__PURE__ */ jsx5(
263
- NotificationList,
264
- {
265
- ...category ? { category } : {},
266
- ...onSelectNotification ? { onSelect: onSelectNotification } : {}
267
- }
268
- ),
269
- renderEmpty?.()
270
- ] }),
287
+ /* @__PURE__ */ jsx5("section", { className: "adn-workspace__inbox", children: /* @__PURE__ */ jsx5(
288
+ NotificationList,
289
+ {
290
+ ...category ? { category } : {},
291
+ ...onSelectNotification ? { onSelect: onSelectNotification } : {},
292
+ ...renderEmpty ? { renderEmpty } : {}
293
+ }
294
+ ) }),
271
295
  showPreferences && /* @__PURE__ */ jsx5("aside", { className: "adn-workspace__aside", children: /* @__PURE__ */ jsx5(PreferencesPanel, {}) })
272
296
  ] })
273
297
  ] });
274
298
  }
275
299
 
276
300
  // src/components/NotificationSettingsWorkspace.tsx
301
+ import { useState as useState2 } from "react";
277
302
  import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
278
303
  function NotificationSettingsWorkspace({
279
304
  labels: labelsOverride,
@@ -282,6 +307,7 @@ function NotificationSettingsWorkspace({
282
307
  previewPayload,
283
308
  templateLabelOf,
284
309
  renderChannelFields,
310
+ renderHeader,
285
311
  renderHeaderActions,
286
312
  className
287
313
  }) {
@@ -289,7 +315,19 @@ function NotificationSettingsWorkspace({
289
315
  const label = (key) => labelsOverride?.[key] ?? messages[key] ?? key;
290
316
  const preferencesQuery = usePreferences();
291
317
  const updatePreferences = useUpdatePreferences();
292
- 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");
293
331
  function isEnabled(category, channel) {
294
332
  const row = (preferencesQuery.data ?? []).find(
295
333
  (preference) => preference.category === category && preference.channel === channel
@@ -307,14 +345,49 @@ function NotificationSettingsWorkspace({
307
345
  updatePreferences.mutate(next);
308
346
  }
309
347
  return /* @__PURE__ */ jsxs6("div", { className: `adn-settings ${className ?? ""}`, children: [
310
- /* @__PURE__ */ jsxs6("header", { className: "adn-settings__header", children: [
348
+ renderHeader ? renderHeader() : /* @__PURE__ */ jsxs6("header", { className: "adn-settings__header", children: [
311
349
  /* @__PURE__ */ jsxs6("div", { children: [
312
350
  /* @__PURE__ */ jsx6("h1", { className: "adn-settings__title", children: label("settings.title") }),
313
351
  /* @__PURE__ */ jsx6("p", { className: "adn-settings__description", children: label("settings.description") })
314
352
  ] }),
315
353
  renderHeaderActions?.()
316
354
  ] }),
317
- /* @__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: [
318
391
  /* @__PURE__ */ jsx6("h2", { className: "adn-settings__section-title", children: label("settings.channelsTitle") }),
319
392
  /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.channelsHint") }),
320
393
  categories.map((category) => /* @__PURE__ */ jsxs6("div", { className: "adn-settings__category", children: [
@@ -337,33 +410,49 @@ function NotificationSettingsWorkspace({
337
410
  ] }, channel.id)) })
338
411
  ] }, category.id))
339
412
  ] }),
340
- /* @__PURE__ */ jsxs6("div", { className: "adn-settings__templates", children: [
413
+ tab === "messages" && /* @__PURE__ */ jsxs6("div", { className: "adn-settings__templates", children: [
341
414
  /* @__PURE__ */ jsxs6("section", { className: "adn-settings__template-list", children: [
342
- /* @__PURE__ */ jsxs6("header", { children: [
343
- /* @__PURE__ */ jsx6("h2", { className: "adn-settings__section-title", children: label("settings.templatesTitle") }),
344
- /* @__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") })
345
421
  ] }),
346
422
  editor.isLoading && /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("list.loading") }),
347
423
  !editor.isLoading && editor.templates.length === 0 && /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.templatesEmpty") }),
348
- /* @__PURE__ */ jsx6("ul", { children: editor.templates.map((template) => /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsxs6(
349
- "button",
350
- {
351
- type: "button",
352
- onClick: () => editor.select(template),
353
- className: editor.selected?.id === template.id ? "adn-settings__row--active" : void 0,
354
- children: [
355
- /* @__PURE__ */ jsxs6("span", { className: "adn-settings__row-main", children: [
356
- /* @__PURE__ */ jsx6("span", { className: "adn-settings__row-title", children: templateLabelOf?.(template) ?? template.key }),
357
- /* @__PURE__ */ jsx6("span", { className: "adn-settings__row-body", children: template.body })
358
- ] }),
359
- /* @__PURE__ */ jsx6("span", { className: `adn-settings__badge adn-settings__badge--${template.channel}`, children: label(`channel.${template.channel}`) }),
360
- /* @__PURE__ */ jsxs6("span", { className: "adn-settings__version", children: [
361
- label("settings.version"),
362
- template.version
363
- ] })
364
- ]
365
- }
366
- ) }, 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)) })
367
456
  ] }),
368
457
  /* @__PURE__ */ jsxs6("section", { className: "adn-settings__editor", children: [
369
458
  !editor.draft && /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.pickTemplate") }),
@@ -382,10 +471,74 @@ function NotificationSettingsWorkspace({
382
471
  ] }),
383
472
  /* @__PURE__ */ jsx6("button", { type: "button", onClick: editor.clear, children: label("settings.close") })
384
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
+ ] }),
385
531
  /* @__PURE__ */ jsxs6("label", { className: "adn-settings__field", children: [
386
532
  /* @__PURE__ */ jsx6("span", { className: "adn-settings__field-label", children: label("settings.subject") }),
387
533
  /* @__PURE__ */ jsx6("span", { className: "adn-settings__hint", children: label("settings.subjectHint") }),
388
- /* @__PURE__ */ jsx6("input", { value: editor.draft.subject, onChange: (event) => editor.update({ subject: event.target.value }) })
534
+ /* @__PURE__ */ jsx6(
535
+ "input",
536
+ {
537
+ value: editor.draft.subject,
538
+ onChange: (event) => editor.update({ subject: event.target.value }),
539
+ onSelect: (event) => rememberCursor("subject", event.currentTarget.selectionStart)
540
+ }
541
+ )
389
542
  ] }),
390
543
  /* @__PURE__ */ jsxs6("label", { className: "adn-settings__field", children: [
391
544
  /* @__PURE__ */ jsx6("span", { className: "adn-settings__field-label", children: label("settings.body") }),
@@ -394,7 +547,8 @@ function NotificationSettingsWorkspace({
394
547
  {
395
548
  rows: 4,
396
549
  value: editor.draft.body,
397
- 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)
398
552
  }
399
553
  )
400
554
  ] }),
@@ -403,14 +557,31 @@ function NotificationSettingsWorkspace({
403
557
  value: editor.draft.whatsappTemplateName,
404
558
  onChange: (value) => editor.update({ whatsappTemplateName: value })
405
559
  }),
406
- editor.preview && /* @__PURE__ */ jsxs6("div", { className: "adn-settings__preview", children: [
560
+ editor.previews.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "adn-settings__previews", children: [
407
561
  /* @__PURE__ */ jsx6("p", { className: "adn-settings__preview-title", children: label("settings.previewTitle") }),
408
- /* @__PURE__ */ jsx6("p", { className: "adn-settings__preview-subject", children: editor.preview.title }),
409
- /* @__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)) })
410
581
  ] }),
411
582
  editor.error && /* @__PURE__ */ jsx6("p", { className: "adn-settings__error", children: editor.error }),
412
583
  /* @__PURE__ */ jsxs6("div", { className: "adn-settings__actions", children: [
413
- /* @__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") }),
414
585
  !editor.isDirty && /* @__PURE__ */ jsx6("span", { className: "adn-settings__hint", children: label("settings.unchanged") })
415
586
  ] })
416
587
  ] })
@@ -429,7 +600,11 @@ export {
429
600
  NotificationsWorkspace,
430
601
  PreferencesPanel,
431
602
  flattenNotificationPages,
603
+ formatNotificationTime,
604
+ formatNotificationTimestamp,
432
605
  resolveMessages,
606
+ useCategoryPolicies,
607
+ useDeactivateTemplate,
433
608
  useDeleteNotification,
434
609
  useMarkAllAsRead,
435
610
  useMarkAsRead,
@@ -438,6 +613,7 @@ export {
438
613
  useNotifications,
439
614
  usePreferences,
440
615
  useTemplateEditor,
616
+ useTemplateVariables,
441
617
  useTemplates,
442
618
  useUnreadCount,
443
619
  useUpdatePreferences,