@ttt-productions/notification-core 0.4.1 → 0.4.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.
Files changed (41) hide show
  1. package/README.md +30 -138
  2. package/dist/react/components/NotificationList.d.ts +1 -1
  3. package/dist/react/components/NotificationList.d.ts.map +1 -1
  4. package/dist/react/components/NotificationList.js +7 -20
  5. package/dist/react/components/NotificationList.js.map +1 -1
  6. package/dist/react/hooks/useArchiveAllNotifications.d.ts +10 -12
  7. package/dist/react/hooks/useArchiveAllNotifications.d.ts.map +1 -1
  8. package/dist/react/hooks/useArchiveAllNotifications.js +10 -58
  9. package/dist/react/hooks/useArchiveAllNotifications.js.map +1 -1
  10. package/dist/react/hooks/useArchiveNotification.d.ts +12 -11
  11. package/dist/react/hooks/useArchiveNotification.d.ts.map +1 -1
  12. package/dist/react/hooks/useArchiveNotification.js +12 -39
  13. package/dist/react/hooks/useArchiveNotification.js.map +1 -1
  14. package/dist/react/hooks/useUnreadCount.d.ts +30 -21
  15. package/dist/react/hooks/useUnreadCount.d.ts.map +1 -1
  16. package/dist/react/hooks/useUnreadCount.js +29 -15
  17. package/dist/react/hooks/useUnreadCount.js.map +1 -1
  18. package/dist/server/archiveNotificationHelper.d.ts +14 -3
  19. package/dist/server/archiveNotificationHelper.d.ts.map +1 -1
  20. package/dist/server/archiveNotificationHelper.js +35 -7
  21. package/dist/server/archiveNotificationHelper.js.map +1 -1
  22. package/dist/server/createNotificationHelper.d.ts +1 -1
  23. package/dist/server/createNotificationHelper.d.ts.map +1 -1
  24. package/dist/server/createNotificationHelper.js +35 -13
  25. package/dist/server/createNotificationHelper.js.map +1 -1
  26. package/dist/server/index.d.ts +1 -0
  27. package/dist/server/index.d.ts.map +1 -1
  28. package/dist/server/index.js +1 -0
  29. package/dist/server/index.js.map +1 -1
  30. package/dist/server/markSeenHelper.d.ts +17 -0
  31. package/dist/server/markSeenHelper.d.ts.map +1 -0
  32. package/dist/server/markSeenHelper.js +29 -0
  33. package/dist/server/markSeenHelper.js.map +1 -0
  34. package/dist/server/processBatchHelper.d.ts.map +1 -1
  35. package/dist/server/processBatchHelper.js +12 -15
  36. package/dist/server/processBatchHelper.js.map +1 -1
  37. package/dist/server/types.d.ts +7 -3
  38. package/dist/server/types.d.ts.map +1 -1
  39. package/dist/types.d.ts +45 -11
  40. package/dist/types.d.ts.map +1 -1
  41. package/package.json +73 -73
package/README.md CHANGED
@@ -1,156 +1,48 @@
1
1
  # @ttt-productions/notification-core
2
2
 
3
- Shared notification system for TTT Productions apps with Firestore integration.
3
+ Generic notification system for TTT Productions apps a two-tier
4
+ **active → history** model with dedup, batch processing, and themed React UI.
4
5
 
5
- ## Installation
6
+ ## Entrypoints
6
7
 
7
- ```bash
8
- npm install @ttt-productions/notification-core
9
- ```
10
-
11
- ## Features
8
+ - `@ttt-productions/notification-core` — root-exported shared types.
9
+ - `@ttt-productions/notification-core/react` — React hooks/components.
10
+ - `@ttt-productions/notification-core/server` — backend helper utilities.
12
11
 
13
- - 🔔 Real-time notification updates via Firestore listeners
14
- - 📊 Unread count tracking
15
- - ✅ Mark as read functionality (single or batch)
16
- - 🎯 Type-safe notification system with TypeScript
17
- - 🔥 Built on `@ttt-productions/query-core` for optimal caching
12
+ Backend/Functions code should avoid the `/react` subpath.
18
13
 
19
- ## Usage
14
+ ## Model
20
15
 
