@adatechnology/notification-ui 0.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/dist/headless.d.ts +139 -0
- package/dist/headless.js +308 -0
- package/dist/headless.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +537 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +313 -0
- package/dist/styles.css.map +1 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
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");
|
|
291
|
+
|
|
292
|
+
// src/components/NotificationBell.tsx
|
|
293
|
+
import { Bell } from "lucide-react";
|
|
294
|
+
import clsx from "clsx";
|
|
295
|
+
var DEFAULT_MAX_BADGE_COUNT = 99;
|
|
296
|
+
function NotificationBell({ onClick, className, maxBadgeCount = DEFAULT_MAX_BADGE_COUNT }) {
|
|
297
|
+
const { messages, theme } = useNotificationContext();
|
|
298
|
+
const { data: unreadCount = 0 } = useUnreadCount();
|
|
299
|
+
const hasUnread = unreadCount > 0;
|
|
300
|
+
const badgeLabel = unreadCount > maxBadgeCount ? `${maxBadgeCount}+` : String(unreadCount);
|
|
301
|
+
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);
|
|
319
|
+
}
|
|
320
|
+
__name(NotificationBell, "NotificationBell");
|
|
321
|
+
|
|
322
|
+
// src/components/NotificationList.tsx
|
|
323
|
+
import { CheckCheck, Inbox, RotateCw } from "lucide-react";
|
|
324
|
+
import clsx3 from "clsx";
|
|
325
|
+
|
|
326
|
+
// src/components/NotificationItem.tsx
|
|
327
|
+
import { Check, Trash2 } from "lucide-react";
|
|
328
|
+
import clsx2 from "clsx";
|
|
329
|
+
function NotificationItem({ notification, onSelect, className }) {
|
|
330
|
+
const { messages } = useNotificationContext();
|
|
331
|
+
const markAsRead = useMarkAsRead();
|
|
332
|
+
const deleteNotification = useDeleteNotification();
|
|
333
|
+
const isUnread = notification.readAt === void 0;
|
|
334
|
+
function handleSelect() {
|
|
335
|
+
if (isUnread) markAsRead.mutate(notification.id);
|
|
336
|
+
onSelect?.(notification);
|
|
337
|
+
}
|
|
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
|
+
}))));
|
|
375
|
+
}
|
|
376
|
+
__name(NotificationItem, "NotificationItem");
|
|
377
|
+
|
|
378
|
+
// src/components/NotificationList.tsx
|
|
379
|
+
function NotificationList({ category, onSelect, className, components }) {
|
|
380
|
+
const { messages, theme } = useNotificationContext();
|
|
381
|
+
const query = useNotifications({
|
|
382
|
+
category
|
|
383
|
+
});
|
|
384
|
+
const { data: unreadCount = 0 } = useUnreadCount();
|
|
385
|
+
const markAllAsRead = useMarkAllAsRead();
|
|
386
|
+
const Item = components?.Item ?? NotificationItem;
|
|
387
|
+
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);
|
|
431
|
+
}
|
|
432
|
+
__name(NotificationList, "NotificationList");
|
|
433
|
+
|
|
434
|
+
// src/components/PreferencesPanel.tsx
|
|
435
|
+
import { useState } from "react";
|
|
436
|
+
import { Save } from "lucide-react";
|
|
437
|
+
import clsx4 from "clsx";
|
|
438
|
+
function channelLabelKey(channel) {
|
|
439
|
+
return `channel.${channel}`;
|
|
440
|
+
}
|
|
441
|
+
__name(channelLabelKey, "channelLabelKey");
|
|
442
|
+
function PreferencesPanel({ className }) {
|
|
443
|
+
const { messages, theme } = useNotificationContext();
|
|
444
|
+
const { data: saved = [], isPending } = usePreferences();
|
|
445
|
+
const updatePreferences = useUpdatePreferences();
|
|
446
|
+
const [draft, setDraft] = useState(void 0);
|
|
447
|
+
const preferences = draft ?? saved;
|
|
448
|
+
function toggleChannel(target) {
|
|
449
|
+
setDraft(preferences.map((preference) => preference.category === target.category && preference.channel === target.channel ? {
|
|
450
|
+
...preference,
|
|
451
|
+
enabled: !preference.enabled
|
|
452
|
+
} : preference));
|
|
453
|
+
}
|
|
454
|
+
__name(toggleChannel, "toggleChannel");
|
|
455
|
+
function handleSave() {
|
|
456
|
+
updatePreferences.mutate(preferences, {
|
|
457
|
+
onSuccess: /* @__PURE__ */ __name(() => setDraft(void 0), "onSuccess")
|
|
458
|
+
});
|
|
459
|
+
}
|
|
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));
|
|
514
|
+
}
|
|
515
|
+
__name(PreferencesPanel, "PreferencesPanel");
|
|
516
|
+
export {
|
|
517
|
+
DEFAULT_LOCALE,
|
|
518
|
+
NOTIFICATION_QUERY_KEYS,
|
|
519
|
+
NotificationBell,
|
|
520
|
+
NotificationItem,
|
|
521
|
+
NotificationList,
|
|
522
|
+
NotificationProvider,
|
|
523
|
+
PreferencesPanel,
|
|
524
|
+
flattenNotificationPages,
|
|
525
|
+
resolveMessages,
|
|
526
|
+
useDeleteNotification,
|
|
527
|
+
useMarkAllAsRead,
|
|
528
|
+
useMarkAsRead,
|
|
529
|
+
useNotificationContext,
|
|
530
|
+
useNotificationStream,
|
|
531
|
+
useNotifications,
|
|
532
|
+
usePreferences,
|
|
533
|
+
useTemplates,
|
|
534
|
+
useUnreadCount,
|
|
535
|
+
useUpdatePreferences
|
|
536
|
+
};
|
|
537
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/NotificationProvider.tsx","../src/locales/pt-BR.json","../src/locales/en.json","../src/locales/index.ts","../src/hooks/useNotifications.ts","../src/hooks/queryKeys.ts","../src/hooks/useNotificationStream.ts","../src/hooks/usePreferences.ts","../src/components/NotificationBell.tsx","../src/components/NotificationList.tsx","../src/components/NotificationItem.tsx","../src/components/PreferencesPanel.tsx"],"sourcesContent":["/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * Provider do pacote. **Não instancia `QueryClient`** — usa o do host (regra de módulos\n * plugáveis §4.2). Instanciar o próprio criaria dois caches na mesma página: a inbox invalidaria\n * um, e a tela do produto continuaria lendo o outro.\n */\n\nimport { createContext, useContext, useMemo, type ReactNode } from 'react'\nimport type { NotificationClient } from '@adatechnology/notification-client'\n\nimport { DEFAULT_LOCALE, resolveMessages, type NotificationLocale, type NotificationMessages } from './locales'\n\nexport type NotificationTheme = {\n /** Classe aplicada na raiz de cada componente — é por onde o host injeta os tokens dele. */\n readonly rootClassName?: string\n readonly components?: Partial<Record<string, string>>\n}\n\nexport type NotificationContextValue = {\n readonly client: NotificationClient\n readonly messages: NotificationMessages\n readonly theme: NotificationTheme\n /** Polling de fallback quando o SSE não está disponível (segundos); `0` desliga. */\n readonly pollIntervalSeconds: number\n}\n\nconst NotificationContext = createContext<NotificationContextValue | null>(null)\n\nexport type NotificationProviderProps = {\n readonly client: NotificationClient\n readonly locale?: NotificationLocale\n /** Sobrescreve textos pontuais sem trocar o locale inteiro. */\n readonly messageOverrides?: Partial<NotificationMessages>\n readonly theme?: NotificationTheme\n readonly pollIntervalSeconds?: number\n readonly children: ReactNode\n}\n\nexport function NotificationProvider({\n client,\n locale = DEFAULT_LOCALE,\n messageOverrides,\n theme = {},\n pollIntervalSeconds = 60,\n children,\n}: NotificationProviderProps) {\n const value = useMemo<NotificationContextValue>(\n () => ({\n client,\n messages: { ...resolveMessages(locale), ...messageOverrides },\n theme,\n pollIntervalSeconds,\n }),\n [client, locale, messageOverrides, theme, pollIntervalSeconds],\n )\n\n return <NotificationContext.Provider value={value}>{children}</NotificationContext.Provider>\n}\n\nexport function useNotificationContext(): NotificationContextValue {\n const context = useContext(NotificationContext)\n if (!context) throw new Error('notification-ui: componente usado fora de <NotificationProvider>')\n return context\n}\n","{\n \"bell.label\": \"Notificações\",\n \"bell.unreadSuffix\": \"não lidas\",\n \"list.title\": \"Notificações\",\n \"list.empty\": \"Nenhuma notificação por aqui.\",\n \"list.loading\": \"Carregando…\",\n \"list.error\": \"Não foi possível carregar suas notificações.\",\n \"list.retry\": \"Tentar de novo\",\n \"list.loadMore\": \"Carregar mais\",\n \"list.markAllAsRead\": \"Marcar todas como lidas\",\n \"item.markAsRead\": \"Marcar como lida\",\n \"item.delete\": \"Excluir\",\n \"item.unread\": \"Não lida\",\n \"preferences.title\": \"Preferências de notificação\",\n \"preferences.description\": \"Escolha por onde você quer ser avisado em cada assunto.\",\n \"preferences.save\": \"Salvar preferências\",\n \"preferences.saved\": \"Preferências salvas.\",\n \"preferences.error\": \"Não foi possível salvar. Tente de novo.\",\n \"preferences.empty\": \"Nenhuma preferência configurada ainda.\",\n \"preferences.quietHours\": \"Silêncio entre\",\n \"preferences.quietHoursTo\": \"e\",\n \"channel.inbox\": \"No app\",\n \"channel.push\": \"Push\",\n \"channel.email\": \"E-mail\",\n \"channel.whatsapp\": \"WhatsApp\",\n \"channel.sms\": \"SMS\"\n}\n","{\n \"bell.label\": \"Notifications\",\n \"bell.unreadSuffix\": \"unread\",\n \"list.title\": \"Notifications\",\n \"list.empty\": \"Nothing here yet.\",\n \"list.loading\": \"Loading…\",\n \"list.error\": \"We couldn't load your notifications.\",\n \"list.retry\": \"Try again\",\n \"list.loadMore\": \"Load more\",\n \"list.markAllAsRead\": \"Mark all as read\",\n \"item.markAsRead\": \"Mark as read\",\n \"item.delete\": \"Delete\",\n \"item.unread\": \"Unread\",\n \"preferences.title\": \"Notification preferences\",\n \"preferences.description\": \"Choose how you want to be notified about each topic.\",\n \"preferences.save\": \"Save preferences\",\n \"preferences.saved\": \"Preferences saved.\",\n \"preferences.error\": \"Couldn't save. Try again.\",\n \"preferences.empty\": \"No preferences configured yet.\",\n \"preferences.quietHours\": \"Quiet between\",\n \"preferences.quietHoursTo\": \"and\",\n \"channel.inbox\": \"In app\",\n \"channel.push\": \"Push\",\n \"channel.email\": \"Email\",\n \"channel.whatsapp\": \"WhatsApp\",\n \"channel.sms\": \"SMS\"\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * Nenhum texto visível vive em tag ou prop (`web.md` §6). O host sobrescreve chaves pontuais via\n * `messageOverrides` sem precisar fornecer um locale inteiro.\n */\n\nimport ptBR from './pt-BR.json'\nimport en from './en.json'\n\nexport type NotificationMessages = typeof ptBR\nexport type NotificationLocale = 'pt-BR' | 'en'\n\nexport const DEFAULT_LOCALE: NotificationLocale = 'pt-BR'\n\nconst MESSAGES_BY_LOCALE: Record<NotificationLocale, NotificationMessages> = {\n 'pt-BR': ptBR,\n en: en as NotificationMessages,\n}\n\nexport function resolveMessages(locale: NotificationLocale): NotificationMessages {\n return MESSAGES_BY_LOCALE[locale] ?? MESSAGES_BY_LOCALE[DEFAULT_LOCALE]\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * Camada headless — a válvula de escape do item 4 da §4 da regra de módulos plugáveis: quando a\n * tela default não serve, o produto monta a própria sobre estes mesmos hooks e continua herdando\n * upgrades de lógica.\n */\n\nimport { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { NotificationSummary } from '@adatechnology/notification-contracts'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { NOTIFICATION_QUERY_KEYS } from './queryKeys'\n\nexport type UseNotificationsOptions = {\n readonly category?: string\n readonly read?: boolean\n readonly perPage?: number\n}\n\nexport function useNotifications(options: UseNotificationsOptions = {}) {\n const { client } = useNotificationContext()\n\n return useInfiniteQuery({\n queryKey: NOTIFICATION_QUERY_KEYS.list({ category: options.category, read: options.read }),\n initialPageParam: undefined as string | undefined,\n queryFn: ({ pageParam }) =>\n client.listNotifications({\n category: options.category,\n read: options.read,\n cursor: pageParam,\n perPage: options.perPage,\n }),\n getNextPageParam: (lastPage) => lastPage.nextCursor,\n })\n}\n\nexport function useUnreadCount() {\n const { client, pollIntervalSeconds } = useNotificationContext()\n\n return useQuery({\n queryKey: NOTIFICATION_QUERY_KEYS.unreadCount(),\n queryFn: () => client.countUnread(),\n // Polling é o fallback de quem não ligou o SSE. Com o stream ativo,\n // `useNotificationStream` invalida esta chave e o refetch acontece na hora.\n refetchInterval: pollIntervalSeconds > 0 ? pollIntervalSeconds * 1000 : false,\n })\n}\n\nexport function useMarkAsRead() {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: (id: string) => client.markAsRead(id),\n async onMutate(id: string) {\n // Atualização otimista: marcar como lida é ação de um clique, e esperar o round-trip para\n // o badge cair faz a interface parecer travada.\n await queryClient.cancelQueries({ queryKey: NOTIFICATION_QUERY_KEYS.all })\n const previousCount = queryClient.getQueryData<number>(NOTIFICATION_QUERY_KEYS.unreadCount())\n\n queryClient.setQueryData<number>(NOTIFICATION_QUERY_KEYS.unreadCount(), (current) =>\n current === undefined ? current : Math.max(0, current - 1),\n )\n return { previousCount, id }\n },\n onError(_error, _id, context) {\n // Rollback: sem isto o badge ficaria mentindo até o próximo refetch.\n if (context?.previousCount !== undefined) {\n queryClient.setQueryData(NOTIFICATION_QUERY_KEYS.unreadCount(), context.previousCount)\n }\n },\n onSettled() {\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.all })\n },\n })\n}\n\nexport function useMarkAllAsRead() {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: () => client.markAllAsRead(),\n onSettled() {\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.all })\n },\n })\n}\n\nexport function useDeleteNotification() {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: (id: string) => client.deleteNotification(id),\n onSettled() {\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.all })\n },\n })\n}\n\n/** Achata as páginas do infinite query — a lista quase sempre quer o array direto. */\nexport function flattenNotificationPages(\n pages: readonly { data: readonly NotificationSummary[] }[] | undefined,\n): readonly NotificationSummary[] {\n return pages?.flatMap((page) => page.data) ?? []\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n *\n * Chaves centralizadas: o sino e a lista leem o mesmo `unreadCount`, e uma invalidação precisa\n * atingir os dois. Chave montada inline em cada hook é como badge e lista passam a mostrar\n * números diferentes.\n */\n\nexport const NOTIFICATION_QUERY_KEYS = {\n all: ['notifications'] as const,\n list: (filters: { category?: string; read?: boolean }) => ['notifications', 'list', filters] as const,\n unreadCount: () => ['notifications', 'unread-count'] as const,\n preferences: () => ['notifications', 'preferences'] as const,\n templates: () => ['notifications', 'templates'] as const,\n} as const\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n */\n\nimport { useEffect } from 'react'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { subscribeToNotificationStream } from '@adatechnology/notification-client'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { NOTIFICATION_QUERY_KEYS } from './queryKeys'\n\nexport type UseNotificationStreamOptions = {\n /** Desligado por omissão: abrir SSE é decisão do host, que sabe se o backend expõe a rota. */\n readonly enabled?: boolean\n readonly onEvent?: (event: { event: string; data: unknown }) => void\n}\n\n/**\n * Liga o SSE do inbox ao cache do TanStack: cada evento invalida as chaves, e o badge e a lista\n * atualizam juntos. `useEffect` aqui é o uso correto do hook — sincronizar com um sistema externo\n * (`react.md`, \"useEffect só para sincronizar com sistemas externos\"), não transformar dado.\n */\nexport function useNotificationStream(options: UseNotificationStreamOptions = {}): void {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n const enabled = options.enabled ?? false\n const onEvent = options.onEvent\n\n useEffect(() => {\n if (!enabled) return\n\n let subscription: { close(): void } | undefined\n let cancelled = false\n\n void client.resolveStreamRequest().then(({ url, headers }) => {\n // A resolução do token é assíncrona; se o componente desmontou nesse meio-tempo, não abre\n // conexão nenhuma — senão sobraria um stream órfão a cada navegação rápida.\n if (cancelled) return\n\n subscription = subscribeToNotificationStream({\n url,\n headers,\n onEvent: (event) => {\n onEvent?.(event)\n void queryClient.invalidateQueries({ queryKey: NOTIFICATION_QUERY_KEYS.all })\n },\n })\n })\n\n return () => {\n cancelled = true\n subscription?.close()\n }\n }, [client, queryClient, enabled, onEvent])\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { NotificationPreference } from '@adatechnology/notification-contracts'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { NOTIFICATION_QUERY_KEYS } from './queryKeys'\n\nexport function usePreferences() {\n const { client } = useNotificationContext()\n\n return useQuery({\n queryKey: NOTIFICATION_QUERY_KEYS.preferences(),\n queryFn: () => client.getPreferences(),\n })\n}\n\nexport function useUpdatePreferences() {\n const { client } = useNotificationContext()\n const queryClient = useQueryClient()\n\n return useMutation({\n mutationFn: (preferences: readonly NotificationPreference[]) => client.updatePreferences(preferences),\n onSuccess(saved) {\n // A resposta já é o estado persistido — usar direto evita um refetch e o piscar do painel\n // entre o valor otimista e o confirmado.\n queryClient.setQueryData(NOTIFICATION_QUERY_KEYS.preferences(), saved)\n },\n })\n}\n\nexport function useTemplates() {\n const { client } = useNotificationContext()\n\n return useQuery({\n queryKey: NOTIFICATION_QUERY_KEYS.templates(),\n queryFn: () => client.listTemplates(),\n })\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n */\n\nimport { Bell } from 'lucide-react'\nimport clsx from 'clsx'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { useUnreadCount } from '../hooks/useNotifications'\n\nexport type NotificationBellProps = {\n readonly onClick?: () => void\n readonly className?: string\n /** Acima disso o badge mostra `99+`; um número de 4 dígitos estoura o desenho do badge. */\n readonly maxBadgeCount?: number\n}\n\nconst DEFAULT_MAX_BADGE_COUNT = 99\n\nexport function NotificationBell({ onClick, className, maxBadgeCount = DEFAULT_MAX_BADGE_COUNT }: NotificationBellProps) {\n const { messages, theme } = useNotificationContext()\n const { data: unreadCount = 0 } = useUnreadCount()\n\n const hasUnread = unreadCount > 0\n const badgeLabel = unreadCount > maxBadgeCount ? `${maxBadgeCount}+` : String(unreadCount)\n const accessibleLabel = hasUnread\n ? `${messages['bell.label']}: ${unreadCount} ${messages['bell.unreadSuffix']}`\n : messages['bell.label']\n\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={clsx('adn-bell', theme.rootClassName, className)}\n // Botão só-ícone exige rótulo acessível (regra de ícones, `web.md` §9).\n aria-label={accessibleLabel}\n >\n {/* Decorativo: o rótulo acessível já está no botão, e anunciar o ícone repetiria a ação. */}\n <Bell className=\"adn-bell__icon\" aria-hidden=\"true\" />\n {hasUnread ? (\n // `aria-live` para leitor de tela anunciar a mudança do contador sem o usuário reabrir\n // o menu; `polite` porque notificação nova não deve interromper o que ele está lendo.\n <span className=\"adn-bell__badge\" aria-live=\"polite\">\n {badgeLabel}\n </span>\n ) : null}\n </button>\n )\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n */\n\nimport { CheckCheck, Inbox, RotateCw } from 'lucide-react'\nimport clsx from 'clsx'\nimport type { NotificationSummary } from '@adatechnology/notification-contracts'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { flattenNotificationPages, useMarkAllAsRead, useNotifications, useUnreadCount } from '../hooks/useNotifications'\nimport { NotificationItem } from './NotificationItem'\n\nexport type NotificationListProps = {\n readonly category?: string\n readonly onSelect?: (notification: NotificationSummary) => void\n readonly className?: string\n /** Slot: o host troca a linha sem reimplementar a lista (regra §4.3, slots e overrides). */\n readonly components?: { readonly Item?: typeof NotificationItem }\n}\n\nexport function NotificationList({ category, onSelect, className, components }: NotificationListProps) {\n const { messages, theme } = useNotificationContext()\n const query = useNotifications({ category })\n const { data: unreadCount = 0 } = useUnreadCount()\n const markAllAsRead = useMarkAllAsRead()\n\n const Item = components?.Item ?? NotificationItem\n const notifications = flattenNotificationPages(query.data?.pages)\n\n return (\n <section className={clsx('adn-list', theme.rootClassName, className)} aria-label={messages['list.title']}>\n <header className=\"adn-list__header\">\n <h2 className=\"adn-list__title\">{messages['list.title']}</h2>\n {unreadCount > 0 ? (\n <button\n type=\"button\"\n className=\"adn-list__bulk-action\"\n onClick={() => markAllAsRead.mutate()}\n disabled={markAllAsRead.isPending}\n >\n <CheckCheck className=\"adn-list__bulk-icon\" aria-hidden=\"true\" />\n {messages['list.markAllAsRead']}\n </button>\n ) : null}\n </header>\n\n {query.isPending ? <p className=\"adn-list__state\">{messages['list.loading']}</p> : null}\n\n {query.isError ? (\n <div className=\"adn-list__state adn-list__state--error\">\n <p>{messages['list.error']}</p>\n <button type=\"button\" className=\"adn-list__retry\" onClick={() => void query.refetch()}>\n <RotateCw className=\"adn-list__retry-icon\" aria-hidden=\"true\" />\n {messages['list.retry']}\n </button>\n </div>\n ) : null}\n\n {!query.isPending && !query.isError && notifications.length === 0 ? (\n <div className=\"adn-list__state adn-list__state--empty\">\n <Inbox className=\"adn-list__empty-icon\" aria-hidden=\"true\" />\n <p>{messages['list.empty']}</p>\n </div>\n ) : null}\n\n {notifications.length > 0 ? (\n <ul className=\"adn-list__items\">\n {notifications.map((notification) => (\n <Item key={notification.id} notification={notification} onSelect={onSelect} />\n ))}\n </ul>\n ) : null}\n\n {query.hasNextPage ? (\n <button\n type=\"button\"\n className=\"adn-list__load-more\"\n onClick={() => void query.fetchNextPage()}\n disabled={query.isFetchingNextPage}\n >\n {messages['list.loadMore']}\n </button>\n ) : null}\n </section>\n )\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n */\n\nimport { Check, Trash2 } from 'lucide-react'\nimport clsx from 'clsx'\nimport type { NotificationSummary } from '@adatechnology/notification-contracts'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { useDeleteNotification, useMarkAsRead } from '../hooks/useNotifications'\n\nexport type NotificationItemProps = {\n readonly notification: NotificationSummary\n readonly onSelect?: (notification: NotificationSummary) => void\n readonly className?: string\n}\n\nexport function NotificationItem({ notification, onSelect, className }: NotificationItemProps) {\n const { messages } = useNotificationContext()\n const markAsRead = useMarkAsRead()\n const deleteNotification = useDeleteNotification()\n\n const isUnread = notification.readAt === undefined\n\n function handleSelect(): void {\n if (isUnread) markAsRead.mutate(notification.id)\n onSelect?.(notification)\n }\n\n return (\n <li className={clsx('adn-item', isUnread && 'adn-item--unread', className)}>\n {/* Semântico: a linha inteira é clicável e precisa ser alcançável por teclado — `button`\n entrega Enter, Espaço e foco de graça, e um `div` com onClick não (web.md §Acessibilidade). */}\n <button type=\"button\" className=\"adn-item__main\" onClick={handleSelect}>\n <span className=\"adn-item__title\">{notification.title}</span>\n <span className=\"adn-item__body\">{notification.body}</span>\n {isUnread ? <span className=\"adn-item__unread-dot\" aria-label={messages['item.unread']} /> : null}\n </button>\n\n <div className=\"adn-item__actions\">\n {isUnread ? (\n <button\n type=\"button\"\n className=\"adn-item__action\"\n onClick={() => markAsRead.mutate(notification.id)}\n disabled={markAsRead.isPending}\n aria-label={messages['item.markAsRead']}\n title={messages['item.markAsRead']}\n >\n <Check className=\"adn-item__action-icon\" aria-hidden=\"true\" />\n </button>\n ) : null}\n\n <button\n type=\"button\"\n className=\"adn-item__action adn-item__action--danger\"\n onClick={() => deleteNotification.mutate(notification.id)}\n disabled={deleteNotification.isPending}\n aria-label={messages['item.delete']}\n title={messages['item.delete']}\n >\n {/* Ação destrutiva: ícone reforça o peso antes do clique (regra de ícones). */}\n <Trash2 className=\"adn-item__action-icon\" aria-hidden=\"true\" />\n </button>\n </div>\n </li>\n )\n}\n","/**\n * Copyright (c) 2026 Ada Technology. MIT License.\n */\n\nimport { useState } from 'react'\nimport { Save } from 'lucide-react'\nimport clsx from 'clsx'\nimport type { NotificationPreference } from '@adatechnology/notification-contracts'\n\nimport { useNotificationContext } from '../NotificationProvider'\nimport { usePreferences, useUpdatePreferences } from '../hooks/usePreferences'\n\nexport type PreferencesPanelProps = {\n readonly className?: string\n}\n\nfunction channelLabelKey(channel: string): string {\n return `channel.${channel}`\n}\n\nexport function PreferencesPanel({ className }: PreferencesPanelProps) {\n const { messages, theme } = useNotificationContext()\n const { data: saved = [], isPending } = usePreferences()\n const updatePreferences = useUpdatePreferences()\n\n // Rascunho local só existe entre abrir e salvar: `undefined` significa \"ainda não mexeu\", e é o\n // que faz o painel refletir o servidor sem sobrescrever edição em andamento a cada refetch.\n const [draft, setDraft] = useState<NotificationPreference[] | undefined>(undefined)\n const preferences = draft ?? saved\n\n function toggleChannel(target: NotificationPreference): void {\n setDraft(\n preferences.map((preference) =>\n preference.category === target.category && preference.channel === target.channel\n ? { ...preference, enabled: !preference.enabled }\n : preference,\n ),\n )\n }\n\n function handleSave(): void {\n updatePreferences.mutate(preferences, { onSuccess: () => setDraft(undefined) })\n }\n\n if (isPending) return <p className=\"adn-preferences__state\">{messages['list.loading']}</p>\n\n return (\n <section className={clsx('adn-preferences', theme.rootClassName, className)}>\n <header className=\"adn-preferences__header\">\n <h2 className=\"adn-preferences__title\">{messages['preferences.title']}</h2>\n <p className=\"adn-preferences__description\">{messages['preferences.description']}</p>\n </header>\n\n {preferences.length === 0 ? (\n <p className=\"adn-preferences__state\">{messages['preferences.empty']}</p>\n ) : (\n <ul className=\"adn-preferences__items\">\n {preferences.map((preference) => {\n const inputId = `adn-pref-${preference.category}-${preference.channel}`\n return (\n <li key={inputId} className=\"adn-preferences__item\">\n {/* `label` associado por `htmlFor`: aumenta a área de toque e é o que faz o\n leitor de tela anunciar canal e estado juntos. */}\n <label className=\"adn-preferences__label\" htmlFor={inputId}>\n <input\n id={inputId}\n type=\"checkbox\"\n className=\"adn-preferences__checkbox\"\n checked={preference.enabled}\n onChange={() => toggleChannel(preference)}\n />\n <span className=\"adn-preferences__category\">{preference.category}</span>\n <span className=\"adn-preferences__channel\">\n {messages[channelLabelKey(preference.channel) as keyof typeof messages] ?? preference.channel}\n </span>\n </label>\n\n {preference.quietHoursStart && preference.quietHoursEnd ? (\n <span className=\"adn-preferences__quiet-hours\">\n {messages['preferences.quietHours']} {preference.quietHoursStart}{' '}\n {messages['preferences.quietHoursTo']} {preference.quietHoursEnd}\n </span>\n ) : null}\n </li>\n )\n })}\n </ul>\n )}\n\n <footer className=\"adn-preferences__footer\">\n {/* Ação primária de formulário com convenção clara → ícone (regra de ícones, web.md §9). */}\n <button\n type=\"button\"\n className=\"adn-preferences__save\"\n onClick={handleSave}\n disabled={updatePreferences.isPending || draft === undefined}\n >\n <Save className=\"adn-preferences__save-icon\" aria-hidden=\"true\" />\n {messages['preferences.save']}\n </button>\n\n {updatePreferences.isSuccess && draft === undefined ? (\n <span className=\"adn-preferences__feedback\" role=\"status\">\n {messages['preferences.saved']}\n </span>\n ) : null}\n {updatePreferences.isError ? (\n <span className=\"adn-preferences__feedback adn-preferences__feedback--error\" role=\"alert\">\n {messages['preferences.error']}\n </span>\n ) : null}\n </footer>\n </section>\n )\n}\n"],"mappings":"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","names":["createContext","useContext","useMemo","DEFAULT_LOCALE","MESSAGES_BY_LOCALE","ptBR","en","resolveMessages","locale","NotificationContext","createContext","NotificationProvider","client","locale","DEFAULT_LOCALE","messageOverrides","theme","pollIntervalSeconds","children","value","useMemo","messages","resolveMessages","Provider","useNotificationContext","context","useContext","Error","useInfiniteQuery","useMutation","useQuery","useQueryClient","NOTIFICATION_QUERY_KEYS","all","list","filters","unreadCount","preferences","templates","useNotifications","options","client","useNotificationContext","useInfiniteQuery","queryKey","NOTIFICATION_QUERY_KEYS","list","category","read","initialPageParam","undefined","queryFn","pageParam","listNotifications","cursor","perPage","getNextPageParam","lastPage","nextCursor","useUnreadCount","pollIntervalSeconds","useQuery","unreadCount","countUnread","refetchInterval","useMarkAsRead","queryClient","useQueryClient","useMutation","mutationFn","id","markAsRead","onMutate","cancelQueries","all","previousCount","getQueryData","setQueryData","current","Math","max","onError","_error","_id","context","onSettled","invalidateQueries","useMarkAllAsRead","markAllAsRead","useDeleteNotification","deleteNotification","flattenNotificationPages","pages","flatMap","page","data","useEffect","useQueryClient","subscribeToNotificationStream","useNotificationStream","options","client","useNotificationContext","queryClient","useQueryClient","enabled","onEvent","useEffect","subscription","cancelled","resolveStreamRequest","then","url","headers","subscribeToNotificationStream","event","invalidateQueries","queryKey","NOTIFICATION_QUERY_KEYS","all","close","useMutation","useQuery","useQueryClient","usePreferences","client","useNotificationContext","useQuery","queryKey","NOTIFICATION_QUERY_KEYS","preferences","queryFn","getPreferences","useUpdatePreferences","queryClient","useQueryClient","useMutation","mutationFn","updatePreferences","onSuccess","saved","setQueryData","useTemplates","templates","listTemplates","Bell","clsx","DEFAULT_MAX_BADGE_COUNT","NotificationBell","onClick","className","maxBadgeCount","messages","theme","useNotificationContext","data","unreadCount","useUnreadCount","hasUnread","badgeLabel","String","accessibleLabel","button","type","clsx","rootClassName","aria-label","Bell","aria-hidden","span","aria-live","CheckCheck","Inbox","RotateCw","clsx","Check","Trash2","clsx","NotificationItem","notification","onSelect","className","messages","useNotificationContext","markAsRead","useMarkAsRead","deleteNotification","useDeleteNotification","isUnread","readAt","undefined","handleSelect","mutate","id","li","clsx","button","type","onClick","span","title","body","aria-label","div","disabled","isPending","Check","aria-hidden","Trash2","NotificationList","category","onSelect","className","components","messages","theme","useNotificationContext","query","useNotifications","data","unreadCount","useUnreadCount","markAllAsRead","useMarkAllAsRead","Item","NotificationItem","notifications","flattenNotificationPages","pages","section","clsx","rootClassName","aria-label","header","h2","button","type","onClick","mutate","disabled","isPending","CheckCheck","aria-hidden","p","isError","div","refetch","RotateCw","length","Inbox","ul","map","notification","key","id","hasNextPage","fetchNextPage","isFetchingNextPage","useState","Save","clsx","channelLabelKey","channel","PreferencesPanel","className","messages","theme","useNotificationContext","data","saved","isPending","usePreferences","updatePreferences","useUpdatePreferences","draft","setDraft","useState","undefined","preferences","toggleChannel","target","map","preference","category","enabled","handleSave","mutate","onSuccess","p","section","clsx","rootClassName","header","h2","length","ul","inputId","li","key","label","htmlFor","input","id","type","checked","onChange","span","quietHoursStart","quietHoursEnd","footer","button","onClick","disabled","Save","aria-hidden","isSuccess","role","isError"]}
|