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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,323 +1,58 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
-
4
- // src/NotificationProvider.tsx
5
- import { createContext, useContext, useMemo } from "react";
6
-
7
- // src/locales/pt-BR.json
8
- var pt_BR_default = {
9
- "bell.label": "Notifica\xE7\xF5es",
10
- "bell.unreadSuffix": "n\xE3o lidas",
11
- "list.title": "Notifica\xE7\xF5es",
12
- "list.empty": "Nenhuma notifica\xE7\xE3o por aqui.",
13
- "list.loading": "Carregando\u2026",
14
- "list.error": "N\xE3o foi poss\xEDvel carregar suas notifica\xE7\xF5es.",
15
- "list.retry": "Tentar de novo",
16
- "list.loadMore": "Carregar mais",
17
- "list.markAllAsRead": "Marcar todas como lidas",
18
- "item.markAsRead": "Marcar como lida",
19
- "item.delete": "Excluir",
20
- "item.unread": "N\xE3o lida",
21
- "preferences.title": "Prefer\xEAncias de notifica\xE7\xE3o",
22
- "preferences.description": "Escolha por onde voc\xEA quer ser avisado em cada assunto.",
23
- "preferences.save": "Salvar prefer\xEAncias",
24
- "preferences.saved": "Prefer\xEAncias salvas.",
25
- "preferences.error": "N\xE3o foi poss\xEDvel salvar. Tente de novo.",
26
- "preferences.empty": "Nenhuma prefer\xEAncia configurada ainda.",
27
- "preferences.quietHours": "Sil\xEAncio entre",
28
- "preferences.quietHoursTo": "e",
29
- "channel.inbox": "No app",
30
- "channel.push": "Push",
31
- "channel.email": "E-mail",
32
- "channel.whatsapp": "WhatsApp",
33
- "channel.sms": "SMS"
34
- };
35
-
36
- // src/locales/en.json
37
- var en_default = {
38
- "bell.label": "Notifications",
39
- "bell.unreadSuffix": "unread",
40
- "list.title": "Notifications",
41
- "list.empty": "Nothing here yet.",
42
- "list.loading": "Loading\u2026",
43
- "list.error": "We couldn't load your notifications.",
44
- "list.retry": "Try again",
45
- "list.loadMore": "Load more",
46
- "list.markAllAsRead": "Mark all as read",
47
- "item.markAsRead": "Mark as read",
48
- "item.delete": "Delete",
49
- "item.unread": "Unread",
50
- "preferences.title": "Notification preferences",
51
- "preferences.description": "Choose how you want to be notified about each topic.",
52
- "preferences.save": "Save preferences",
53
- "preferences.saved": "Preferences saved.",
54
- "preferences.error": "Couldn't save. Try again.",
55
- "preferences.empty": "No preferences configured yet.",
56
- "preferences.quietHours": "Quiet between",
57
- "preferences.quietHoursTo": "and",
58
- "channel.inbox": "In app",
59
- "channel.push": "Push",
60
- "channel.email": "Email",
61
- "channel.whatsapp": "WhatsApp",
62
- "channel.sms": "SMS"
63
- };
64
-
65
- // src/locales/index.ts
66
- var DEFAULT_LOCALE = "pt-BR";
67
- var MESSAGES_BY_LOCALE = {
68
- "pt-BR": pt_BR_default,
69
- en: en_default
70
- };
71
- function resolveMessages(locale) {
72
- return MESSAGES_BY_LOCALE[locale] ?? MESSAGES_BY_LOCALE[DEFAULT_LOCALE];
73
- }
74
- __name(resolveMessages, "resolveMessages");
75
-
76
- // src/NotificationProvider.tsx
77
- var NotificationContext = /* @__PURE__ */ createContext(null);
78
- function NotificationProvider({ client, locale = DEFAULT_LOCALE, messageOverrides, theme = {}, pollIntervalSeconds = 60, children }) {
79
- const value = useMemo(() => ({
80
- client,
81
- messages: {
82
- ...resolveMessages(locale),
83
- ...messageOverrides
84
- },
85
- theme,
86
- pollIntervalSeconds
87
- }), [
88
- client,
89
- locale,
90
- messageOverrides,
91
- theme,
92
- pollIntervalSeconds
93
- ]);
94
- return /* @__PURE__ */ React.createElement(NotificationContext.Provider, {
95
- value
96
- }, children);
97
- }
98
- __name(NotificationProvider, "NotificationProvider");
99
- function useNotificationContext() {
100
- const context = useContext(NotificationContext);
101
- if (!context) throw new Error("notification-ui: componente usado fora de <NotificationProvider>");
102
- return context;
103
- }
104
- __name(useNotificationContext, "useNotificationContext");
105
-
106
- // src/hooks/useNotifications.ts
107
- import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
108
-
109
- // src/hooks/queryKeys.ts
110
- var NOTIFICATION_QUERY_KEYS = {
111
- all: [
112
- "notifications"
113
- ],
114
- list: /* @__PURE__ */ __name((filters) => [
115
- "notifications",
116
- "list",
117
- filters
118
- ], "list"),
119
- unreadCount: /* @__PURE__ */ __name(() => [
120
- "notifications",
121
- "unread-count"
122
- ], "unreadCount"),
123
- preferences: /* @__PURE__ */ __name(() => [
124
- "notifications",
125
- "preferences"
126
- ], "preferences"),
127
- templates: /* @__PURE__ */ __name(() => [
128
- "notifications",
129
- "templates"
130
- ], "templates")
131
- };
132
-
133
- // src/hooks/useNotifications.ts
134
- function useNotifications(options = {}) {
135
- const { client } = useNotificationContext();
136
- return useInfiniteQuery({
137
- queryKey: NOTIFICATION_QUERY_KEYS.list({
138
- category: options.category,
139
- read: options.read
140
- }),
141
- initialPageParam: void 0,
142
- queryFn: /* @__PURE__ */ __name(({ pageParam }) => client.listNotifications({
143
- category: options.category,
144
- read: options.read,
145
- cursor: pageParam,
146
- perPage: options.perPage
147
- }), "queryFn"),
148
- getNextPageParam: /* @__PURE__ */ __name((lastPage) => lastPage.nextCursor, "getNextPageParam")
149
- });
150
- }
151
- __name(useNotifications, "useNotifications");
152
- function useUnreadCount() {
153
- const { client, pollIntervalSeconds } = useNotificationContext();
154
- return useQuery({
155
- queryKey: NOTIFICATION_QUERY_KEYS.unreadCount(),
156
- queryFn: /* @__PURE__ */ __name(() => client.countUnread(), "queryFn"),
157
- // Polling é o fallback de quem não ligou o SSE. Com o stream ativo,
158
- // `useNotificationStream` invalida esta chave e o refetch acontece na hora.
159
- refetchInterval: pollIntervalSeconds > 0 ? pollIntervalSeconds * 1e3 : false
160
- });
161
- }
162
- __name(useUnreadCount, "useUnreadCount");
163
- function useMarkAsRead() {
164
- const { client } = useNotificationContext();
165
- const queryClient = useQueryClient();
166
- return useMutation({
167
- mutationFn: /* @__PURE__ */ __name((id) => client.markAsRead(id), "mutationFn"),
168
- async onMutate(id) {
169
- await queryClient.cancelQueries({
170
- queryKey: NOTIFICATION_QUERY_KEYS.all
171
- });
172
- const previousCount = queryClient.getQueryData(NOTIFICATION_QUERY_KEYS.unreadCount());
173
- queryClient.setQueryData(NOTIFICATION_QUERY_KEYS.unreadCount(), (current) => current === void 0 ? current : Math.max(0, current - 1));
174
- return {
175
- previousCount,
176
- id
177
- };
178
- },
179
- onError(_error, _id, context) {
180
- if (context?.previousCount !== void 0) {
181
- queryClient.setQueryData(NOTIFICATION_QUERY_KEYS.unreadCount(), context.previousCount);
182
- }
183
- },
184
- onSettled() {
185
- void queryClient.invalidateQueries({
186
- queryKey: NOTIFICATION_QUERY_KEYS.all
187
- });
188
- }
189
- });
190
- }
191
- __name(useMarkAsRead, "useMarkAsRead");
192
- function useMarkAllAsRead() {
193
- const { client } = useNotificationContext();
194
- const queryClient = useQueryClient();
195
- return useMutation({
196
- mutationFn: /* @__PURE__ */ __name(() => client.markAllAsRead(), "mutationFn"),
197
- onSettled() {
198
- void queryClient.invalidateQueries({
199
- queryKey: NOTIFICATION_QUERY_KEYS.all
200
- });
201
- }
202
- });
203
- }
204
- __name(useMarkAllAsRead, "useMarkAllAsRead");
205
- function useDeleteNotification() {
206
- const { client } = useNotificationContext();
207
- const queryClient = useQueryClient();
208
- return useMutation({
209
- mutationFn: /* @__PURE__ */ __name((id) => client.deleteNotification(id), "mutationFn"),
210
- onSettled() {
211
- void queryClient.invalidateQueries({
212
- queryKey: NOTIFICATION_QUERY_KEYS.all
213
- });
214
- }
215
- });
216
- }
217
- __name(useDeleteNotification, "useDeleteNotification");
218
- function flattenNotificationPages(pages) {
219
- return pages?.flatMap((page) => page.data) ?? [];
220
- }
221
- __name(flattenNotificationPages, "flattenNotificationPages");
222
-
223
- // src/hooks/useNotificationStream.ts
224
- import { useEffect } from "react";
225
- import { useQueryClient as useQueryClient2 } from "@tanstack/react-query";
226
- import { subscribeToNotificationStream } from "@adatechnology/notification-client";
227
- function useNotificationStream(options = {}) {
228
- const { client } = useNotificationContext();
229
- const queryClient = useQueryClient2();
230
- const enabled = options.enabled ?? false;
231
- const onEvent = options.onEvent;
232
- useEffect(() => {
233
- if (!enabled) return;
234
- let subscription;
235
- let cancelled = false;
236
- void client.resolveStreamRequest().then(({ url, headers }) => {
237
- if (cancelled) return;
238
- subscription = subscribeToNotificationStream({
239
- url,
240
- headers,
241
- onEvent: /* @__PURE__ */ __name((event) => {
242
- onEvent?.(event);
243
- void queryClient.invalidateQueries({
244
- queryKey: NOTIFICATION_QUERY_KEYS.all
245
- });
246
- }, "onEvent")
247
- });
248
- });
249
- return () => {
250
- cancelled = true;
251
- subscription?.close();
252
- };
253
- }, [
254
- client,
255
- queryClient,
256
- enabled,
257
- onEvent
258
- ]);
259
- }
260
- __name(useNotificationStream, "useNotificationStream");
261
-
262
- // src/hooks/usePreferences.ts
263
- import { useMutation as useMutation2, useQuery as useQuery2, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
264
- function usePreferences() {
265
- const { client } = useNotificationContext();
266
- return useQuery2({
267
- queryKey: NOTIFICATION_QUERY_KEYS.preferences(),
268
- queryFn: /* @__PURE__ */ __name(() => client.getPreferences(), "queryFn")
269
- });
270
- }
271
- __name(usePreferences, "usePreferences");
272
- function useUpdatePreferences() {
273
- const { client } = useNotificationContext();
274
- const queryClient = useQueryClient3();
275
- return useMutation2({
276
- mutationFn: /* @__PURE__ */ __name((preferences) => client.updatePreferences(preferences), "mutationFn"),
277
- onSuccess(saved) {
278
- queryClient.setQueryData(NOTIFICATION_QUERY_KEYS.preferences(), saved);
279
- }
280
- });
281
- }
282
- __name(useUpdatePreferences, "useUpdatePreferences");
283
- function useTemplates() {
284
- const { client } = useNotificationContext();
285
- return useQuery2({
286
- queryKey: NOTIFICATION_QUERY_KEYS.templates(),
287
- queryFn: /* @__PURE__ */ __name(() => client.listTemplates(), "queryFn")
288
- });
289
- }
290
- __name(useTemplates, "useTemplates");
1
+ import {
2
+ DEFAULT_LOCALE,
3
+ NOTIFICATION_QUERY_KEYS,
4
+ NotificationProvider,
5
+ flattenNotificationPages,
6
+ formatNotificationTime,
7
+ formatNotificationTimestamp,
8
+ resolveMessages,
9
+ useDeleteNotification,
10
+ useMarkAllAsRead,
11
+ useMarkAsRead,
12
+ useNotificationContext,
13
+ useNotificationStream,
14
+ useNotifications,
15
+ usePreferences,
16
+ useTemplateEditor,
17
+ useTemplates,
18
+ useUnreadCount,
19
+ useUpdatePreferences,
20
+ useUpsertTemplate
21
+ } from "./chunk-I6WQKOCP.js";
291
22
 
292
23
  // src/components/NotificationBell.tsx
293
24
  import { Bell } from "lucide-react";
294
25
  import clsx from "clsx";
26
+ import { jsx, jsxs } from "react/jsx-runtime";
295
27
  var DEFAULT_MAX_BADGE_COUNT = 99;
296
- function NotificationBell({ onClick, className, maxBadgeCount = DEFAULT_MAX_BADGE_COUNT }) {
28
+ function NotificationBell({
29
+ onClick,
30
+ className,
31
+ maxBadgeCount = DEFAULT_MAX_BADGE_COUNT
32
+ }) {
297
33
  const { messages, theme } = useNotificationContext();
298
34
  const { data: unreadCount = 0 } = useUnreadCount();
299
35
  const hasUnread = unreadCount > 0;
300
36
  const badgeLabel = unreadCount > maxBadgeCount ? `${maxBadgeCount}+` : String(unreadCount);
301
37
  const accessibleLabel = hasUnread ? `${messages["bell.label"]}: ${unreadCount} ${messages["bell.unreadSuffix"]}` : messages["bell.label"];
302
- return /* @__PURE__ */ React.createElement("button", {
303
- type: "button",
304
- onClick,
305
- className: clsx("adn-bell", theme.rootClassName, className),
306
- // Botão só-ícone exige rótulo acessível (regra de ícones, `web.md` §9).
307
- "aria-label": accessibleLabel
308
- }, /* @__PURE__ */ React.createElement(Bell, {
309
- className: "adn-bell__icon",
310
- "aria-hidden": "true"
311
- }), hasUnread ? (
312
- // `aria-live` para leitor de tela anunciar a mudança do contador sem o usuário reabrir
313
- // o menu; `polite` porque notificação nova não deve interromper o que ele está lendo.
314
- /* @__PURE__ */ React.createElement("span", {
315
- className: "adn-bell__badge",
316
- "aria-live": "polite"
317
- }, badgeLabel)
318
- ) : null);
38
+ return /* @__PURE__ */ jsxs(
39
+ "button",
40
+ {
41
+ type: "button",
42
+ onClick,
43
+ className: clsx("adn-bell", theme.rootClassName, className),
44
+ "aria-label": accessibleLabel,
45
+ children: [
46
+ /* @__PURE__ */ jsx(Bell, { className: "adn-bell__icon", "aria-hidden": "true" }),
47
+ hasUnread ? (
48
+ // `aria-live` para leitor de tela anunciar a mudança do contador sem o usuário reabrir
49
+ // o menu; `polite` porque notificação nova não deve interromper o que ele está lendo.
50
+ /* @__PURE__ */ jsx("span", { className: "adn-bell__badge", "aria-live": "polite", children: badgeLabel })
51
+ ) : null
52
+ ]
53
+ }
54
+ );
319
55
  }
320
- __name(NotificationBell, "NotificationBell");
321
56
 
322
57
  // src/components/NotificationList.tsx
323
58
  import { CheckCheck, Inbox, RotateCw } from "lucide-react";
@@ -326,8 +61,9 @@ import clsx3 from "clsx";
326
61
  // src/components/NotificationItem.tsx
327
62
  import { Check, Trash2 } from "lucide-react";
328
63
  import clsx2 from "clsx";
64
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
329
65
  function NotificationItem({ notification, onSelect, className }) {
330
- const { messages } = useNotificationContext();
66
+ const { locale, messages } = useNotificationContext();
331
67
  const markAsRead = useMarkAsRead();
332
68
  const deleteNotification = useDeleteNotification();
333
69
  const isUnread = notification.readAt === void 0;
@@ -335,110 +71,117 @@ function NotificationItem({ notification, onSelect, className }) {
335
71
  if (isUnread) markAsRead.mutate(notification.id);
336
72
  onSelect?.(notification);
337
73
  }
338
- __name(handleSelect, "handleSelect");
339
- return /* @__PURE__ */ React.createElement("li", {
340
- className: clsx2("adn-item", isUnread && "adn-item--unread", className)
341
- }, /* @__PURE__ */ React.createElement("button", {
342
- type: "button",
343
- className: "adn-item__main",
344
- onClick: handleSelect
345
- }, /* @__PURE__ */ React.createElement("span", {
346
- className: "adn-item__title"
347
- }, notification.title), /* @__PURE__ */ React.createElement("span", {
348
- className: "adn-item__body"
349
- }, notification.body), isUnread ? /* @__PURE__ */ React.createElement("span", {
350
- className: "adn-item__unread-dot",
351
- "aria-label": messages["item.unread"]
352
- }) : null), /* @__PURE__ */ React.createElement("div", {
353
- className: "adn-item__actions"
354
- }, isUnread ? /* @__PURE__ */ React.createElement("button", {
355
- type: "button",
356
- className: "adn-item__action",
357
- onClick: /* @__PURE__ */ __name(() => markAsRead.mutate(notification.id), "onClick"),
358
- disabled: markAsRead.isPending,
359
- "aria-label": messages["item.markAsRead"],
360
- title: messages["item.markAsRead"]
361
- }, /* @__PURE__ */ React.createElement(Check, {
362
- className: "adn-item__action-icon",
363
- "aria-hidden": "true"
364
- })) : null, /* @__PURE__ */ React.createElement("button", {
365
- type: "button",
366
- className: "adn-item__action adn-item__action--danger",
367
- onClick: /* @__PURE__ */ __name(() => deleteNotification.mutate(notification.id), "onClick"),
368
- disabled: deleteNotification.isPending,
369
- "aria-label": messages["item.delete"],
370
- title: messages["item.delete"]
371
- }, /* @__PURE__ */ React.createElement(Trash2, {
372
- className: "adn-item__action-icon",
373
- "aria-hidden": "true"
374
- }))));
74
+ return /* @__PURE__ */ jsxs2("li", { className: clsx2("adn-item", isUnread && "adn-item--unread", className), children: [
75
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "adn-item__main", onClick: handleSelect, children: [
76
+ /* @__PURE__ */ jsxs2("span", { className: "adn-item__heading", children: [
77
+ isUnread ? /* @__PURE__ */ jsx2("span", { className: "adn-item__unread-dot", "aria-label": messages["item.unread"] }) : null,
78
+ /* @__PURE__ */ jsx2("span", { className: "adn-item__title", children: notification.title }),
79
+ /* @__PURE__ */ jsx2(
80
+ "time",
81
+ {
82
+ className: "adn-item__time",
83
+ dateTime: notification.createdAt,
84
+ title: formatNotificationTimestamp(notification.createdAt, locale),
85
+ children: formatNotificationTime(notification.createdAt, locale, /* @__PURE__ */ new Date())
86
+ }
87
+ )
88
+ ] }),
89
+ /* @__PURE__ */ jsx2("span", { className: "adn-item__body", children: notification.body })
90
+ ] }),
91
+ /* @__PURE__ */ jsxs2("div", { className: "adn-item__actions", children: [
92
+ isUnread ? /* @__PURE__ */ jsx2(
93
+ "button",
94
+ {
95
+ type: "button",
96
+ className: "adn-item__action",
97
+ onClick: () => markAsRead.mutate(notification.id),
98
+ disabled: markAsRead.isPending,
99
+ "aria-label": messages["item.markAsRead"],
100
+ title: messages["item.markAsRead"],
101
+ children: /* @__PURE__ */ jsx2(Check, { className: "adn-item__action-icon", "aria-hidden": "true" })
102
+ }
103
+ ) : null,
104
+ /* @__PURE__ */ jsx2(
105
+ "button",
106
+ {
107
+ type: "button",
108
+ className: "adn-item__action adn-item__action--danger",
109
+ onClick: () => deleteNotification.mutate(notification.id),
110
+ disabled: deleteNotification.isPending,
111
+ "aria-label": messages["item.delete"],
112
+ title: messages["item.delete"],
113
+ children: /* @__PURE__ */ jsx2(Trash2, { className: "adn-item__action-icon", "aria-hidden": "true" })
114
+ }
115
+ )
116
+ ] })
117
+ ] });
375
118
  }