21
- ### Fetch Notifications
22
-
23
- ```tsx
24
- import { useNotifications } from '@ttt-productions/notification-core';
25
-
26
- function NotificationList() {
27
- const { data: notifications, isLoading } = useNotifications({
28
- userId: currentUser.uid,
29
- subscribe: true, // Real-time updates
30
- });
31
-
32
- if (isLoading) return <div>Loading...</div>;
33
-
34
- return (
35
- <ul>
36
- {notifications?.map((notification) => (
37
- <li key={notification.id}>
38
- {notification.title} - {notification.message}
39
- </li>
40
- ))}
41
- </ul>
42
- );
43
- }
44
- ```
16
+ There is **no `isRead` flag** — existence in the active collection means unread.
17
+ Archiving moves a notification from the active collection to history (carrying an
18
+ `ArchivalInfo` audit trail). Duplicate triggers for the same `dedupKey`
19
+ increment a single active doc's `count` and append the actor.
45
20
 
46
- ### Unread Count Badge
47
-
48
- ```tsx
49
- import { useUnreadCount } from '@ttt-productions/notification-core';
50
-
51
- function NotificationBadge() {
52
- const { data: unreadCount } = useUnreadCount({
53
- userId: currentUser.uid,
54
- subscribe: true,
55
- });
56
-
57
- return <span className="badge">{unreadCount}</span>;
58
- }
59
- ```
60
-
61
- ### Mark as Read
62
-
63
- ```tsx
64
- import { useMarkAsRead } from '@ttt-productions/notification-core';
21
+ ### Identity is id-only
65
22
 
66
- function NotificationItem({ notification }) {
67
- const markAsRead = useMarkAsRead({ userId: currentUser.uid });
23
+ Shared notification docs persist actor **ids only** — `NotificationDoc`
24
+ exposes `latestActorIds`, and `PendingNotification` exposes `actorId`. There are
25
+ **no persisted display names** (no `latestActorNames` / `actorName`); names are
26
+ resolved at read time by the consuming app (e.g. from `publicUsers`) so they
27
+ never drift in the stored doc.
68
28
 
69
- const handleClick = () => {
70
- markAsRead.mutate({ notificationId: notification.id });
71
- // Navigate to target
72
- };
73
-
74
- return <div onClick={handleClick}>{notification.message}</div>;
75
- }
76
- ```
77
-
78
- ### Mark All as Read
29
+ ## Usage
79
30
 
80
31
  ```tsx
81
- import { useMarkAllAsRead, useNotifications } from '@ttt-productions/notification-core';
82
-
83
- function NotificationPanel() {
84
- const { data: notifications } = useNotifications({
85
- userId: currentUser.uid,
86
- unreadOnly: true,
87
- });
88
-
89
- const markAllAsRead = useMarkAllAsRead({ userId: currentUser.uid });
90
-
91
- const handleMarkAllRead = () => {
92
- const unreadIds = notifications
93
- ?.filter((n) => !n.isRead)
94
- .map((n) => n.id) ?? [];
95
-
96
- if (unreadIds.length > 0) {
97
- markAllAsRead.mutate({ notificationIds: unreadIds });
98
- }
99
- };
100
-
101
- return <button onClick={handleMarkAllRead}>Mark All Read</button>;
102
- }
103
- ```
104
-
105
- ## Data Structure
106
-
107
- Notifications are stored in Firestore at:
108
- ```
109
- userData/{userId}/metadata/notifications/{notificationId}
110
- ```
32
+ import { useActiveNotifications } from '@ttt-productions/notification-core/react';
111
33
 
112
- Each notification document has:
113
- ```typescript
114
- interface Notification {
115
- id: string;
116
- type: string; // App-specific type
117
- title: string;
118
- message: string;
119
- targetPath: string; // Navigation target
120
- targetParams?: Record<string, any>;
121
- isRead: boolean;
122
- createdAt: Timestamp;
123
- metadata?: Record<string, any>; // Type-specific data
124
- }
34
+ const { data: notifications, isLoading } = useActiveNotifications({
35
+ config: TTT_NOTIFICATION_CONFIG,
36
+ userId: currentUser.uid,
37
+ category: 'user',
38
+ });
125
39
  ```
126
40
 
127
- ## Backend Integration
41
+ Backend create/queue (id-only — pass `actorId`, never a name):
128
42
 
