@ttt-productions/notification-core 0.2.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 ADDED
@@ -0,0 +1,156 @@
1
+ # @ttt-productions/notification-core
2
+
3
+ Shared notification system for TTT Productions apps with Firestore integration.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @ttt-productions/notification-core
9
+ ```
10
+
11
+ ## Features
12
+
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
18
+
19
+ ## Usage
20
+
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
+ ```
45
+
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';
65
+
66
+ function NotificationItem({ notification }) {
67
+ const markAsRead = useMarkAsRead({ userId: currentUser.uid });
68
+
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
79
+
80
+ ```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
+ ```
111
+
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
+ }
125
+ ```
126
+
127
+ ## Backend Integration
128
+
129
+ Apps should implement a callable Cloud Function to create notifications:
130
+
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
+ });
156
+ ```
@@ -0,0 +1,9 @@
1
+ export { useNotifications } from './useNotifications.js';
2
+ export type { UseNotificationsOptions } from './useNotifications.js';
3
+ export { useUnreadCount } from './useUnreadCount.js';
4
+ export type { UseUnreadCountOptions } from './useUnreadCount.js';
5
+ export { useMarkAsRead } from './useMarkAsRead.js';
6
+ export type { UseMarkAsReadOptions } from './useMarkAsRead.js';
7
+ export { useMarkAllAsRead } from './useMarkAllAsRead.js';
8
+ export type { UseMarkAllAsReadOptions } from './useMarkAllAsRead.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { useNotifications } from './useNotifications.js';
2
+ export { useUnreadCount } from './useUnreadCount.js';
3
+ export { useMarkAsRead } from './useMarkAsRead.js';
4
+ export { useMarkAllAsRead } from './useMarkAllAsRead.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,30 @@
1
+ export interface UseMarkAllAsReadOptions {
2
+ /** User ID */
3
+ userId: string;
4
+ /** Query keys to invalidate after marking all as read */
5
+ invalidateKeys?: readonly unknown[][];
6
+ }
7
+ /**
8
+ * Mark all unread notifications as read in a batch operation.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const markAllAsRead = useMarkAllAsRead({ userId: currentUser.uid });
13
+ *
14
+ * const handleMarkAllRead = (unreadNotifications: Notification[]) => {
15
+ * markAllAsRead.mutate({ notifications: unreadNotifications });
16
+ * };
17
+ * ```
18
+ */
19
+ export declare function useMarkAllAsRead({ userId, invalidateKeys, }: UseMarkAllAsReadOptions): {
20
+ mutate: ({ notificationIds }: {
21
+ notificationIds: string[];
22
+ }) => void;
23
+ mutateAsync: ({ notificationIds }: {
24
+ notificationIds: string[];
25
+ }) => Promise<any>;
26
+ isPending: any;
27
+ isError: any;
28
+ error: any;
29
+ };
30
+ //# sourceMappingURL=useMarkAllAsRead.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMarkAllAsRead.d.ts","sourceRoot":"","sources":["../../src/hooks/useMarkAllAsRead.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,uBAAuB;IACtC,cAAc;IACd,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,cAAc,CAAC,EAAE,SAAS,OAAO,EAAE,EAAE,CAAC;CACvC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,cAAc,GACf,EAAE,uBAAuB;kCASQ;QAAE,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE;uCASlB;QAAE,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE;;;;EAazE"}
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+ import { useFirestoreBatch } from '@ttt-productions/query-core';
3
+ /**
4
+ * Mark all unread notifications as read in a batch operation.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * const markAllAsRead = useMarkAllAsRead({ userId: currentUser.uid });
9
+ *
10
+ * const handleMarkAllRead = (unreadNotifications: Notification[]) => {
11
+ * markAllAsRead.mutate({ notifications: unreadNotifications });
12
+ * };
13
+ * ```
14
+ */
15
+ export function useMarkAllAsRead({ userId, invalidateKeys, }) {
16
+ const batchMutation = useFirestoreBatch({
17
+ invalidateKeys: invalidateKeys ?? [
18
+ ['notifications', userId],
19
+ ['notifications-unread-count', userId],
20
+ ],
21
+ });
22
+ return {
23
+ mutate: ({ notificationIds }) => {
24
+ const operations = notificationIds.map((id) => ({
25
+ type: 'update',
26
+ docPath: `userData/${userId}/metadata/notifications/${id}`,
27
+ data: { isRead: true },
28
+ }));
29
+ batchMutation.mutate({ operations });
30
+ },
31
+ mutateAsync: async ({ notificationIds }) => {
32
+ const operations = notificationIds.map((id) => ({
33
+ type: 'update',
34
+ docPath: `userData/${userId}/metadata/notifications/${id}`,
35
+ data: { isRead: true },
36
+ }));
37
+ return batchMutation.mutateAsync({ operations });
38
+ },
39
+ isPending: batchMutation.isPending,
40
+ isError: batchMutation.isError,
41
+ error: batchMutation.error,
42
+ };
43
+ }
44
+ //# sourceMappingURL=useMarkAllAsRead.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMarkAllAsRead.js","sourceRoot":"","sources":["../../src/hooks/useMarkAllAsRead.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAUhE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,MAAM,EACN,cAAc,GACU;IACxB,MAAM,aAAa,GAAG,iBAAiB,CAAC;QACtC,cAAc,EAAE,cAAc,IAAI;YAChC,CAAC,eAAe,EAAE,MAAM,CAAC;YACzB,CAAC,4BAA4B,EAAE,MAAM,CAAC;SACvC;KACF,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,CAAC,EAAE,eAAe,EAAiC,EAAE,EAAE;YAC7D,MAAM,UAAU,GAAwB,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBACnE,IAAI,EAAE,QAAiB;gBACvB,OAAO,EAAE,YAAY,MAAM,2BAA2B,EAAE,EAAE;gBAC1D,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;aACvB,CAAC,CAAC,CAAC;YAEJ,aAAa,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;QACvC,CAAC;QACD,WAAW,EAAE,KAAK,EAAE,EAAE,eAAe,EAAiC,EAAE,EAAE;YACxE,MAAM,UAAU,GAAwB,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBACnE,IAAI,EAAE,QAAiB;gBACvB,OAAO,EAAE,YAAY,MAAM,2BAA2B,EAAE,EAAE;gBAC1D,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;aACvB,CAAC,CAAC,CAAC;YAEJ,OAAO,aAAa,CAAC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;QACnD,CAAC;QACD,SAAS,EAAE,aAAa,CAAC,SAAS;QAClC,OAAO,EAAE,aAAa,CAAC,OAAO;QAC9B,KAAK,EAAE,aAAa,CAAC,KAAK;KAC3B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,30 @@
1
+ export interface UseMarkAsReadOptions {
2
+ /** User ID */
3
+ userId: string;
4
+ /** Query keys to invalidate after marking as read */
5
+ invalidateKeys?: readonly unknown[][];
6
+ }
7
+ /**
8
+ * Mark a single notification as read.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * const markAsRead = useMarkAsRead({ userId: currentUser.uid });
13
+ *
14
+ * const handleNotificationClick = (notificationId: string) => {
15
+ * markAsRead.mutate({ notificationId });
16
+ * };
17
+ * ```
18
+ */
19
+ export declare function useMarkAsRead({ userId, invalidateKeys, }: UseMarkAsReadOptions): {
20
+ mutate: ({ notificationId }: {
21
+ notificationId: string;
22
+ }) => void;
23
+ mutateAsync: ({ notificationId }: {
24
+ notificationId: string;
25
+ }) => Promise<any>;
26
+ isPending: any;
27
+ isError: any;
28
+ error: any;
29
+ };
30
+ //# sourceMappingURL=useMarkAsRead.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMarkAsRead.d.ts","sourceRoot":"","sources":["../../src/hooks/useMarkAsRead.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,oBAAoB;IACnC,cAAc;IACd,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,cAAc,CAAC,EAAE,SAAS,OAAO,EAAE,EAAE,CAAC;CACvC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,cAAc,GACf,EAAE,oBAAoB;iCASU;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE;sCAMf;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE;;;;EAUrE"}
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+ import { useFirestoreUpdate } from '@ttt-productions/query-core';
3
+ /**
4
+ * Mark a single notification as read.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * const markAsRead = useMarkAsRead({ userId: currentUser.uid });
9
+ *
10
+ * const handleNotificationClick = (notificationId: string) => {
11
+ * markAsRead.mutate({ notificationId });
12
+ * };
13
+ * ```
14
+ */
15
+ export function useMarkAsRead({ userId, invalidateKeys, }) {
16
+ const updateMutation = useFirestoreUpdate({
17
+ invalidateKeys: invalidateKeys ?? [
18
+ ['notifications', userId],
19
+ ['notifications-unread-count', userId],
20
+ ],
21
+ });
22
+ return {
23
+ mutate: ({ notificationId }) => {
24
+ updateMutation.mutate({
25
+ docPath: `userData/${userId}/metadata/notifications/${notificationId}`,
26
+ data: { isRead: true },
27
+ });
28
+ },
29
+ mutateAsync: async ({ notificationId }) => {
30
+ return updateMutation.mutateAsync({
31
+ docPath: `userData/${userId}/metadata/notifications/${notificationId}`,
32
+ data: { isRead: true },
33
+ });
34
+ },
35
+ isPending: updateMutation.isPending,
36
+ isError: updateMutation.isError,
37
+ error: updateMutation.error,
38
+ };
39
+ }
40
+ //# sourceMappingURL=useMarkAsRead.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMarkAsRead.js","sourceRoot":"","sources":["../../src/hooks/useMarkAsRead.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAUjE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,MAAM,EACN,cAAc,GACO;IACrB,MAAM,cAAc,GAAG,kBAAkB,CAAe;QACtD,cAAc,EAAE,cAAc,IAAI;YAChC,CAAC,eAAe,EAAE,MAAM,CAAC;YACzB,CAAC,4BAA4B,EAAE,MAAM,CAAC;SACvC;KACF,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,CAAC,EAAE,cAAc,EAA8B,EAAE,EAAE;YACzD,cAAc,CAAC,MAAM,CAAC;gBACpB,OAAO,EAAE,YAAY,MAAM,2BAA2B,cAAc,EAAE;gBACtE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;aACvB,CAAC,CAAC;QACL,CAAC;QACD,WAAW,EAAE,KAAK,EAAE,EAAE,cAAc,EAA8B,EAAE,EAAE;YACpE,OAAO,cAAc,CAAC,WAAW,CAAC;gBAChC,OAAO,EAAE,YAAY,MAAM,2BAA2B,cAAc,EAAE;gBACtE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;aACvB,CAAC,CAAC;QACL,CAAC;QACD,SAAS,EAAE,cAAc,CAAC,SAAS;QACnC,OAAO,EAAE,cAAc,CAAC,OAAO;QAC/B,KAAK,EAAE,cAAc,CAAC,KAAK;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ export interface UseNotificationsOptions {
2
+ /** User ID to fetch notifications for */
3
+ userId: string;
4
+ /** Enable/disable the query */
5
+ enabled?: boolean;
6
+ /** Enable realtime updates (default: true) */
7
+ subscribe?: boolean;
8
+ /** Maximum number of notifications to fetch (default: 50) */
9
+ limit?: number;
10
+ /** Only fetch unread notifications */
11
+ unreadOnly?: boolean;
12
+ }
13
+ /**
14
+ * Fetch user notifications with optional realtime updates.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const { data: notifications, isLoading } = useNotifications({
19
+ * userId: currentUser.uid,
20
+ * subscribe: true,
21
+ * });
22
+ * ```
23
+ */
24
+ export declare function useNotifications({ userId, enabled, subscribe, limit: maxResults, unreadOnly, }: UseNotificationsOptions): any;
25
+ //# sourceMappingURL=useNotifications.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNotifications.d.ts","sourceRoot":"","sources":["../../src/hooks/useNotifications.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,uBAAuB;IACtC,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,OAAc,EACd,SAAgB,EAChB,KAAK,EAAE,UAAe,EACtB,UAAkB,GACnB,EAAE,uBAAuB,OAiBzB"}
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+ import { useFirestoreCollection } from '@ttt-productions/query-core';
3
+ import { orderBy, limit, where } from 'firebase/firestore';
4
+ /**
5
+ * Fetch user notifications with optional realtime updates.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const { data: notifications, isLoading } = useNotifications({
10
+ * userId: currentUser.uid,
11
+ * subscribe: true,
12
+ * });
13
+ * ```
14
+ */
15
+ export function useNotifications({ userId, enabled = true, subscribe = true, limit: maxResults = 50, unreadOnly = false, }) {
16
+ const constraints = [
17
+ orderBy('createdAt', 'desc'),
18
+ limit(maxResults),
19
+ ];
20
+ if (unreadOnly) {
21
+ constraints.unshift(where('isRead', '==', false));
22
+ }
23
+ return useFirestoreCollection({
24
+ collectionPath: `userData/${userId}/metadata/notifications`,
25
+ queryKey: ['notifications', userId, { unreadOnly, limit: maxResults }],
26
+ constraints,
27
+ enabled: enabled && !!userId,
28
+ subscribe,
29
+ });
30
+ }
31
+ //# sourceMappingURL=useNotifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNotifications.js","sourceRoot":"","sources":["../../src/hooks/useNotifications.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAwB,MAAM,oBAAoB,CAAC;AAgBjF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,MAAM,EACN,OAAO,GAAG,IAAI,EACd,SAAS,GAAG,IAAI,EAChB,KAAK,EAAE,UAAU,GAAG,EAAE,EACtB,UAAU,GAAG,KAAK,GACM;IACxB,MAAM,WAAW,GAAsB;QACrC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC;QAC5B,KAAK,CAAC,UAAU,CAAC;KAClB,CAAC;IAEF,IAAI,UAAU,EAAE,CAAC;QACf,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,sBAAsB,CAAe;QAC1C,cAAc,EAAE,YAAY,MAAM,yBAAyB;QAC3D,QAAQ,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;QACtE,WAAW;QACX,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;QAC5B,SAAS;KACV,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,24 @@
1
+ export interface UseUnreadCountOptions {
2
+ /** User ID to fetch unread count for */
3
+ userId: string;
4
+ /** Enable/disable the query */
5
+ enabled?: boolean;
6
+ /** Enable realtime updates (default: true) */
7
+ subscribe?: boolean;
8
+ }
9
+ /**
10
+ * Get the count of unread notifications for a user.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * const { data: unreadCount } = useUnreadCount({
15
+ * userId: currentUser.uid,
16
+ * subscribe: true,
17
+ * });
18
+ * ```
19
+ */
20
+ export declare function useUnreadCount({ userId, enabled, subscribe, }: UseUnreadCountOptions): {
21
+ data: any;
22
+ notifications: any;
23
+ };
24
+ //# sourceMappingURL=useUnreadCount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useUnreadCount.d.ts","sourceRoot":"","sources":["../../src/hooks/useUnreadCount.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,qBAAqB;IACpC,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAc,EACd,SAAgB,GACjB,EAAE,qBAAqB;;;EAiBvB"}
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+ import { useFirestoreCollection } from '@ttt-productions/query-core';
3
+ import { where } from 'firebase/firestore';
4
+ /**
5
+ * Get the count of unread notifications for a user.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const { data: unreadCount } = useUnreadCount({
10
+ * userId: currentUser.uid,
11
+ * subscribe: true,
12
+ * });
13
+ * ```
14
+ */
15
+ export function useUnreadCount({ userId, enabled = true, subscribe = true, }) {
16
+ const constraints = [
17
+ where('isRead', '==', false),
18
+ ];
19
+ const { data: notifications } = useFirestoreCollection({
20
+ collectionPath: `userData/${userId}/metadata/notifications`,
21
+ queryKey: ['notifications-unread-count', userId],
22
+ constraints,
23
+ enabled: enabled && !!userId,
24
+ subscribe,
25
+ });
26
+ return {
27
+ data: notifications?.length ?? 0,
28
+ notifications,
29
+ };
30
+ }
31
+ //# sourceMappingURL=useUnreadCount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useUnreadCount.js","sourceRoot":"","sources":["../../src/hooks/useUnreadCount.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,KAAK,EAAwB,MAAM,oBAAoB,CAAC;AAYjE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,MAAM,EACN,OAAO,GAAG,IAAI,EACd,SAAS,GAAG,IAAI,GACM;IACtB,MAAM,WAAW,GAAsB;QACrC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC;KAC7B,CAAC;IAEF,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,sBAAsB,CAAe;QACnE,cAAc,EAAE,YAAY,MAAM,yBAAyB;QAC3D,QAAQ,EAAE,CAAC,4BAA4B,EAAE,MAAM,CAAC;QAChD,WAAW;QACX,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;QAC5B,SAAS;KACV,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;QAChC,aAAa;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export type { Notification, NotificationInput, NotificationType, MarkAsReadOptions, DeleteNotificationOptions, NavigationHandler, NotificationTypeConfig, NotificationConfig, } from './types.js';
2
+ export { useNotifications, useUnreadCount, useMarkAsRead, useMarkAllAsRead, } from './hooks/index.js';
3
+ export type { UseNotificationsOptions, UseUnreadCountOptions, UseMarkAsReadOptions, UseMarkAllAsReadOptions, } from './hooks/index.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EACzB,iBAAiB,EACjB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // Hooks
2
+ export { useNotifications, useUnreadCount, useMarkAsRead, useMarkAllAsRead, } from './hooks/index.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,QAAQ;AACR,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,73 @@
1
+ import type { Timestamp } from 'firebase/firestore';
2
+ /**
3
+ * Base notification type that can be extended per app.
4
+ */
5
+ export type NotificationType = string;
6
+ /**
7
+ * Core notification document structure stored in Firestore.
8
+ */
9
+ export interface Notification {
10
+ /** Unique notification ID (usually Firestore doc ID) */
11
+ id: string;
12
+ /** Type of notification - app-specific (e.g., 'trophy_received', 'game_scheduled') */
13
+ type: NotificationType;
14
+ /** Notification title */
15
+ title: string;
16
+ /** Notification message/body */
17
+ message: string;
18
+ /** Target path to navigate to when clicked (app-specific route) */
19
+ targetPath: string;
20
+ /** Optional URL/route parameters for navigation */
21
+ targetParams?: Record<string, any>;
22
+ /** Whether the notification has been read */
23
+ isRead: boolean;
24
+ /** When the notification was created */
25
+ createdAt: Timestamp;
26
+ /** Optional type-specific metadata */
27
+ metadata?: Record<string, any>;
28
+ }
29
+ /**
30
+ * Notification data without the ID (for creating new notifications).
31
+ */
32
+ export type NotificationInput = Omit<Notification, 'id'>;
33
+ /**
34
+ * Options for marking notifications as read.
35
+ */
36
+ export interface MarkAsReadOptions {
37
+ /** Single notification ID to mark as read */
38
+ notificationId?: string;
39
+ /** Mark all notifications as read */
40
+ all?: boolean;
41
+ }
42
+ /**
43
+ * Options for deleting notifications.
44
+ */
45
+ export interface DeleteNotificationOptions {
46
+ /** Single notification ID to delete */
47
+ notificationId?: string;
48
+ /** Delete all read notifications */
49
+ allRead?: boolean;
50
+ }
51
+ /**
52
+ * Navigation handler function signature that apps must provide.
53
+ */
54
+ export type NavigationHandler = (path: string, params?: Record<string, any>) => void;
55
+ /**
56
+ * Notification configuration per type (app-specific).
57
+ */
58
+ export interface NotificationTypeConfig {
59
+ /** Icon or emoji to display */
60
+ icon?: string;
61
+ /** Color theme for the notification */
62
+ color?: string;
63
+ /** Custom navigation handler for this type */
64
+ navigateTo?: (params?: Record<string, any>) => string;
65
+ }
66
+ /**
67
+ * App-specific notification configuration.
68
+ */
69
+ export interface NotificationConfig {
70
+ /** Map of notification types to their configurations */
71
+ types: Record<NotificationType, NotificationTypeConfig>;
72
+ }
73
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,sFAAsF;IACtF,IAAI,EAAE,gBAAgB,CAAC;IACvB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,6CAA6C;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,wCAAwC;IACxC,SAAS,EAAE,SAAS,CAAC;IACrB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qCAAqC;IACrC,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;AAErF;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,UAAU,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,MAAM,CAAC;CACvD;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC,gBAAgB,EAAE,sBAAsB,CAAC,CAAC;CACzD"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@ttt-productions/notification-core",
3
+ "version": "0.2.1",
4
+ "description": "Shared notification system for TTT Productions apps — active/history two-tier architecture with dedup, batch processing, and themed UI components",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/ttt-productions/ttt-packages.git",
8
+ "directory": "packages/notification-core"
9
+ },
10
+ "type": "module",
11
+ "main": "dist/index.js",
12
+ "module": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "sideEffects": false,
15
+ "files": [
16
+ "dist",
17
+ "src/styles"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./server": {
25
+ "types": "./dist/server/index.d.ts",
26
+ "default": "./dist/server/index.js"
27
+ },
28
+ "./styles": "./src/styles/notifications.css"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc",
32
+ "clean": "rm -rf dist *.tsbuildinfo",
33
+ "typecheck": "tsc --noEmit",
34
+ "prepublishOnly": "npm run clean && npm run build"
35
+ },
36
+ "peerDependencies": {
37
+ "@tanstack/react-query": ">=5.0.0",
38
+ "@ttt-productions/query-core": ">=0.3.0",
39
+ "@ttt-productions/ui-core": ">=0.2.0",
40
+ "firebase": ">=10.0.0",
41
+ "react": ">=18.0.0",
42
+ "react-dom": ">=18.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@tanstack/react-query": "^5.0.0",
46
+ "@ttt-productions/query-core": "^0.3.7",
47
+ "@ttt-productions/ui-core": "^0.2.22",
48
+ "@types/react": "^19.0.0",
49
+ "@types/react-dom": "^19.0.0",
50
+ "firebase": "^11.0.0",
51
+ "react": "^19.2.0",
52
+ "react-dom": "^19.2.0",
53
+ "typescript": "^5.8.3"
54
+ },
55
+ "author": "DJ (TTT Productions)",
56
+ "license": "MIT"
57
+ }