376
- __name(NotificationItem, "NotificationItem");
377
119
 
378
120
  // src/components/NotificationList.tsx
379
- function NotificationList({ category, onSelect, className, components }) {
121
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
122
+ var SKELETON_ROWS = [0, 1, 2];
123
+ function NotificationList({ category, onSelect, className, renderEmpty, components }) {
380
124
  const { messages, theme } = useNotificationContext();
381
- const query = useNotifications({
382
- category
383
- });
125
+ const query = useNotifications({ category });
384
126
  const { data: unreadCount = 0 } = useUnreadCount();
385
127
  const markAllAsRead = useMarkAllAsRead();
386
128
  const Item = components?.Item ?? NotificationItem;
387
129
  const notifications = flattenNotificationPages(query.data?.pages);
388
- return /* @__PURE__ */ React.createElement("section", {
389
- className: clsx3("adn-list", theme.rootClassName, className),
390
- "aria-label": messages["list.title"]
391
- }, /* @__PURE__ */ React.createElement("header", {
392
- className: "adn-list__header"
393
- }, /* @__PURE__ */ React.createElement("h2", {
394
- className: "adn-list__title"
395
- }, messages["list.title"]), unreadCount > 0 ? /* @__PURE__ */ React.createElement("button", {
396
- type: "button",
397
- className: "adn-list__bulk-action",
398
- onClick: /* @__PURE__ */ __name(() => markAllAsRead.mutate(), "onClick"),
399
- disabled: markAllAsRead.isPending
400
- }, /* @__PURE__ */ React.createElement(CheckCheck, {
401
- className: "adn-list__bulk-icon",
402
- "aria-hidden": "true"
403
- }), messages["list.markAllAsRead"]) : null), query.isPending ? /* @__PURE__ */ React.createElement("p", {
404
- className: "adn-list__state"
405
- }, messages["list.loading"]) : null, query.isError ? /* @__PURE__ */ React.createElement("div", {
406
- className: "adn-list__state adn-list__state--error"
407
- }, /* @__PURE__ */ React.createElement("p", null, messages["list.error"]), /* @__PURE__ */ React.createElement("button", {
408
- type: "button",
409
- className: "adn-list__retry",
410
- onClick: /* @__PURE__ */ __name(() => void query.refetch(), "onClick")
411
- }, /* @__PURE__ */ React.createElement(RotateCw, {
412
- className: "adn-list__retry-icon",
413
- "aria-hidden": "true"
414
- }), messages["list.retry"])) : null, !query.isPending && !query.isError && notifications.length === 0 ? /* @__PURE__ */ React.createElement("div", {
415
- className: "adn-list__state adn-list__state--empty"
416
- }, /* @__PURE__ */ React.createElement(Inbox, {
417
- className: "adn-list__empty-icon",
418
- "aria-hidden": "true"
419
- }), /* @__PURE__ */ React.createElement("p", null, messages["list.empty"])) : null, notifications.length > 0 ? /* @__PURE__ */ React.createElement("ul", {
420
- className: "adn-list__items"
421
- }, notifications.map((notification) => /* @__PURE__ */ React.createElement(Item, {
422
- key: notification.id,
423
- notification,
424
- onSelect
425
- }))) : null, query.hasNextPage ? /* @__PURE__ */ React.createElement("button", {
426
- type: "button",
427
- className: "adn-list__load-more",
428
- onClick: /* @__PURE__ */ __name(() => void query.fetchNextPage(), "onClick"),
429
- disabled: query.isFetchingNextPage
430
- }, messages["list.loadMore"]) : null);
130
+ const isEmpty = !query.isPending && !query.isError && notifications.length === 0;
131
+ return /* @__PURE__ */ jsxs3("section", { className: clsx3("adn-list", theme.rootClassName, className), "aria-label": messages["list.title"], children: [
132
+ /* @__PURE__ */ jsxs3("header", { className: "adn-list__header", children: [
133
+ /* @__PURE__ */ jsxs3("div", { className: "adn-list__heading", children: [
134
+ /* @__PURE__ */ jsx3("h2", { className: "adn-list__title", children: messages["list.title"] }),
135
+ unreadCount > 0 ? /* @__PURE__ */ jsx3("span", { className: "adn-list__count", role: "status", "aria-label": `${unreadCount} ${messages["list.unread"]}`, children: unreadCount }) : null
136
+ ] }),
137
+ unreadCount > 0 ? /* @__PURE__ */ jsxs3(
138
+ "button",
139
+ {
140
+ type: "button",
141
+ className: "adn-list__bulk-action",
142
+ onClick: () => markAllAsRead.mutate(),
143
+ disabled: markAllAsRead.isPending,
144
+ children: [
145
+ /* @__PURE__ */ jsx3(CheckCheck, { className: "adn-list__bulk-icon", "aria-hidden": "true" }),
146
+ messages["list.markAllAsRead"]
147
+ ]
148
+ }
149
+ ) : null
150
+ ] }),
151
+ 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,
152
+ query.isError ? /* @__PURE__ */ jsxs3("div", { className: "adn-list__state adn-list__state--error", role: "alert", children: [
153
+ /* @__PURE__ */ jsx3("p", { children: messages["list.error"] }),
154
+ /* @__PURE__ */ jsxs3("button", { type: "button", className: "adn-list__retry", onClick: () => void query.refetch(), children: [
155
+ /* @__PURE__ */ jsx3(RotateCw, { className: "adn-list__retry-icon", "aria-hidden": "true" }),
156
+ messages["list.retry"]
157
+ ] })
158
+ ] }) : null,
159
+ isEmpty ? renderEmpty?.() ?? /* @__PURE__ */ jsxs3("div", { className: "adn-list__state adn-list__state--empty", children: [
160
+ /* @__PURE__ */ jsx3(Inbox, { className: "adn-list__empty-icon", "aria-hidden": "true" }),
161
+ /* @__PURE__ */ jsx3("p", { children: messages["list.empty"] })
162
+ ] }) : null,
163
+ notifications.length > 0 ? /* @__PURE__ */ jsx3("ul", { className: "adn-list__items", children: notifications.map((notification) => /* @__PURE__ */ jsx3(Item, { notification, onSelect }, notification.id)) }) : null,
164
+ query.hasNextPage ? /* @__PURE__ */ jsx3("div", { className: "adn-list__footer", children: /* @__PURE__ */ jsx3(
165
+ "button",
166
+ {
167
+ type: "button",
168
+ className: "adn-list__load-more",
169
+ onClick: () => void query.fetchNextPage(),
170
+ disabled: query.isFetchingNextPage,
171
+ children: messages["list.loadMore"]
172
+ }
173
+ ) }) : null
174
+ ] });
431
175
  }
432
- __name(NotificationList, "NotificationList");
433
176
 
434
177
  // src/components/PreferencesPanel.tsx
435
178
  import { useState } from "react";
436
179
  import { Save } from "lucide-react";
437
180
  import clsx4 from "clsx";
181
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
438
182
  function channelLabelKey(channel) {
439
183
  return `channel.${channel}`;
440
184
  }
441
- __name(channelLabelKey, "channelLabelKey");
442
185
  function PreferencesPanel({ className }) {
443
186
  const { messages, theme } = useNotificationContext();
444
187
  const { data: saved = [], isPending } = usePreferences();
@@ -446,73 +189,263 @@ function PreferencesPanel({ className }) {
446
189
  const [draft, setDraft] = useState(void 0);
447
190
  const preferences = draft ?? saved;
448
191
  function toggleChannel(target) {
449
- setDraft(preferences.map((preference) => preference.category === target.category && preference.channel === target.channel ? {
450
- ...preference,
451
- enabled: !preference.enabled
452
- } : preference));
192
+ setDraft(
193
+ preferences.map(
194
+ (preference) => preference.category === target.category && preference.channel === target.channel ? { ...preference, enabled: !preference.enabled } : preference
195
+ )
196
+ );
453
197
  }
454
- __name(toggleChannel, "toggleChannel");
455
198
  function handleSave() {
456
- updatePreferences.mutate(preferences, {
457
- onSuccess: /* @__PURE__ */ __name(() => setDraft(void 0), "onSuccess")
458
- });
199
+ updatePreferences.mutate(preferences, { onSuccess: () => setDraft(void 0) });
200
+ }
201
+ if (isPending) return /* @__PURE__ */ jsx4("p", { className: "adn-preferences__state", children: messages["list.loading"] });
202
+ return /* @__PURE__ */ jsxs4("section", { className: clsx4("adn-preferences", theme.rootClassName, className), children: [
203
+ /* @__PURE__ */ jsxs4("header", { className: "adn-preferences__header", children: [
204
+ /* @__PURE__ */ jsx4("h2", { className: "adn-preferences__title", children: messages["preferences.title"] }),
205
+ /* @__PURE__ */ jsx4("p", { className: "adn-preferences__description", children: messages["preferences.description"] })
206
+ ] }),
207
+ preferences.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "adn-preferences__state", children: messages["preferences.empty"] }) : /* @__PURE__ */ jsx4("ul", { className: "adn-preferences__items", children: preferences.map((preference) => {
208
+ const inputId = `adn-pref-${preference.category}-${preference.channel}`;
209
+ return /* @__PURE__ */ jsxs4("li", { className: "adn-preferences__item", children: [
210
+ /* @__PURE__ */ jsxs4("label", { className: "adn-preferences__label", htmlFor: inputId, children: [
211
+ /* @__PURE__ */ jsx4(
212
+ "input",
213
+ {
214
+ id: inputId,
215
+ type: "checkbox",
216
+ className: "adn-preferences__checkbox",
217
+ checked: preference.enabled,
218
+ onChange: () => toggleChannel(preference)
219
+ }
220
+ ),
221
+ /* @__PURE__ */ jsx4("span", { className: "adn-preferences__category", children: preference.category }),
222
+ /* @__PURE__ */ jsx4("span", { className: "adn-preferences__channel", children: messages[channelLabelKey(preference.channel)] ?? preference.channel })
223
+ ] }),
224
+ preference.quietHoursStart && preference.quietHoursEnd ? /* @__PURE__ */ jsxs4("span", { className: "adn-preferences__quiet-hours", children: [
225
+ messages["preferences.quietHours"],
226
+ " ",
227
+ preference.quietHoursStart,
228
+ " ",
229
+ messages["preferences.quietHoursTo"],
230
+ " ",
231
+ preference.quietHoursEnd
232
+ ] }) : null
233
+ ] }, inputId);
234
+ }) }),
235
+ /* @__PURE__ */ jsxs4("footer", { className: "adn-preferences__footer", children: [
236
+ /* @__PURE__ */ jsxs4(
237
+ "button",
238
+ {
239
+ type: "button",
240
+ className: "adn-preferences__save",
241
+ onClick: handleSave,
242
+ disabled: updatePreferences.isPending || draft === void 0,
243
+ children: [
244
+ /* @__PURE__ */ jsx4(Save, { className: "adn-preferences__save-icon", "aria-hidden": "true" }),
245
+ messages["preferences.save"]
246
+ ]
247
+ }
248
+ ),
249
+ updatePreferences.isSuccess && draft === void 0 ? /* @__PURE__ */ jsx4("span", { className: "adn-preferences__feedback", role: "status", children: messages["preferences.saved"] }) : null,
250
+ updatePreferences.isError ? /* @__PURE__ */ jsx4("span", { className: "adn-preferences__feedback adn-preferences__feedback--error", role: "alert", children: messages["preferences.error"] }) : null
251
+ ] })
252
+ ] });
253
+ }
254
+
255
+ // src/components/NotificationsWorkspace.tsx
256
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
257
+ function NotificationsWorkspace({
258
+ labels: labelsOverride,
259
+ category,
260
+ showPreferences = true,
261
+ settingsHref,
262
+ onSelectNotification,
263
+ renderHeader,
264
+ renderHeaderActions,
265
+ renderAboveList,
266
+ renderEmpty,
267
+ className
268
+ }) {
269
+ const { messages } = useNotificationContext();
270
+ const label = (key) => labelsOverride?.[key] ?? messages[key] ?? key;
271
+ return /* @__PURE__ */ jsxs5("div", { className: `adn-workspace ${className ?? ""}`, children: [
272
+ renderHeader ? renderHeader() : /* @__PURE__ */ jsxs5("header", { className: "adn-workspace__header", children: [
273
+ /* @__PURE__ */ jsxs5("div", { children: [
274
+ /* @__PURE__ */ jsx5("h1", { className: "adn-workspace__title", children: label("workspace.title") }),
275
+ /* @__PURE__ */ jsx5("p", { className: "adn-workspace__description", children: label("workspace.description") })
276
+ ] }),
277
+ /* @__PURE__ */ jsxs5("div", { className: "adn-workspace__actions", children: [
278
+ renderHeaderActions?.(),
279
+ settingsHref && /* @__PURE__ */ jsx5("a", { href: settingsHref, className: "adn-button adn-button--outline", children: label("workspace.settingsLink") })
280
+ ] })
281
+ ] }),
282
+ renderAboveList?.(),
283
+ /* @__PURE__ */ jsxs5("div", { className: showPreferences ? "adn-workspace__grid" : "adn-workspace__grid--single", children: [
284
+ /* @__PURE__ */ jsx5("section", { className: "adn-workspace__inbox", children: /* @__PURE__ */ jsx5(
285
+ NotificationList,
286
+ {
287
+ ...category ? { category } : {},
288
+ ...onSelectNotification ? { onSelect: onSelectNotification } : {},
289
+ ...renderEmpty ? { renderEmpty } : {}
290
+ }
291
+ ) }),
292
+ showPreferences && /* @__PURE__ */ jsx5("aside", { className: "adn-workspace__aside", children: /* @__PURE__ */ jsx5(PreferencesPanel, {}) })
293
+ ] })
294
+ ] });
295
+ }
296
+
297
+ // src/components/NotificationSettingsWorkspace.tsx
298
+ import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
299
+ function NotificationSettingsWorkspace({
300
+ labels: labelsOverride,
301
+ channels,
302
+ categories,
303
+ previewPayload,
304
+ templateLabelOf,
305
+ renderChannelFields,
306
+ renderHeader,
307
+ renderHeaderActions,
308
+ className
309
+ }) {
310
+ const { messages } = useNotificationContext();
311
+ const label = (key) => labelsOverride?.[key] ?? messages[key] ?? key;
312
+ const preferencesQuery = usePreferences();
313
+ const updatePreferences = useUpdatePreferences();
314
+ const editor = useTemplateEditor(previewPayload ? { previewPayload } : {});
315
+ function isEnabled(category, channel) {
316
+ const row = (preferencesQuery.data ?? []).find(
317
+ (preference) => preference.category === category && preference.channel === channel
318
+ );
319
+ return row?.enabled ?? true;
320
+ }
321
+ function toggle(category, channel) {
322
+ const next = categories.flatMap(
323
+ (eachCategory) => channels.map((eachChannel) => ({
324
+ category: eachCategory.id,
325
+ channel: eachChannel.id,
326
+ enabled: eachCategory.id === category && eachChannel.id === channel ? !isEnabled(category, channel) : isEnabled(eachCategory.id, eachChannel.id)
327
+ }))
328
+ );
329
+ updatePreferences.mutate(next);
459
330
  }
460
- __name(handleSave, "handleSave");
461
- if (isPending) return /* @__PURE__ */ React.createElement("p", {
462
- className: "adn-preferences__state"
463
- }, messages["list.loading"]);
464
- return /* @__PURE__ */ React.createElement("section", {
465
- className: clsx4("adn-preferences", theme.rootClassName, className)
466
- }, /* @__PURE__ */ React.createElement("header", {
467
- className: "adn-preferences__header"
468
- }, /* @__PURE__ */ React.createElement("h2", {
469
- className: "adn-preferences__title"
470
- }, messages["preferences.title"]), /* @__PURE__ */ React.createElement("p", {
471
- className: "adn-preferences__description"
472
- }, messages["preferences.description"])), preferences.length === 0 ? /* @__PURE__ */ React.createElement("p", {
473
- className: "adn-preferences__state"
474
- }, messages["preferences.empty"]) : /* @__PURE__ */ React.createElement("ul", {
475
- className: "adn-preferences__items"
476
- }, preferences.map((preference) => {
477
- const inputId = `adn-pref-${preference.category}-${preference.channel}`;
478
- return /* @__PURE__ */ React.createElement("li", {
479
- key: inputId,
480
- className: "adn-preferences__item"
481
- }, /* @__PURE__ */ React.createElement("label", {
482
- className: "adn-preferences__label",
483
- htmlFor: inputId
484
- }, /* @__PURE__ */ React.createElement("input", {
485
- id: inputId,
486
- type: "checkbox",
487
- className: "adn-preferences__checkbox",
488
- checked: preference.enabled,
489
- onChange: /* @__PURE__ */ __name(() => toggleChannel(preference), "onChange")
490
- }), /* @__PURE__ */ React.createElement("span", {
491
- className: "adn-preferences__category"
492
- }, preference.category), /* @__PURE__ */ React.createElement("span", {
493
- className: "adn-preferences__channel"
494
- }, messages[channelLabelKey(preference.channel)] ?? preference.channel)), preference.quietHoursStart && preference.quietHoursEnd ? /* @__PURE__ */ React.createElement("span", {
495
- className: "adn-preferences__quiet-hours"
496
- }, messages["preferences.quietHours"], " ", preference.quietHoursStart, " ", messages["preferences.quietHoursTo"], " ", preference.quietHoursEnd) : null);
497
- })), /* @__PURE__ */ React.createElement("footer", {
498
- className: "adn-preferences__footer"
499
- }, /* @__PURE__ */ React.createElement("button", {
500
- type: "button",
501
- className: "adn-preferences__save",
502
- onClick: handleSave,
503
- disabled: updatePreferences.isPending || draft === void 0
504
- }, /* @__PURE__ */ React.createElement(Save, {
505
- className: "adn-preferences__save-icon",
506
- "aria-hidden": "true"
507
- }), messages["preferences.save"]), updatePreferences.isSuccess && draft === void 0 ? /* @__PURE__ */ React.createElement("span", {
508
- className: "adn-preferences__feedback",
509
- role: "status"
510
- }, messages["preferences.saved"]) : null, updatePreferences.isError ? /* @__PURE__ */ React.createElement("span", {
511
- className: "adn-preferences__feedback adn-preferences__feedback--error",
512
- role: "alert"
513
- }, messages["preferences.error"]) : null));
331
+ return /* @__PURE__ */ jsxs6("div", { className: `adn-settings ${className ?? ""}`, children: [
332
+ renderHeader ? renderHeader() : /* @__PURE__ */ jsxs6("header", { className: "adn-settings__header", children: [
333
+ /* @__PURE__ */ jsxs6("div", { children: [
334
+ /* @__PURE__ */ jsx6("h1", { className: "adn-settings__title", children: label("settings.title") }),
335
+ /* @__PURE__ */ jsx6("p", { className: "adn-settings__description", children: label("settings.description") })
336
+ ] }),
337
+ renderHeaderActions?.()
338
+ ] }),
339
+ /* @__PURE__ */ jsxs6("section", { className: "adn-settings__channels", children: [
340
+ /* @__PURE__ */ jsx6("h2", { className: "adn-settings__section-title", children: label("settings.channelsTitle") }),
341
+ /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.channelsHint") }),
342
+ categories.map((category) => /* @__PURE__ */ jsxs6("div", { className: "adn-settings__category", children: [
343
+ /* @__PURE__ */ jsx6("p", { className: "adn-settings__category-label", children: category.label }),
344
+ category.hint && /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: category.hint }),
345
+ /* @__PURE__ */ jsx6("div", { className: "adn-settings__channel-grid", children: channels.map((channel) => /* @__PURE__ */ jsxs6("label", { className: "adn-settings__channel", children: [
346
+ /* @__PURE__ */ jsx6(
347
+ "input",
348
+ {
349
+ type: "checkbox",
350
+ checked: isEnabled(category.id, channel.id),
351
+ disabled: updatePreferences.isPending,
352
+ onChange: () => toggle(category.id, channel.id)
353
+ }
354
+ ),
355
+ /* @__PURE__ */ jsxs6("span", { children: [
356
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__channel-label", children: channel.label }),
357
+ channel.hint && /* @__PURE__ */ jsx6("span", { className: "adn-settings__hint", children: channel.hint })
358
+ ] })
359
+ ] }, channel.id)) })
360
+ ] }, category.id))
361
+ ] }),
362
+ /* @__PURE__ */ jsxs6("div", { className: "adn-settings__templates", children: [
363
+ /* @__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") })
367
+ ] }),
368
+ editor.isLoading && /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("list.loading") }),
369
+ !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)) })
389
+ ] }),
390
+ /* @__PURE__ */ jsxs6("section", { className: "adn-settings__editor", children: [
391
+ !editor.draft && /* @__PURE__ */ jsx6("p", { className: "adn-settings__hint", children: label("settings.pickTemplate") }),
392
+ editor.draft && /* @__PURE__ */ jsxs6(Fragment, { children: [
393
+ /* @__PURE__ */ jsxs6("header", { className: "adn-settings__editor-header", children: [
394
+ /* @__PURE__ */ jsxs6("div", { children: [
395
+ /* @__PURE__ */ jsx6("h2", { className: "adn-settings__section-title", children: editor.selected ? templateLabelOf?.(editor.selected) ?? editor.selected.key : "" }),
396
+ /* @__PURE__ */ jsxs6("p", { className: "adn-settings__hint", children: [
397
+ editor.draft.channel,
398
+ " \xB7 ",
399
+ editor.draft.locale,
400
+ " \xB7 ",
401
+ label("settings.version"),
402
+ editor.selected?.version
403
+ ] })
404
+ ] }),
405
+ /* @__PURE__ */ jsx6("button", { type: "button", onClick: editor.clear, children: label("settings.close") })
406
+ ] }),
407
+ /* @__PURE__ */ jsxs6("label", { className: "adn-settings__field", children: [
408
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__field-label", children: label("settings.subject") }),
409
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__hint", children: label("settings.subjectHint") }),
410
+ /* @__PURE__ */ jsx6(
411
+ "input",
412
+ {
413
+ value: editor.draft.subject,
414
+ onChange: (event) => editor.update({ subject: event.target.value })
415
+ }
416
+ )
417
+ ] }),
418
+ /* @__PURE__ */ jsxs6("label", { className: "adn-settings__field", children: [
419
+ /* @__PURE__ */ jsx6("span", { className: "adn-settings__field-label", children: label("settings.body") }),
420
+ /* @__PURE__ */ jsx6(
421
+ "textarea",
422
+ {
423
+ rows: 4,
424
+ value: editor.draft.body,
425
+ onChange: (event) => editor.update({ body: event.target.value })
426
+ }
427
+ )
428
+ ] }),
429
+ renderChannelFields?.({
430
+ channel: editor.draft.channel,
431
+ value: editor.draft.whatsappTemplateName,
432
+ onChange: (value) => editor.update({ whatsappTemplateName: value })
433
+ }),
434
+ editor.preview && /* @__PURE__ */ jsxs6("div", { className: "adn-settings__preview", children: [
435
+ /* @__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 })
438
+ ] }),
439
+ editor.error && /* @__PURE__ */ jsx6("p", { className: "adn-settings__error", children: editor.error }),
440
+ /* @__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") }),
442
+ !editor.isDirty && /* @__PURE__ */ jsx6("span", { className: "adn-settings__hint", children: label("settings.unchanged") })
443
+ ] })
444
+ ] })
445
+ ] })
446
+ ] })
447
+ ] });
514
448
  }
515
- __name(PreferencesPanel, "PreferencesPanel");
516
449
  export {
517
450
  DEFAULT_LOCALE,
518
451
  NOTIFICATION_QUERY_KEYS,
@@ -520,8 +453,12 @@ export {
520
453
  NotificationItem,
521
454
  NotificationList,
522
455
  NotificationProvider,
456
+ NotificationSettingsWorkspace,
457
+ NotificationsWorkspace,
523
458
  PreferencesPanel,
524
459
  flattenNotificationPages,
460
+ formatNotificationTime,
461
+ formatNotificationTimestamp,
525
462
  resolveMessages,
526
463
  useDeleteNotification,
527
464
  useMarkAllAsRead,
@@ -530,8 +467,10 @@ export {
530
467
  useNotificationStream,
531
468
  useNotifications,
532
469
  usePreferences,
470
+ useTemplateEditor,
533
471
  useTemplates,
534
472
  useUnreadCount,
535
- useUpdatePreferences
473
+ useUpdatePreferences,
474
+ useUpsertTemplate
536
475
  };
537
476
  //# sourceMappingURL=index.js.map