129
- Apps should implement a callable Cloud Function to create notifications:
43
+ ```ts
44
+ import { createNotificationHelper } from '@ttt-productions/notification-core/server';
130
45
 
131
- ```typescript
132
- // In your app's functions/src/notifications.ts
133
- import { onCall } from 'firebase-functions/v2/https';
134
- import { getFirestore, Timestamp } from 'firebase-admin/firestore';
135
-
136
- export const createNotification = onCall(async (request) => {
137
- const { userId, type, title, message, targetPath, targetParams, metadata } = request.data;
138
-
139
- await getFirestore()
140
- .collection('userData')
141
- .doc(userId)
142
- .collection('metadata')
143
- .doc('notifications')
144
- .collection('notifications')
145
- .add({
146
- type,
147
- title,
148
- message,
149
- targetPath,
150
- targetParams,
151
- isRead: false,
152
- createdAt: Timestamp.now(),
153
- metadata,
154
- });
155
- });
46
+ const notifier = createNotificationHelper(db, TTT_NOTIFICATION_CONFIG);
47
+ await notifier.send({ type: 'content_report', actorId, metadata: { itemId } });
156
48
  ```
@@ -2,5 +2,5 @@ import type { NotificationListProps } from '../../types.js';
2
2
  /**
3
3
  * Scrollable list of active notifications with click-to-archive and clear-all.
4
4
  */
5
- export declare function NotificationList({ config, userId, category, onNotificationClick, onClearAll, refetchInterval, device, emptyText, }: NotificationListProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function NotificationList({ config, userId, category, onNotificationClick, archiveFn, archiveAllFn, onClearAll, refetchInterval, emptyText, }: NotificationListProps): import("react/jsx-runtime").JSX.Element;
6
6
  //# sourceMappingURL=NotificationList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationList.d.ts","sourceRoot":"","sources":["../../../src/react/components/NotificationList.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE7E;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,MAAM,EACN,QAAQ,EACR,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,MAAc,EACd,SAAS,GACV,EAAE,qBAAqB,2CA2IvB"}
1
+ {"version":3,"file":"NotificationList.d.ts","sourceRoot":"","sources":["../../../src/react/components/NotificationList.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE7E;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,MAAM,EACN,QAAQ,EACR,mBAAmB,EACnB,SAAS,EACT,YAAY,EACZ,UAAU,EACV,eAAe,EACf,SAAS,GACV,EAAE,qBAAqB,2CA8HvB"}
@@ -11,7 +11,7 @@ import { formatRelativeTime } from './relative-time.js';
11
11
  /**
12
12
  * Scrollable list of active notifications with click-to-archive and clear-all.
13
13
  */
14
- export function NotificationList({ config, userId, category, onNotificationClick, onClearAll, refetchInterval, device = 'web', emptyText, }) {
14
+ export function NotificationList({ config, userId, category, onNotificationClick, archiveFn, archiveAllFn, onClearAll, refetchInterval, emptyText, }) {
15
15
  const { data: notifications, isLoading, hasNextPage, nextPage, } = useActiveNotifications({
16
16
  config,
17
17
  userId,
@@ -25,46 +25,33 @@ export function NotificationList({ config, userId, category, onNotificationClick
25
25
  refetchInterval,
26
26
  });
27
27
  const archiveMutation = useArchiveNotification({
28
- config,
29
28
  userId,
30
29
  category,
30
+ archiveFn,
31
31
  });
32
32
  const archiveAllMutation = useArchiveAllNotifications({
33
- config,
34
33
  userId,
35
34
  category,
35
+ archiveAllFn,
36
36
  });
37
37
  const handleNotificationClick = useCallback(async (notification) => {
38
38
  try {
39
- await archiveMutation.mutateAsync({
40
- notificationId: notification.id,
41
- archivalInfo: {
42
- archivedBy: userId,
43
- archivedAt: Date.now(),
44
- device,
45
- },
46
- });
39
+ await archiveMutation.mutateAsync(notification.id);
47
40
  }
48
41
  catch {
49
42
  // Still navigate even if archive fails
50
43
  }
51
44
  onNotificationClick(notification);
52
- }, [archiveMutation, userId, device, onNotificationClick]);
45
+ }, [archiveMutation, onNotificationClick]);
53
46
  const handleClearAll = useCallback(async () => {
54
47
  try {
55
- await archiveAllMutation.mutateAsync({
56
- archivalInfo: {
57
- archivedBy: userId,
58
- archivedAt: Date.now(),
59
- device,
60
- },
61
- });
48
+ await archiveAllMutation.mutateAsync();
62
49
  }
63
50
  catch {
64
51
  // Silently fail
65
52
  }
66
53
  onClearAll?.();
67
- }, [archiveAllMutation, userId, device, onClearAll]);
54
+ }, [archiveAllMutation, onClearAll]);
68
55
  const getTypeIcon = useCallback((type) => {
69
56
  const typeConfig = config.types[type];
70
57
  return typeConfig?.icon ?? '🔔';
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationList.js","sourceRoot":"","sources":["../../../src/react/components/NotificationList.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NotificationList.js","sourceRoot":"","sources":["../../../src/react/components/NotificationList.tsx"],"names":[],"mappings":"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"}
@@ -1,24 +1,22 @@
1
- import type { ArchivalInfo, UseArchiveAllNotificationsOptions } from '../../types.js';
1
+ import type { UseArchiveAllNotificationsOptions } from '../../types.js';
2
2
  /**
3
- * Archive all active notifications in batches.
4
- * Reads active docs in batches of 50, archives each batch, repeats until empty.
3
+ * Archive the caller's whole category through an app-supplied callable adapter.
4
+ *
5
+ * Like {@link useArchiveNotification}, this performs no client Firestore writes
6
+ * — paging + ownership-checked deletion happen server-side via the callable the
7
+ * app wires into `archiveAllFn`. On success it invalidates the read keys.
5
8
  *
6
9
  * @example
7
10
  * ```tsx
