@myrjfa/state 1.1.1 → 1.1.2

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 (73) hide show
  1. package/dist/index.d.ts +18 -18
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +18 -18
  4. package/dist/lib/actions/actions.d.ts +189 -170
  5. package/dist/lib/actions/actions.d.ts.map +1 -1
  6. package/dist/lib/actions/actions.js +347 -307
  7. package/dist/lib/actions/auth.d.ts +12 -2
  8. package/dist/lib/actions/auth.d.ts.map +1 -1
  9. package/dist/lib/actions/fetcher.d.ts +0 -3
  10. package/dist/lib/actions/fetcher.d.ts.map +1 -1
  11. package/dist/lib/actions/fetcher.js +122 -84
  12. package/dist/lib/actions/{severActions.d.ts → serverActions.d.ts} +1 -1
  13. package/dist/lib/actions/serverActions.d.ts.map +1 -0
  14. package/dist/lib/actions/{severActions.js → serverActions.js} +3 -0
  15. package/dist/lib/authSessionManager.d.ts.map +1 -1
  16. package/dist/lib/authSessionManager.js +41 -34
  17. package/dist/lib/context/ChatContext.d.ts +1 -1
  18. package/dist/lib/context/ChatContext.d.ts.map +1 -1
  19. package/dist/lib/context/ChatContext.js +338 -338
  20. package/dist/lib/models/blog.d.ts +3 -2
  21. package/dist/lib/models/blog.d.ts.map +1 -1
  22. package/dist/lib/models/{notfications.d.ts → notifications.d.ts} +94 -94
  23. package/dist/lib/models/notifications.d.ts.map +1 -0
  24. package/dist/lib/models/opportunities/freelance.d.ts +48 -20
  25. package/dist/lib/models/opportunities/freelance.d.ts.map +1 -1
  26. package/dist/lib/models/opportunities/internship.d.ts +48 -20
  27. package/dist/lib/models/opportunities/internship.d.ts.map +1 -1
  28. package/dist/lib/models/opportunities/job.d.ts +58 -30
  29. package/dist/lib/models/opportunities/job.d.ts.map +1 -1
  30. package/dist/lib/models/opportunities/opportunity.d.ts +61 -33
  31. package/dist/lib/models/opportunities/opportunity.d.ts.map +1 -1
  32. package/dist/lib/models/opportunities/opportunity.js +1 -0
  33. package/dist/lib/models/opportunities/volunteerJob.d.ts +48 -20
  34. package/dist/lib/models/opportunities/volunteerJob.d.ts.map +1 -1
  35. package/dist/lib/models/portfolio.d.ts +42 -42
  36. package/dist/lib/models/props.d.ts +21 -4
  37. package/dist/lib/models/props.d.ts.map +1 -1
  38. package/dist/lib/models/tile.d.ts +28 -28
  39. package/dist/lib/models/user.d.ts +19 -4
  40. package/dist/lib/models/user.d.ts.map +1 -1
  41. package/dist/lib/models/user.js +5 -0
  42. package/dist/lib/userAtom.d.ts +218 -198
  43. package/dist/lib/userAtom.d.ts.map +1 -1
  44. package/dist/lib/userAtom.js +129 -127
  45. package/dist/lib/utils.js +4 -4
  46. package/package.json +3 -1
  47. package/dist/lib/actions/property.d.ts +0 -77
  48. package/dist/lib/actions/property.d.ts.map +0 -1
  49. package/dist/lib/actions/property.js +0 -133
  50. package/dist/lib/actions/severActions.d.ts.map +0 -1
  51. package/dist/lib/actions.d.ts +0 -141
  52. package/dist/lib/actions.d.ts.map +0 -1
  53. package/dist/lib/actions.js +0 -307
  54. package/dist/lib/auth.d.ts +0 -150
  55. package/dist/lib/auth.d.ts.map +0 -1
  56. package/dist/lib/auth.js +0 -125
  57. package/dist/lib/fetcher.d.ts +0 -9
  58. package/dist/lib/fetcher.d.ts.map +0 -1
  59. package/dist/lib/fetcher.js +0 -84
  60. package/dist/lib/models/notfications.d.ts.map +0 -1
  61. package/dist/lib/models/property.d.ts +0 -79
  62. package/dist/lib/models/property.d.ts.map +0 -1
  63. package/dist/lib/models/property.js +0 -134
  64. package/dist/lib/models/volunteerJob.d.ts +0 -398
  65. package/dist/lib/models/volunteerJob.d.ts.map +0 -1
  66. package/dist/lib/models/volunteerJob.js +0 -152
  67. package/dist/lib/severActions.d.ts +0 -3
  68. package/dist/lib/severActions.d.ts.map +0 -1
  69. package/dist/lib/severActions.js +0 -19
  70. package/dist/lib/socket.d.ts +0 -7
  71. package/dist/lib/socket.d.ts.map +0 -1
  72. package/dist/lib/socket.js +0 -22
  73. /package/dist/lib/models/{notfications.js → notifications.js} +0 -0
