@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 +156 -0
- package/dist/hooks/index.d.ts +9 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useMarkAllAsRead.d.ts +30 -0
- package/dist/hooks/useMarkAllAsRead.d.ts.map +1 -0
- package/dist/hooks/useMarkAllAsRead.js +44 -0
- package/dist/hooks/useMarkAllAsRead.js.map +1 -0
- package/dist/hooks/useMarkAsRead.d.ts +30 -0
- package/dist/hooks/useMarkAsRead.d.ts.map +1 -0
- package/dist/hooks/useMarkAsRead.js +40 -0
- package/dist/hooks/useMarkAsRead.js.map +1 -0
- package/dist/hooks/useNotifications.d.ts +25 -0
- package/dist/hooks/useNotifications.d.ts.map +1 -0
- package/dist/hooks/useNotifications.js +31 -0
- package/dist/hooks/useNotifications.js.map +1 -0
- package/dist/hooks/useUnreadCount.d.ts +24 -0
- package/dist/hooks/useUnreadCount.d.ts.map +1 -0
- package/dist/hooks/useUnreadCount.js +31 -0
- package/dist/hooks/useUnreadCount.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
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 @@
|
|
|
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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|