11
+ * const archiveNotification = httpsCallable(functions, 'archiveNotification');
8
12
  * const archiveAll = useArchiveAllNotifications({
9
- * config: TTT_NOTIFICATION_CONFIG,
10
13
  * userId: user.uid,
11
14
  * category: 'user',
15
+ * archiveAllFn: () => archiveNotification({ category: 'user', scope: { kind: 'all' } }),
12
16
  * });
13
17
  *
14
- * archiveAll.mutate({
15
- * archivalInfo: { archivedBy: user.uid, archivedAt: Date.now(), device: 'web' },
16
- * });
18
+ * archiveAll.mutate();
17
19
  * ```
18
20
  */
19
- export declare function useArchiveAllNotifications({ config, userId, category, invalidateKeys, }: UseArchiveAllNotificationsOptions): import("@tanstack/react-query").UseMutationResult<{
20
- totalArchived: number;
21
- }, Error, {
22
- archivalInfo: ArchivalInfo;
23
- }, unknown>;
21
+ export declare function useArchiveAllNotifications({ userId, category, archiveAllFn, invalidateKeys, }: UseArchiveAllNotificationsOptions): import("@tanstack/react-query").UseMutationResult<unknown, Error, void, unknown>;
24
22
  //# sourceMappingURL=useArchiveAllNotifications.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useArchiveAllNotifications.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveAllNotifications.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAEV,YAAY,EACZ,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AAIxB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,cAAc,GACf,EAAE,iCAAiC;;;kBAehB,YAAY;YAkE/B"}
1
+ {"version":3,"file":"useArchiveAllNotifications.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveAllNotifications.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,gBAAgB,CAAC;AAExE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,cAAc,GACf,EAAE,iCAAiC,oFAkBnC"}
@@ -1,81 +1,33 @@
1
1
  'use client';
2
2
  import { useMutation, useQueryClient } from '@tanstack/react-query';
3
- import { collection, query, where, orderBy, limit, getDocs, doc, writeBatch, } from 'firebase/firestore';
4
- import { useFirestoreDb } from '@ttt-productions/query-core/react';
5
- const BATCH_SIZE = 50;
6
3
  /**
7
- * Archive all active notifications in batches.
8
- * Reads active docs in batches of 50, archives each batch, repeats until empty.
4
+ * Archive the caller's whole category through an app-supplied callable adapter.
5
+ *
6
+ * Like {@link useArchiveNotification}, this performs no client Firestore writes
7
+ * — paging + ownership-checked deletion happen server-side via the callable the
8
+ * app wires into `archiveAllFn`. On success it invalidates the read keys.
9
9
  *
10
10
  * @example
11
11
  * ```tsx