@@ -1,338 +1,338 @@
1
- "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
4
- import { getSocket } from "../actions/socket";
5
- import { chatApi } from "../actions/chat";
6
- import { useAtomValue } from "jotai";
7
- import { userAtom } from "../userAtom";
8
- const ChatContext = createContext({
9
- socket: null,
10
- isConnected: false,
11
- conversations: [],
12
- activeConversation: null,
13
- setActiveConversation: () => { },
14
- messages: [],
15
- setMessages: () => { },
16
- typingUsers: new Map(),
17
- onlineUsers: new Set(),
18
- refreshConversations: async () => { },
19
- sendMessage: async () => { },
20
- createPoll: () => { },
21
- votePoll: () => { },
22
- editMessage: () => { },
23
- toggleMessageReaction: () => { },
24
- deleteMessage: () => { },
25
- markRead: () => { },
26
- unreadNotificationCount: 0,
27
- notifications: [],
28
- clearNotifications: () => { },
29
- });
30
- export function useChatContext() {
31
- return useContext(ChatContext);
32
- }
33
- export function ChatProvider({ children }) {
34
- const user = useAtomValue(userAtom);
35
- const [isConnected, setIsConnected] = useState(false);
36
- const [conversations, setConversations] = useState([]);
37
- const [activeConversation, _setActiveConversation] = useState(null);
38
- const activeConversationRef = useRef(null);
39
- const [messages, setMessages] = useState([]);
40
- const [typingUsers, setTypingUsers] = useState(new Map());
41
- const [onlineUsers, setOnlineUsers] = useState(new Set());
42
- const [unreadNotificationCount, setUnreadNotificationCount] = useState(0);
43
- const [notifications, setNotifications] = useState([]);
44
- const socketRef = useRef(null);
45
- const setActiveConversation = useCallback((conv) => {
46
- _setActiveConversation(conv);
47
- activeConversationRef.current = conv;
48
- }, []);
49
- useEffect(() => {
50
- activeConversationRef.current = activeConversation;
51
- }, [activeConversation]);
52
- const refreshConversations = useCallback(async () => {
53
- try {
54
- const data = await chatApi.getConversations();
55
- setConversations(data);
56
- }
57
- catch (err) {
58
- console.error("Failed to fetch conversations:", err);
59
- }
60
- }, []);
61
- const sendMessage = useCallback(async (conversationId, text, files, replyTo) => {
62
- let actualId = conversationId;
63
- // If it's a draft conversation, create it first
64
- if (conversationId.startsWith("draft_")) {
65
- const otherUserId = conversationId.replace("draft_", "");
66
- try {
67
- const res = await chatApi.createConversation({
68
- type: "individual",
69
- otherUserId
70
- });
71
- if (res._id) {
72
- actualId = res._id;
73
- // Update active conversation in state to point to the real one
74
- setActiveConversation(res);
75
- // Refresh to include in list
76
- await refreshConversations();
77
- }
78
- else {
79
- console.error("Failed to create conversation during lazy send");
80
- return;
81
- }
82
- }
83
- catch (err) {
84
- console.error("Failed to create conversation during lazy send:", err);
85
- return;
86
- }
87
- }
88
- if (!socketRef.current)
89
- return;
90
- let media = [];
91
- if (files && files.length > 0) {
92
- // Upload files first
93
- const formData = new FormData();
94
- files.forEach(f => formData.append("chatFiles", f));
95
- try {
96
- // We need a dedicated upload endpoint.
97
- // For now, let's assume /api/v1/chat/upload
98
- const response = await chatApi.uploadFiles(formData);
99
- if (response.error) {
100
- console.error("File upload failed:", response.error);
101
- return;
102
- }
103
- media = response.data.files.map((f) => ({
104
- type: f.mimetype.startsWith("audio") ? "audio" : (f.mimetype.startsWith("image") ? "image" : "file"),
105
- url: f.location,
106
- name: f.originalname
107
- }));
108
- }
109
- catch (err) {
110
- console.error("File upload failed:", err);
111
- }
112
- }
113
- socketRef.current.emit("message:send", {
114
- conversationId: actualId,
115
- content: { text, media },
116
- type: media.length > 0 ? "media" : "text",
117
- replyTo,
118
- });
119
- }, [refreshConversations, setActiveConversation]);
120
- const editMessage = useCallback((messageId, text) => {
121
- if (!socketRef.current)
122
- return;
123
- socketRef.current.emit("message:edit", { messageId, text });
124
- }, []);
125
- const toggleMessageReaction = useCallback((messageId, reaction) => {
126
- if (!socketRef.current)
127
- return;
128
- socketRef.current.emit("message:react", { messageId, reaction });
129
- }, []);
130
- const deleteMessage = useCallback((messageId) => {
131
- if (!socketRef.current)
132
- return;
133
- socketRef.current.emit("message:delete", { messageId });
134
- }, []);
135
- const createPoll = useCallback((conversationId, question, options, allowMultiple) => {
136
- if (!socketRef.current)
137
- return;
138
- socketRef.current.emit("poll:create", { conversationId, question, options, allowMultiple });
139
- }, []);
140
- const votePoll = useCallback((pollId, optionIndex) => {
141
- if (!socketRef.current)
142
- return;
143
- socketRef.current.emit("poll:vote", { pollId, optionIndex });
144
- }, []);
145
- const clearNotifications = useCallback(() => {
146
- setNotifications([]);
147
- setUnreadNotificationCount(0);
148
- }, []);
149
- const markRead = useCallback((conversationId, unreadIds) => {
150
- if (!socketRef.current)
151
- return;
152
- socketRef.current.emit("message:read", { conversationId, unreadIds });
153
- }, []);
154
- useEffect(() => {
155
- if (!user) {
156
- setIsConnected(false);
157
- setConversations([]);
158
- return;
159
- }
160
- const socket = getSocket();
161
- socketRef.current = socket;
162
- function onConnect() {
163
- setIsConnected(true);
164
- refreshConversations();
165
- }
166
- function onDisconnect() {
167
- setIsConnected(false);
168
- }
169
- socket.on("connect", onConnect);
170
- socket.on("disconnect", onDisconnect);
171
- if (!socket.connected) {
172
- socket.connect();
173
- }
174
- else {
175
- // Include logic for if already connected
176
- onConnect();
177
- }
178
- // New message
179
- socket.on("message:new", ({ conversationId, message }) => {
180
- setMessages((prev) => {
181
- const currentActiveId = activeConversationRef.current?._id;
182
- // If this message belongs to the active conversation, add it
183
- if (currentActiveId === conversationId) {
184
- // Deduplicate
185
- if (prev.some((m) => m._id === message._id))
186
- return prev;
187
- return [...prev, message];
188
- }
189
- return prev;
190
- });
191
- // Update conversations last message
192
- setConversations((prev) => prev
193
- .map((c) => c._id === conversationId
194
- ? {
195
- ...c,
196
- lastMessage: {
197
- content: message.content?.text || `[${message.type}]`,
198
- senderId: message.senderId,
199
- sentAt: message.createdAt,
200
- },
201
- updatedAt: message.createdAt,
202
- }
203
- : c)
204
- .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()));
205
- });
206
- // Message updated
207
- socket.on("message:updated", ({ message }) => {
208
- setMessages((prev) => prev.map((m) => (m._id === message._id ? message : m)));
209
- });
210
- socket.on("message:readed", ({ conversationId, userId, unreadIds }) => {
211
- setMessages((prev) => prev.map((m) => {
212
- if (m.conversationId === conversationId && unreadIds.includes(m._id)) {
213
- return {
214
- ...m,
215
- isRead: true,
216
- };
217
- }
218
- return m;
219
- }));
220
- setConversations((prev) => {
221
- return prev.map((c) => {
222
- if (c._id === conversationId) {
223
- return {
224
- ...c,
225
- participants: c.participants.map((p) => {
226
- if (p.userId === userId) {
227
- return {
228
- ...p,
229
- unreadCounts: {
230
- messages: 0,
231
- mentions: 0,
232
- milestones: 0,
233
- expenses: 0,
234
- }
235
- };
236
- }
237
- return p;
238
- }),
239
- };
240
- }
241
- return c;
242
- });
243
- });
244
- });
245
- // // Poll created/updated (we treat them as messages that might trigger UI updates)
246
- // socket.on("poll:created", ({ pollMessage }: { pollMessage: Message }) => {
247
- // setMessages((prev) => [...prev, pollMessage]);
248
- // });
249
- socket.on("poll:updated", ({ pollMessage }) => {
250
- // Re-fetch messages or update a specific one if referenceId matches
251
- setMessages(prev => prev.map(m => (m.type === "poll" && m._id === pollMessage._id)
252
- ? pollMessage // Store poll detail in message temporarily for UI
253
- : m));
254
- });
255
- // Message deleted
256
- socket.on("message:deleted", ({ messageId }) => {
257
- setMessages((prev) => prev.filter((m) => m._id !== messageId));
258
- });
259
- // Typing indicators
260
- socket.on("typing:start", ({ conversationId, userId }) => {
261
- setTypingUsers((prev) => {
262
- const next = new Map(prev);
263
- const users = next.get(conversationId) || [];
264
- if (!users.includes(userId)) {
265
- next.set(conversationId, [...users, userId]);
266
- }
267
- return next;
268
- });
269
- });
270
- socket.on("typing:stop", ({ conversationId, userId }) => {
271
- setTypingUsers((prev) => {
272
- const next = new Map(prev);
273
- const users = next.get(conversationId) || [];
274
- next.set(conversationId, users.filter((u) => u !== userId));
275
- return next;
276
- });
277
- });
278
- // Online/offline
279
- socket.on("presence:online", ({ userId }) => {
280
- setOnlineUsers((prev) => new Set(prev).add(userId));
281
- });
282
- socket.on("presence:offline", ({ userId }) => {
283
- setOnlineUsers((prev) => {
284
- const next = new Set(prev);
285
- next.delete(userId);
286
- return next;
287
- });
288
- });
289
- // Real-time notifications
290
- socket.on("notification:new", (notification) => {
291
- setNotifications((prev) => [notification, ...prev].slice(0, 50));
292
- setUnreadNotificationCount((prev) => prev + 1);
293
- });
294
- return () => {
295
- socket.off("connect", onConnect);
296
- socket.off("disconnect", onDisconnect);
297
- socket.off("message:new");
298
- socket.off("message:updated");
299
- socket.off("message:deleted");
300
- socket.off("typing:start");
301
- socket.off("typing:stop");
302
- socket.off("presence:online");
303
- socket.off("presence:offline");
304
- socket.off("notification:new");
305
- // Disconnect on unmount? Maybe not if shared across pages.
306
- // But if specific to chat...
307
- // For shared state lib, we might want to keep it open?
308
- // If user navigates away from chat pages, maybe close it?
309
- // The original logic disconnected on unmount.
310
- // But if we want global notifications (unread count), we need it open.
311
- // Let's keep it open but manage listeners.
312
- // Actually, if we unmount the provider, we should disconnect or remove listeners.
313
- // Assuming this Provider wraps only Chat pages.
314
- };
315
- }, [user, refreshConversations]);
316
- return (_jsx(ChatContext.Provider, { value: {
317
- socket: socketRef.current,
318
- isConnected,
319
- conversations,
320
- activeConversation,
321
- setActiveConversation,
322
- messages,
323
- setMessages,
324
- typingUsers,
325
- onlineUsers,
326
- refreshConversations,
327
- sendMessage,
328
- editMessage,
329
- toggleMessageReaction,
330
- deleteMessage,
331
- markRead,
332
- createPoll,
333
- votePoll,
334
- unreadNotificationCount,
335
- notifications,
336
- clearNotifications,
337
- }, children: children }));
338
- }
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
4
+ import { getSocket } from "../actions/socket";
5
+ import { chatApi } from "../actions/chat";
6
+ import { useAtomValue } from "jotai";
7
+ import { userAtom } from "../userAtom";
8
+ const ChatContext = createContext({
9
+ socket: null,
10
+ isConnected: false,
11
+ conversations: [],
12
+ activeConversation: null,
13
+ setActiveConversation: () => { },
14
+ messages: [],
15
+ setMessages: () => { },
16
+ typingUsers: new Map(),
17
+ onlineUsers: new Set(),
18
+ refreshConversations: async () => { },
19
+ sendMessage: async () => { },
20
+ createPoll: () => { },
21
+ votePoll: () => { },
22
+ editMessage: () => { },
23
+ toggleMessageReaction: () => { },
24
+ deleteMessage: () => { },
25
+ markRead: () => { },
26
+ unreadNotificationCount: 0,
27
+ notifications: [],
28
+ clearNotifications: () => { },
29
+ });
30
+ export function useChatContext() {
31
+ return useContext(ChatContext);
32
+ }
33
+ export function ChatProvider({ children }) {
34
+ const user = useAtomValue(userAtom);
35
+ const [isConnected, setIsConnected] = useState(false);
36
+ const [conversations, setConversations] = useState([]);
37
+ const [activeConversation, _setActiveConversation] = useState(null);
38
+ const activeConversationRef = useRef(null);
39
+ const [messages, setMessages] = useState([]);
40
+ const [typingUsers, setTypingUsers] = useState(new Map());
41
+ const [onlineUsers, setOnlineUsers] = useState(new Set());
42
+ const [unreadNotificationCount, setUnreadNotificationCount] = useState(0);
43
+ const [notifications, setNotifications] = useState([]);
44
+ const socketRef = useRef(null);
45
+ const setActiveConversation = useCallback((conv) => {
46
+ _setActiveConversation(conv);
47
+ activeConversationRef.current = conv;
48
+ }, []);
49
+ useEffect(() => {
50
+ activeConversationRef.current = activeConversation;
51
+ }, [activeConversation]);
52
+ const refreshConversations = useCallback(async () => {
53
+ try {
54
+ const data = await chatApi.getConversations();
55
+ setConversations(data);
56
+ }
57
+ catch (err) {
58
+ console.error("Failed to fetch conversations:", err);
59
+ }
60
+ }, []);
61
+ const sendMessage = useCallback(async (conversationId, text, files, replyTo) => {
62
+ let actualId = conversationId;
63
+ // If it's a draft conversation, create it first
64
+ if (conversationId.startsWith("draft_")) {
65
+ const otherUserId = conversationId.replace("draft_", "");
66
+ try {
67
+ const res = await chatApi.createConversation({
68
+ type: "individual",
69
+ otherUserId
70
+ });
71
+ if (res._id) {
72
+ actualId = res._id;
73
+ // Update active conversation in state to point to the real one
74
+ setActiveConversation(res);
75
+ // Refresh to include in list
76
+ await refreshConversations();
77
+ }
78
+ else {
79
+ console.error("Failed to create conversation during lazy send");
80
+ return;
81
+ }
82
+ }
83
+ catch (err) {
84
+ console.error("Failed to create conversation during lazy send:", err);
85
+ return;
86
+ }
87
+ }
88
+ if (!socketRef.current)
89
+ return;
90
+ let media = [];
91
+ if (files && files.length > 0) {
92
+ // Upload files first
93
+ const formData = new FormData();
94
+ files.forEach(f => formData.append("chatFiles", f));
95
+ try {
96
+ // We need a dedicated upload endpoint.
97
+ // For now, let's assume /api/v1/chat/upload
98
+ const response = await chatApi.uploadFiles(formData);
99
+ if (response.error) {
100
+ console.error("File upload failed:", response.error);
101
+ return;
102
+ }
103
+ media = response.data.files.map((f) => ({
104
+ type: f.mimetype.startsWith("audio") ? "audio" : (f.mimetype.startsWith("image") ? "image" : "file"),
105
+ url: f.location,
106
+ name: f.originalname
107
+ }));
108
+ }
109
+ catch (err) {
110
+ console.error("File upload failed:", err);
111
+ }
112
+ }
113
+ socketRef.current.emit("message:send", {
114
+ conversationId: actualId,
115
+ content: { text, media },
116
+ type: media.length > 0 ? "media" : "text",
117
+ replyTo,
118
+ });
119
+ }, [refreshConversations, setActiveConversation]);
120
+ const editMessage = useCallback((messageId, text) => {
121
+ if (!socketRef.current)
122
+ return;
123
+ socketRef.current.emit("message:edit", { messageId, text });
124
+ }, []);
125
+ const toggleMessageReaction = useCallback((messageId, reaction) => {
126
+ if (!socketRef.current)
127
+ return;
128
+ socketRef.current.emit("message:react", { messageId, reaction });
129
+ }, []);
130
+ const deleteMessage = useCallback((messageId) => {
131
+ if (!socketRef.current)
132
+ return;
133
+ socketRef.current.emit("message:delete", { messageId });
134
+ }, []);
135
+ const createPoll = useCallback((conversationId, question, options, allowMultiple) => {
136
+ if (!socketRef.current)
137
+ return;
138
+ socketRef.current.emit("poll:create", { conversationId, question, options, allowMultiple });
139
+ }, []);
140
+ const votePoll = useCallback((pollId, optionIndex) => {
141
+ if (!socketRef.current)
142
+ return;
143
+ socketRef.current.emit("poll:vote", { pollId, optionIndex });
144
+ }, []);
145
+ const clearNotifications = useCallback(() => {
146
+ setNotifications([]);
147
+ setUnreadNotificationCount(0);
148
+ }, []);
149
+ const markRead = useCallback((conversationId, unreadIds) => {
150
+ if (!socketRef.current)
151
+ return;
152
+ socketRef.current.emit("message:read", { conversationId, unreadIds });
153
+ }, []);
154
+ useEffect(() => {
155
+ if (!user) {
156
+ setIsConnected(false);
157
+ setConversations([]);
158
+ return;
159
+ }
160
+ const socket = getSocket();
161
+ socketRef.current = socket;
162
+ function onConnect() {
163
+ setIsConnected(true);
164
+ refreshConversations();
165
+ }
166
+ function onDisconnect() {
167
+ setIsConnected(false);
168
+ }
169
+ socket.on("connect", onConnect);
170
+ socket.on("disconnect", onDisconnect);
171
+ if (!socket.connected) {
172
+ socket.connect();
173
+ }
174
+ else {
175
+ // Include logic for if already connected
176
+ onConnect();
177
+ }
178
+ // New message
179
+ socket.on("message:new", ({ conversationId, message }) => {
180
+ setMessages((prev) => {
181
+ const currentActiveId = activeConversationRef.current?._id;
182
+ // If this message belongs to the active conversation, add it
183
+ if (currentActiveId === conversationId) {
184
+ // Deduplicate
185
+ if (prev.some((m) => m._id === message._id))
186
+ return prev;
187
+ return [...prev, message];
188
+ }
189
+ return prev;
190
+ });
191
+ // Update conversations last message
192
+ setConversations((prev) => prev
193
+ .map((c) => c._id === conversationId
194
+ ? {
195
+ ...c,
196
+ lastMessage: {
197
+ content: message.content?.text || `[${message.type}]`,
198
+ senderId: message.senderId,
199
+ sentAt: message.createdAt,
200
+ },
201
+ updatedAt: message.createdAt,
202
+ }
203
+ : c)
204
+ .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()));
205
+ });
206
+ // Message updated
207
+ socket.on("message:updated", ({ message }) => {
208
+ setMessages((prev) => prev.map((m) => (m._id === message._id ? message : m)));
209
+ });
210
+ socket.on("message:readed", ({ conversationId, userId, unreadIds }) => {
211
+ setMessages((prev) => prev.map((m) => {
212
+ if (m.conversationId === conversationId && unreadIds.includes(m._id)) {
213
+ return {
214
+ ...m,
215
+ isRead: true,
216
+ };
217
+ }
218
+ return m;
219
+ }));
220
+ setConversations((prev) => {
221
+ return prev.map((c) => {
222
+ if (c._id === conversationId) {
223
+ return {
224
+ ...c,
225
+ participants: c.participants.map((p) => {
226
+ if (p.userId === userId) {
227
+ return {
228
+ ...p,
229
+ unreadCounts: {
230
+ messages: 0,
231
+ mentions: 0,
232
+ milestones: 0,
233
+ expenses: 0,
234
+ }
235
+ };
236
+ }
237
+ return p;
238
+ }),
239
+ };
240
+ }
241
+ return c;
242
+ });
243
+ });
244
+ });
245
+ // // Poll created/updated (we treat them as messages that might trigger UI updates)
246
+ // socket.on("poll:created", ({ pollMessage }: { pollMessage: Message }) => {
247
+ // setMessages((prev) => [...prev, pollMessage]);
248
+ // });
249
+ socket.on("poll:updated", ({ pollMessage }) => {
250
+ // Re-fetch messages or update a specific one if referenceId matches
251
+ setMessages(prev => prev.map(m => (m.type === "poll" && m._id === pollMessage._id)
252
+ ? pollMessage // Store poll detail in message temporarily for UI
253
+ : m));
254
+ });
255
+ // Message deleted
256
+ socket.on("message:deleted", ({ messageId }) => {
257
+ setMessages((prev) => prev.filter((m) => m._id !== messageId));
258
+ });
259
+ // Typing indicators
260
+ socket.on("typing:start", ({ conversationId, userId }) => {
261
+ setTypingUsers((prev) => {
262
+ const next = new Map(prev);
263
+ const users = next.get(conversationId) || [];
264
+ if (!users.includes(userId)) {
265
+ next.set(conversationId, [...users, userId]);
266
+ }
267
+ return next;
268
+ });
269
+ });
270
+ socket.on("typing:stop", ({ conversationId, userId }) => {
271
+ setTypingUsers((prev) => {
272
+ const next = new Map(prev);
273
+ const users = next.get(conversationId) || [];
274
+ next.set(conversationId, users.filter((u) => u !== userId));
275
+ return next;
276
+ });
277
+ });
278
+ // Online/offline
279
+ socket.on("presence:online", ({ userId }) => {
280
+ setOnlineUsers((prev) => new Set(prev).add(userId));
281
+ });
282
+ socket.on("presence:offline", ({ userId }) => {
283
+ setOnlineUsers((prev) => {
284
+ const next = new Set(prev);
285
+ next.delete(userId);
286
+ return next;
287
+ });
288
+ });
289
+ // Real-time notifications
290
+ socket.on("notification:new", (notification) => {
291
+ setNotifications((prev) => [notification, ...prev].slice(0, 50));
292
+ setUnreadNotificationCount((prev) => prev + 1);
293
+ });
294
+ return () => {
295
+ socket.off("connect", onConnect);
296
+ socket.off("disconnect", onDisconnect);
297
+ socket.off("message:new");
298
+ socket.off("message:updated");
299
+ socket.off("message:deleted");
300
+ socket.off("typing:start");
301
+ socket.off("typing:stop");
302
+ socket.off("presence:online");
303
+ socket.off("presence:offline");
304
+ socket.off("notification:new");
305
+ // Disconnect on unmount? Maybe not if shared across pages.
306
+ // But if specific to chat...
307
+ // For shared state lib, we might want to keep it open?
308
+ // If user navigates away from chat pages, maybe close it?
309
+ // The original logic disconnected on unmount.
310
+ // But if we want global notifications (unread count), we need it open.
311
+ // Let's keep it open but manage listeners.
312
+ // Actually, if we unmount the provider, we should disconnect or remove listeners.
313
+ // Assuming this Provider wraps only Chat pages.
314
+ };
315
+ }, [user, refreshConversations]);
316
+ return (_jsx(ChatContext.Provider, { value: {
317
+ socket: socketRef.current,
318
+ isConnected,
319
+ conversations,
320
+ activeConversation,
321
+ setActiveConversation,
322
+ messages,
323
+ setMessages,
324
+ typingUsers,
325
+ onlineUsers,
326
+ refreshConversations,
327
+ sendMessage,
328
+ editMessage,
329
+ toggleMessageReaction,
330
+ deleteMessage,
331
+ markRead,
332
+ createPoll,
333
+ votePoll,
334
+ unreadNotificationCount,
335
+ notifications,
336
+ clearNotifications,
337
+ }, children: children }));
338
+ }