12
+ * const archiveNotification = httpsCallable(functions, 'archiveNotification');
12
13
  * const archiveAll = useArchiveAllNotifications({
13
- * config: TTT_NOTIFICATION_CONFIG,
14
14
  * userId: user.uid,
15
15
  * category: 'user',
16
+ * archiveAllFn: () => archiveNotification({ category: 'user', scope: { kind: 'all' } }),
16
17
  * });
17
18
  *
18
- * archiveAll.mutate({
19
- * archivalInfo: { archivedBy: user.uid, archivedAt: Date.now(), device: 'web' },
20
- * });
19
+ * archiveAll.mutate();
21
20
  * ```
22
21
  */
23
- export function useArchiveAllNotifications({ config, userId, category, invalidateKeys, }) {
24
- const db = useFirestoreDb();
22
+ export function useArchiveAllNotifications({ userId, category, archiveAllFn, invalidateKeys, }) {
25
23
  const queryClient = useQueryClient();
26
- const categoryConfig = config.categories[category];
27
24
  const defaultInvalidateKeys = [
28
25
  ['notifications', 'active', category, userId],
29
26
  ['notifications', 'unread-count', category, userId],
30
27
  ['notifications', 'history', category, userId],
31
28
  ];
32
29
  return useMutation({
33
- mutationFn: async ({ archivalInfo, }) => {
34
- if (!categoryConfig) {
35
- throw new Error(`[notification-core] Unknown category: ${category}`);
36
- }
37
- const activePath = categoryConfig.activePath;
38
- const historyPath = categoryConfig.historyPath(userId);
39
- const isPersonal = categoryConfig.audienceType === 'personal';
40
- let totalArchived = 0;
41
- let hasMore = true;
42
- while (hasMore) {
43
- const constraints = [
44
- ...(isPersonal ? [where('targetUserId', '==', userId)] : []),
45
- orderBy('updatedAt', 'desc'),
46
- limit(BATCH_SIZE),
47
- ];
48
- const q = query(collection(db, activePath), ...constraints);
49
- const snapshot = await getDocs(q);
50
- if (snapshot.empty) {
51
- hasMore = false;
52
- break;
53
- }
54
- const batch = writeBatch(db);
55
- snapshot.docs.forEach((docSnap) => {
56
- const data = docSnap.data();
57
- // Write to history
58
- const historyRef = doc(db, historyPath, docSnap.id);
59
- batch.set(historyRef, {
60
- ...data,
61
- id: docSnap.id,
62
- archival: archivalInfo,
63
- ...(categoryConfig.audienceType === 'shared'
64
- ? { handledBy: archivalInfo.archivedBy }
65
- : {}),
66
- });
67
- // Delete from active
68
- const activeRef = doc(db, activePath, docSnap.id);
69
- batch.delete(activeRef);
70
- });
71
- await batch.commit();
72
- totalArchived += snapshot.docs.length;
73
- if (snapshot.docs.length < BATCH_SIZE) {
74
- hasMore = false;
75
- }
76
- }
77
- return { totalArchived };
78
- },
30
+ mutationFn: async () => archiveAllFn(),
79
31
  onSuccess: () => {
80
32
  const keysToInvalidate = invalidateKeys ?? defaultInvalidateKeys;
81
33
  keysToInvalidate.forEach((key) => {
@@ -1 +1 @@
1
- {"version":3,"file":"useArchiveAllNotifications.js","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveAllNotifications.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useArchiveAllNotifications.js","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveAllNotifications.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGpE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,0BAA0B,CAAC,EACzC,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,cAAc,GACoB;IAClC,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,MAAM,qBAAqB,GAAG;QAC5B,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;QAC7C,CAAC,eAAe,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC;QACnD,CAAC,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC;KAC/C,CAAC;IAEF,OAAO,WAAW,CAAC;QACjB,UAAU,EAAE,KAAK,IAAI,EAAE,CAAC,YAAY,EAAE;QACtC,SAAS,EAAE,GAAG,EAAE;YACd,MAAM,gBAAgB,GAAG,cAAc,IAAI,qBAAqB,CAAC;YACjE,gBAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC/B,WAAW,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtE,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -1,23 +1,24 @@
1
- import type { ArchivalInfo, UseArchiveNotificationOptions } from '../../types.js';
1
+ import type { UseArchiveNotificationOptions } from '../../types.js';
2
2
  /**
3
- * Archive a single notification: read from active write to history → delete from active.
3
+ * Archive a single notification through an app-supplied callable adapter.
4
+ *
5
+ * The notification system is Cloud-Functions-only: clients never write
6
+ * notification docs. This hook performs no Firestore writes — it delegates to
7
+ * `archiveFn` (wired by the app to `httpsCallable(functions, 'archiveNotification')`)
8
+ * and invalidates the read keys on success.
4
9
  *
5
10
  * @example
6
11
  * ```tsx
12
+ * const archiveNotification = httpsCallable(functions, 'archiveNotification');
7
13
  * const archive = useArchiveNotification({
8
- * config: TTT_NOTIFICATION_CONFIG,
9
14
  * userId: user.uid,
10
15
  * category: 'user',
16
+ * archiveFn: (notificationId) =>
17
+ * archiveNotification({ category: 'user', scope: { kind: 'single', notificationId } }),
11
18
  * });
12
19
  *
13
- * archive.mutate({
14
- * notificationId: 'abc123',
15
- * archivalInfo: { archivedBy: user.uid, archivedAt: Date.now(), device: 'web' },
16
- * });
20
+ * archive.mutate('abc123');
17
21
  * ```
18
22
  */
19
- export declare function useArchiveNotification({ config, userId, category, invalidateKeys, }: UseArchiveNotificationOptions): import("@tanstack/react-query").UseMutationResult<void, Error, {
20
- notificationId: string;
21
- archivalInfo: ArchivalInfo;
22
- }, unknown>;
23
+ export declare function useArchiveNotification({ userId, category, archiveFn, invalidateKeys, }: UseArchiveNotificationOptions): import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
23
24
  //# sourceMappingURL=useArchiveNotification.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useArchiveNotification.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveNotification.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,YAAY,EACZ,6BAA6B,EAC9B,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,cAAc,GACf,EAAE,6BAA6B;oBAgBV,MAAM;kBACR,YAAY;YA2C/B"}
1
+ {"version":3,"file":"useArchiveNotification.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveNotification.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,QAAQ,EACR,SAAS,EACT,cAAc,GACf,EAAE,6BAA6B,sFAkB/B"}
@@ -1,62 +1,35 @@
1
1
  'use client';
2
2
  import { useMutation, useQueryClient } from '@tanstack/react-query';
3
- import { doc, getDoc, setDoc, deleteDoc } from 'firebase/firestore';
4
- import { useFirestoreDb } from '@ttt-productions/query-core/react';
5
3
  /**
6
- * Archive a single notification: read from active write to history → delete from active.
4
+ * Archive a single notification through an app-supplied callable adapter.
5
+ *
6
+ * The notification system is Cloud-Functions-only: clients never write
7
+ * notification docs. This hook performs no Firestore writes — it delegates to
8
+ * `archiveFn` (wired by the app to `httpsCallable(functions, 'archiveNotification')`)
9
+ * and invalidates the read keys on success.
7
10
  *
8
11
  * @example
9
12
  * ```tsx
13
+ * const archiveNotification = httpsCallable(functions, 'archiveNotification');
10
14
  * const archive = useArchiveNotification({
11
- * config: TTT_NOTIFICATION_CONFIG,
12
15
  * userId: user.uid,
13
16
  * category: 'user',
17
+ * archiveFn: (notificationId) =>
18
+ * archiveNotification({ category: 'user', scope: { kind: 'single', notificationId } }),
14
19
  * });
15
20
  *
16
- * archive.mutate({
17
- * notificationId: 'abc123',
18
- * archivalInfo: { archivedBy: user.uid, archivedAt: Date.now(), device: 'web' },
19
- * });
21
+ * archive.mutate('abc123');
20
22
  * ```
21
23
  */
22
- export function useArchiveNotification({ config, userId, category, invalidateKeys, }) {
23
- const db = useFirestoreDb();
24
+ export function useArchiveNotification({ userId, category, archiveFn, invalidateKeys, }) {
24
25
  const queryClient = useQueryClient();
25
- const categoryConfig = config.categories[category];
26
26
  const defaultInvalidateKeys = [
27
27
  ['notifications', 'active', category, userId],
28
28
  ['notifications', 'unread-count', category, userId],
29
29
  ['notifications', 'history', category, userId],
30
30
  ];
31
31
  return useMutation({
32
- mutationFn: async ({ notificationId, archivalInfo, }) => {
33
- if (!categoryConfig) {
34
- throw new Error(`[notification-core] Unknown category: ${category}`);
35
- }
36
- const activePath = categoryConfig.activePath;
37
- const historyPath = categoryConfig.historyPath(userId);
38
- // Read active doc
39
- const activeRef = doc(db, activePath, notificationId);
40
- const activeSnap = await getDoc(activeRef);
41
- if (!activeSnap.exists()) {
42
- // Already archived or deleted — no-op
43
- return;
44
- }
45
- const activeData = { id: activeSnap.id, ...activeSnap.data() };
46
- // Build history doc
47
- const historyDoc = {
48
- ...activeData,
49
- archival: archivalInfo,
50
- ...(categoryConfig.audienceType === 'shared'
51
- ? { handledBy: archivalInfo.archivedBy }
52
- : {}),
53
- };
54
- // Write to history
55
- const historyRef = doc(db, historyPath, notificationId);
56
- await setDoc(historyRef, historyDoc);
57
- // Delete from active
58
- await deleteDoc(activeRef);
59
- },
32
+ mutationFn: async (notificationId) => archiveFn(notificationId),
60
33
  onSuccess: () => {
61
34
  const keysToInvalidate = invalidateKeys ?? defaultInvalidateKeys;
62
35
  keysToInvalidate.forEach((key) => {
@@ -1 +1 @@
1
- {"version":3,"file":"useArchiveNotification.js","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveNotification.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAQnE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,cAAc,GACgB;IAC9B,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAC5B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IACrC,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAEnD,MAAM,qBAAqB,GAAG;QAC5B,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;QAC7C,CAAC,eAAe,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC;QACnD,CAAC,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC;KAC/C,CAAC;IAEF,OAAO,WAAW,CAAC;QACjB,UAAU,EAAE,KAAK,EAAE,EACjB,cAAc,EACd,YAAY,GAIb,EAAE,EAAE;YACH,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CAAC,yCAAyC,QAAQ,EAAE,CAAC,CAAC;YACvE,CAAC;YAED,MAAM,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC;YAC7C,MAAM,WAAW,GAAG,cAAc,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAEvD,kBAAkB;YAClB,MAAM,SAAS,GAAG,GAAG,CAAC,EAAE,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC;YACtD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;YAE3C,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;gBACzB,sCAAsC;gBACtC,OAAO;YACT,CAAC;YAED,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,GAAG,UAAU,CAAC,IAAI,EAAE,EAAqB,CAAC;YAElF,oBAAoB;YACpB,MAAM,UAAU,GAAuC;gBACrD,GAAG,UAAU;gBACb,QAAQ,EAAE,YAAY;gBACtB,GAAG,CAAC,cAAc,CAAC,YAAY,KAAK,QAAQ;oBAC1C,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,UAAU,EAAE;oBACxC,CAAC,CAAC,EAAE,CAAC;aACR,CAAC;YAEF,mBAAmB;YACnB,MAAM,UAAU,GAAG,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;YACxD,MAAM,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YAErC,qBAAqB;YACrB,MAAM,SAAS,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;QACD,SAAS,EAAE,GAAG,EAAE;YACd,MAAM,gBAAgB,GAAG,cAAc,IAAI,qBAAqB,CAAC;YACjE,gBAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC/B,WAAW,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtE,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"useArchiveNotification.js","sourceRoot":"","sources":["../../../src/react/hooks/useArchiveNotification.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGpE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,QAAQ,EACR,SAAS,EACT,cAAc,GACgB;IAC9B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,MAAM,qBAAqB,GAAG;QAC5B,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;QAC7C,CAAC,eAAe,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC;QACnD,CAAC,eAAe,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC;KAC/C,CAAC;IAEF,OAAO,WAAW,CAAC;QACjB,UAAU,EAAE,KAAK,EAAE,cAAsB,EAAE,EAAE,CAAC,SAAS,CAAC,cAAc,CAAC;QACvE,SAAS,EAAE,GAAG,EAAE;YACd,MAAM,gBAAgB,GAAG,cAAc,IAAI,qBAAqB,CAAC;YACjE,gBAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC/B,WAAW,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtE,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}