@myrjfa/state 2.2.0 → 2.3.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.
Files changed (249) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +20 -3
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +8 -0
  6. package/dist/lib/actions/actions.d.ts +13 -13
  7. package/dist/lib/actions/actions.d.ts.map +1 -1
  8. package/dist/lib/actions/actions.js +5 -4
  9. package/dist/lib/actions/adminDashboard.d.ts +4 -0
  10. package/dist/lib/actions/adminDashboard.d.ts.map +1 -0
  11. package/dist/lib/actions/adminDashboard.js +29 -0
  12. package/dist/lib/actions/adventure.d.ts +104 -0
  13. package/dist/lib/actions/adventure.d.ts.map +1 -0
  14. package/dist/lib/actions/adventure.js +203 -0
  15. package/dist/lib/actions/application.d.ts +35 -0
  16. package/dist/lib/actions/application.d.ts.map +1 -0
  17. package/dist/lib/actions/application.js +573 -0
  18. package/dist/lib/actions/auth.d.ts +108 -3
  19. package/dist/lib/actions/auth.d.ts.map +1 -1
  20. package/dist/lib/actions/auth.js +38 -8
  21. package/dist/lib/actions/blog.d.ts +61 -0
  22. package/dist/lib/actions/blog.d.ts.map +1 -0
  23. package/dist/lib/actions/blog.js +762 -0
  24. package/dist/lib/actions/brand.d.ts +83 -0
  25. package/dist/lib/actions/brand.d.ts.map +1 -0
  26. package/dist/lib/actions/brand.js +28 -0
  27. package/dist/lib/actions/chat.d.ts +261 -24
  28. package/dist/lib/actions/chat.d.ts.map +1 -1
  29. package/dist/lib/actions/chat.js +525 -41
  30. package/dist/lib/actions/companion.d.ts +174 -0
  31. package/dist/lib/actions/companion.d.ts.map +1 -0
  32. package/dist/lib/actions/companion.js +276 -0
  33. package/dist/lib/actions/event.d.ts +178 -0
  34. package/dist/lib/actions/event.d.ts.map +1 -0
  35. package/dist/lib/actions/event.js +214 -0
  36. package/dist/lib/actions/fetcher.d.ts.map +1 -1
  37. package/dist/lib/actions/fetcher.js +18 -8
  38. package/dist/lib/actions/guide.d.ts +55 -0
  39. package/dist/lib/actions/guide.d.ts.map +1 -0
  40. package/dist/lib/actions/guide.js +128 -0
  41. package/dist/lib/actions/hostDashboard.d.ts +122 -0
  42. package/dist/lib/actions/hostDashboard.d.ts.map +1 -0
  43. package/dist/lib/actions/hostDashboard.js +329 -0
  44. package/dist/lib/actions/location.d.ts +141 -0
  45. package/dist/lib/actions/location.d.ts.map +1 -0
  46. package/dist/lib/actions/location.js +113 -0
  47. package/dist/lib/actions/notifications.d.ts +91 -0
  48. package/dist/lib/actions/notifications.d.ts.map +1 -0
  49. package/dist/lib/actions/notifications.js +83 -0
  50. package/dist/lib/actions/opportunity.d.ts +44 -0
  51. package/dist/lib/actions/opportunity.d.ts.map +1 -0
  52. package/dist/lib/actions/opportunity.js +318 -0
  53. package/dist/lib/actions/package.d.ts +85 -0
  54. package/dist/lib/actions/package.d.ts.map +1 -0
  55. package/dist/lib/actions/package.js +167 -0
  56. package/dist/lib/actions/place.d.ts +191 -0
  57. package/dist/lib/actions/place.d.ts.map +1 -0
  58. package/dist/lib/actions/place.js +57 -0
  59. package/dist/lib/actions/portfolio.d.ts +65 -0
  60. package/dist/lib/actions/portfolio.d.ts.map +1 -0
  61. package/dist/lib/actions/portfolio.js +62 -0
  62. package/dist/lib/actions/property.d.ts +17 -0
  63. package/dist/lib/actions/property.d.ts.map +1 -0
  64. package/dist/lib/actions/property.js +16 -0
  65. package/dist/lib/actions/rental.d.ts +87 -0
  66. package/dist/lib/actions/rental.d.ts.map +1 -0
  67. package/dist/lib/actions/rental.js +205 -0
  68. package/dist/lib/actions/review.d.ts +8 -0
  69. package/dist/lib/actions/review.d.ts.map +1 -0
  70. package/dist/lib/actions/review.js +102 -0
  71. package/dist/lib/actions/room.d.ts +39 -0
  72. package/dist/lib/actions/room.d.ts.map +1 -0
  73. package/dist/lib/actions/room.js +74 -0
  74. package/dist/lib/actions/serverActions.d.ts +1 -0
  75. package/dist/lib/actions/serverActions.d.ts.map +1 -1
  76. package/dist/lib/actions/serverActions.js +15 -0
  77. package/dist/lib/actions/socket.d.ts.map +1 -1
  78. package/dist/lib/actions/socket.js +5 -1
  79. package/dist/lib/actions/stay.d.ts +97 -0
  80. package/dist/lib/actions/stay.d.ts.map +1 -0
  81. package/dist/lib/actions/stay.js +138 -0
  82. package/dist/lib/actions/travelPackage.d.ts +104 -0
  83. package/dist/lib/actions/travelPackage.d.ts.map +1 -0
  84. package/dist/lib/actions/travelPackage.js +487 -0
  85. package/dist/lib/actions.d.ts +24 -24
  86. package/dist/lib/actions.d.ts.map +1 -1
  87. package/dist/lib/actions.js +14 -11
  88. package/dist/lib/adminDashboardActions.d.ts +15 -3
  89. package/dist/lib/adminDashboardActions.d.ts.map +1 -1
  90. package/dist/lib/adminDashboardActions.js +16 -7
  91. package/dist/lib/authSessionManager.d.ts.map +1 -1
  92. package/dist/lib/authSessionManager.js +4 -3
  93. package/dist/lib/authSessionPolicy.d.ts +10 -0
  94. package/dist/lib/authSessionPolicy.d.ts.map +1 -0
  95. package/dist/lib/authSessionPolicy.js +9 -0
  96. package/dist/lib/authorizationActions.d.ts +77 -0
  97. package/dist/lib/authorizationActions.d.ts.map +1 -0
  98. package/dist/lib/authorizationActions.js +61 -0
  99. package/dist/lib/context/ChatContext.d.ts +25 -6
  100. package/dist/lib/context/ChatContext.d.ts.map +1 -1
  101. package/dist/lib/context/ChatContext.js +356 -245
  102. package/dist/lib/data/adventureDummyData.d.ts +3 -0
  103. package/dist/lib/data/adventureDummyData.d.ts.map +1 -0
  104. package/dist/lib/data/adventureDummyData.js +138 -0
  105. package/dist/lib/data/adventureFormPage.d.ts +28 -0
  106. package/dist/lib/data/adventureFormPage.d.ts.map +1 -0
  107. package/dist/lib/data/adventureFormPage.js +58 -0
  108. package/dist/lib/data/companionDummyData.d.ts +99 -0
  109. package/dist/lib/data/companionDummyData.d.ts.map +1 -0
  110. package/dist/lib/data/companionDummyData.js +203 -0
  111. package/dist/lib/data/destinations.d.ts +14 -0
  112. package/dist/lib/data/destinations.d.ts.map +1 -0
  113. package/dist/lib/data/destinations.js +203 -0
  114. package/dist/lib/data/eventFormPage.d.ts +38 -0
  115. package/dist/lib/data/eventFormPage.d.ts.map +1 -0
  116. package/dist/lib/data/eventFormPage.js +56 -0
  117. package/dist/lib/data/eventsDummyData.d.ts +54 -0
  118. package/dist/lib/data/eventsDummyData.d.ts.map +1 -0
  119. package/dist/lib/data/eventsDummyData.js +586 -0
  120. package/dist/lib/data/guideDummyData.d.ts +3 -0
  121. package/dist/lib/data/guideDummyData.d.ts.map +1 -0
  122. package/dist/lib/data/guideDummyData.js +66 -0
  123. package/dist/lib/data/opportunityFormData.d.ts +13 -0
  124. package/dist/lib/data/opportunityFormData.d.ts.map +1 -0
  125. package/dist/lib/data/opportunityFormData.js +62 -0
  126. package/dist/lib/data/opportunityFormPage.d.ts +12 -0
  127. package/dist/lib/data/opportunityFormPage.d.ts.map +1 -0
  128. package/dist/lib/data/opportunityFormPage.js +33 -0
  129. package/dist/lib/data/packageDummyData.d.ts +3 -0
  130. package/dist/lib/data/packageDummyData.d.ts.map +1 -0
  131. package/dist/lib/data/packageDummyData.js +56 -0
  132. package/dist/lib/data/rentalDummyData.d.ts +3 -0
  133. package/dist/lib/data/rentalDummyData.d.ts.map +1 -0
  134. package/dist/lib/data/rentalDummyData.js +382 -0
  135. package/dist/lib/data/rentalFormPage.d.ts +11 -0
  136. package/dist/lib/data/rentalFormPage.d.ts.map +1 -0
  137. package/dist/lib/data/rentalFormPage.js +86 -0
  138. package/dist/lib/data/staysDummyData.d.ts +72 -0
  139. package/dist/lib/data/staysDummyData.d.ts.map +1 -0
  140. package/dist/lib/data/staysDummyData.js +694 -0
  141. package/dist/lib/data/storeDummyData.d.ts +33 -0
  142. package/dist/lib/data/storeDummyData.d.ts.map +1 -0
  143. package/dist/lib/data/storeDummyData.js +336 -0
  144. package/dist/lib/data/travelPackageFormData.d.ts +19 -0
  145. package/dist/lib/data/travelPackageFormData.d.ts.map +1 -0
  146. package/dist/lib/data/travelPackageFormData.js +51 -0
  147. package/dist/lib/data/travelPackageFormPage.d.ts +22 -0
  148. package/dist/lib/data/travelPackageFormPage.d.ts.map +1 -0
  149. package/dist/lib/data/travelPackageFormPage.js +40 -0
  150. package/dist/lib/data/vehicleData.d.ts +2 -0
  151. package/dist/lib/data/vehicleData.d.ts.map +1 -0
  152. package/dist/lib/data/vehicleData.js +187 -0
  153. package/dist/lib/data/vehicleModels.d.ts +2 -0
  154. package/dist/lib/data/vehicleModels.d.ts.map +1 -0
  155. package/dist/lib/data/vehicleModels.js +128 -0
  156. package/dist/lib/discountActions.d.ts +43 -0
  157. package/dist/lib/discountActions.d.ts.map +1 -0
  158. package/dist/lib/discountActions.js +69 -0
  159. package/dist/lib/hooks/usePersistentDraftState.d.ts +20 -0
  160. package/dist/lib/hooks/usePersistentDraftState.d.ts.map +1 -0
  161. package/dist/lib/hooks/usePersistentDraftState.js +47 -0
  162. package/dist/lib/loyaltyActions.d.ts +32 -0
  163. package/dist/lib/loyaltyActions.d.ts.map +1 -0
  164. package/dist/lib/loyaltyActions.js +92 -0
  165. package/dist/lib/models/adventure.d.ts +0 -2
  166. package/dist/lib/models/adventure.d.ts.map +1 -1
  167. package/dist/lib/models/authorization.d.ts +107 -0
  168. package/dist/lib/models/authorization.d.ts.map +1 -0
  169. package/dist/lib/models/authorization.js +263 -0
  170. package/dist/lib/models/blog.d.ts +2 -2
  171. package/dist/lib/models/booking.d.ts +7 -2
  172. package/dist/lib/models/booking.d.ts.map +1 -1
  173. package/dist/lib/models/chat.d.ts +418 -32
  174. package/dist/lib/models/chat.d.ts.map +1 -1
  175. package/dist/lib/models/chat.js +5 -1
  176. package/dist/lib/models/discount.d.ts +587 -136
  177. package/dist/lib/models/discount.d.ts.map +1 -1
  178. package/dist/lib/models/discount.js +118 -96
  179. package/dist/lib/models/forms.d.ts +384 -0
  180. package/dist/lib/models/forms.d.ts.map +1 -0
  181. package/dist/lib/models/forms.js +121 -0
  182. package/dist/lib/models/guide.d.ts +0 -2
  183. package/dist/lib/models/guide.d.ts.map +1 -1
  184. package/dist/lib/models/loyalty.d.ts +88 -0
  185. package/dist/lib/models/loyalty.d.ts.map +1 -0
  186. package/dist/lib/models/loyalty.js +1 -0
  187. package/dist/lib/models/notifications.d.ts +4 -4
  188. package/dist/lib/models/opportunities/freelance.d.ts +66 -20
  189. package/dist/lib/models/opportunities/freelance.d.ts.map +1 -1
  190. package/dist/lib/models/opportunities/internship.d.ts +66 -20
  191. package/dist/lib/models/opportunities/internship.d.ts.map +1 -1
  192. package/dist/lib/models/opportunities/job.d.ts +66 -20
  193. package/dist/lib/models/opportunities/job.d.ts.map +1 -1
  194. package/dist/lib/models/opportunities/opportunity.d.ts +66 -20
  195. package/dist/lib/models/opportunities/opportunity.d.ts.map +1 -1
  196. package/dist/lib/models/opportunities/opportunity.js +2 -0
  197. package/dist/lib/models/opportunities/volunteerJob.d.ts +66 -20
  198. package/dist/lib/models/opportunities/volunteerJob.d.ts.map +1 -1
  199. package/dist/lib/models/package.d.ts +81 -220
  200. package/dist/lib/models/package.d.ts.map +1 -1
  201. package/dist/lib/models/package.js +0 -2
  202. package/dist/lib/models/props.d.ts +1 -1
  203. package/dist/lib/models/props.d.ts.map +1 -1
  204. package/dist/lib/models/rental.d.ts +0 -2
  205. package/dist/lib/models/rental.d.ts.map +1 -1
  206. package/dist/lib/models/stay.d.ts +4 -4
  207. package/dist/lib/models/store.d.ts +71 -0
  208. package/dist/lib/models/store.d.ts.map +1 -0
  209. package/dist/lib/models/store.js +1 -0
  210. package/dist/lib/models/tile.d.ts +0 -2
  211. package/dist/lib/models/tile.d.ts.map +1 -1
  212. package/dist/lib/models/travelPackage.d.ts +206 -305
  213. package/dist/lib/models/travelPackage.d.ts.map +1 -1
  214. package/dist/lib/models/travelPackage.js +0 -2
  215. package/dist/lib/models/user.d.ts +28 -4
  216. package/dist/lib/models/user.d.ts.map +1 -1
  217. package/dist/lib/models/user.js +10 -1
  218. package/dist/lib/opportunityFormPage.d.ts +1 -0
  219. package/dist/lib/opportunityFormPage.d.ts.map +1 -1
  220. package/dist/lib/opportunityFormPage.js +1 -0
  221. package/dist/lib/pricingActions.d.ts +13 -0
  222. package/dist/lib/pricingActions.d.ts.map +1 -0
  223. package/dist/lib/pricingActions.js +16 -0
  224. package/dist/lib/rentalActions.js +0 -1
  225. package/dist/lib/rentalDummyData.d.ts.map +1 -1
  226. package/dist/lib/rentalDummyData.js +0 -35
  227. package/dist/lib/rentalFormPage.d.ts +0 -2
  228. package/dist/lib/rentalFormPage.d.ts.map +1 -1
  229. package/dist/lib/rentalFormPage.js +0 -3
  230. package/dist/lib/storeActions.d.ts +25 -0
  231. package/dist/lib/storeActions.d.ts.map +1 -0
  232. package/dist/lib/storeActions.js +46 -0
  233. package/dist/lib/storeDummyData.d.ts +2 -26
  234. package/dist/lib/storeDummyData.d.ts.map +1 -1
  235. package/dist/lib/userAtom.d.ts +71 -36
  236. package/dist/lib/userAtom.d.ts.map +1 -1
  237. package/dist/lib/userAtom.js +11 -17
  238. package/dist/lib/utils/entitySubmission.d.ts +21 -0
  239. package/dist/lib/utils/entitySubmission.d.ts.map +1 -0
  240. package/dist/lib/utils/entitySubmission.js +10 -0
  241. package/dist/lib/utils/imageUpload.d.ts +4 -0
  242. package/dist/lib/utils/imageUpload.d.ts.map +1 -0
  243. package/dist/lib/utils/imageUpload.js +36 -0
  244. package/dist/lib/utils/tileConverters.d.ts +8 -0
  245. package/dist/lib/utils/tileConverters.d.ts.map +1 -0
  246. package/dist/lib/utils/tileConverters.js +59 -0
  247. package/dist/lib/utils.d.ts.map +1 -1
  248. package/dist/lib/utils.js +1 -1
  249. package/package.json +4 -2
@@ -1,318 +1,426 @@
1
1
  "use client";
2
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";
3
+ import { createContext, useCallback, useContext, useEffect, useRef, useState, } from "react";
6
4
  import { useAtomValue } from "jotai";
5
+ import { getSocket } from "../actions/socket";
6
+ import { chatApi, normalizeChatMessage, } from "../actions/chat";
7
+ import { chatActorKey, isSameChatActor, } from "../models/chat";
7
8
  import { userAtom } from "../userAtom";
8
- const ChatContext = createContext({
9
+ const emptyContext = {
9
10
  socket: null,
10
11
  isConnected: false,
11
12
  conversations: [],
12
13
  activeConversation: null,
13
- setActiveConversation: () => { },
14
+ setActiveConversation: () => undefined,
14
15
  messages: [],
15
- setMessages: () => { },
16
+ setMessages: () => undefined,
16
17
  typingUsers: new Map(),
17
18
  onlineUsers: new Set(),
18
- refreshConversations: async () => { },
19
- sendMessage: async () => { },
20
- createPoll: () => { },
21
- votePoll: () => { },
22
- editMessage: () => { },
23
- toggleMessageReaction: () => { },
24
- deleteMessage: () => { },
25
- markRead: () => { },
19
+ refreshConversations: async () => undefined,
20
+ sendMessage: async () => undefined,
21
+ createPoll: () => undefined,
22
+ votePoll: async () => null,
23
+ updatePoll: async () => null,
24
+ editMessage: () => undefined,
25
+ toggleMessageReaction: () => undefined,
26
+ deleteMessage: () => undefined,
27
+ markRead: () => undefined,
26
28
  unreadNotificationCount: 0,
27
29
  notifications: [],
28
- clearNotifications: () => { },
29
- });
30
- export function useChatContext() {
31
- return useContext(ChatContext);
32
- }
30
+ clearNotifications: () => undefined,
31
+ eventRevision: 0,
32
+ paymentRevision: 0,
33
+ };
34
+ const ChatContext = createContext(emptyContext);
35
+ export const useChatContext = () => useContext(ChatContext);
33
36
  export function ChatProvider({ children }) {
34
37
  const user = useAtomValue(userAtom);
38
+ const actorId = user?._id || user?.id || "";
39
+ const accountType = user?.accountType || user?.role;
40
+ const actorModel = accountType === "host" ? "HostProfile" : "User";
35
41
  const [isConnected, setIsConnected] = useState(false);
36
42
  const [conversations, setConversations] = useState([]);
37
- const [activeConversation, _setActiveConversation] = useState(null);
43
+ const [activeConversation, setActiveConversationState] = useState(null);
38
44
  const activeConversationRef = useRef(null);
39
45
  const [messages, setMessages] = useState([]);
40
46
  const [typingUsers, setTypingUsers] = useState(new Map());
41
47
  const [onlineUsers, setOnlineUsers] = useState(new Set());
42
48
  const [unreadNotificationCount, setUnreadNotificationCount] = useState(0);
43
49
  const [notifications, setNotifications] = useState([]);
50
+ const [eventRevision, setEventRevision] = useState(0);
51
+ const [paymentRevision, setPaymentRevision] = useState(0);
44
52
  const socketRef = useRef(null);
45
- const setActiveConversation = useCallback((conv) => {
46
- _setActiveConversation(conv);
47
- activeConversationRef.current = conv;
53
+ const conversationListParamsRef = useRef({ filter: "all" });
54
+ const setActiveConversation = useCallback((conversation) => {
55
+ setActiveConversationState(conversation);
56
+ activeConversationRef.current = conversation;
48
57
  }, []);
49
- useEffect(() => {
50
- activeConversationRef.current = activeConversation;
51
- }, [activeConversation]);
52
- const refreshConversations = useCallback(async () => {
53
- try {
54
- const data = await chatApi.getConversations();
55
- setConversations(data);
58
+ const refreshConversations = useCallback(async (params) => {
59
+ if (params)
60
+ conversationListParamsRef.current = params;
61
+ const data = await chatApi.getConversations(params || conversationListParamsRef.current);
62
+ setConversations(data);
63
+ const activeId = activeConversationRef.current?._id;
64
+ if (activeId) {
65
+ const refreshed = data.find((conversation) => conversation._id === activeId);
66
+ if (refreshed)
67
+ setActiveConversation(refreshed);
56
68
  }
57
- catch (err) {
58
- console.error("Failed to fetch conversations:", err);
69
+ }, [setActiveConversation]);
70
+ const sendMessage = useCallback(async (conversationId, text, files, replyTo, options) => {
71
+ const trimmed = text.trim();
72
+ let media = [];
73
+ if (files?.length) {
74
+ const formData = new FormData();
75
+ files.forEach((file) => formData.append("files", file));
76
+ const upload = await chatApi.uploadFiles(formData);
77
+ media = upload?.data?.files || [];
78
+ if (!media.length)
79
+ throw new Error("Unable to upload attachments");
80
+ media = media.map((item, index) => {
81
+ const metadata = files[index]?.chatMediaMetadata;
82
+ return metadata ? { ...item, ...metadata } : item;
83
+ });
59
84
  }
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
85
+ if (!trimmed && !media.length)
86
+ return;
87
+ const socket = socketRef.current;
88
+ if (socket?.connected) {
89
+ await new Promise((resolve, reject) => {
90
+ socket.emit("message:send", {
91
+ conversationId,
92
+ text: trimmed,
93
+ replyTo,
94
+ media,
95
+ type: options?.type || (media.length ? "media" : "text"),
96
+ mentions: options?.mentions || [],
97
+ }, (result) => {
98
+ if (result?.success)
99
+ resolve();
100
+ else
101
+ reject(new Error(result?.error?.message || "Unable to send message"));
70
102
  });
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)
103
+ });
89
104
  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
105
  }
113
- socketRef.current.emit("message:send", {
114
- conversationId: actualId,
115
- content: { text, media },
116
- type: media.length > 0 ? "media" : "text",
106
+ const message = await chatApi.sendMessage(conversationId, {
107
+ text: trimmed,
117
108
  replyTo,
109
+ media,
110
+ type: options?.type || (media.length ? "media" : "text"),
111
+ mentions: options?.mentions || [],
118
112
  });
119
- }, [refreshConversations, setActiveConversation]);
120
- const editMessage = useCallback((messageId, text) => {
121
- if (!socketRef.current)
113
+ if (message) {
114
+ setMessages((current) => current.some((item) => item._id === message._id)
115
+ ? current
116
+ : [...current, message]);
117
+ await refreshConversations();
118
+ }
119
+ }, [refreshConversations]);
120
+ const editMessage = useCallback(async (messageId, text) => {
121
+ if (socketRef.current?.connected) {
122
+ socketRef.current.emit("message:edit", { messageId, text });
122
123
  return;
123
- socketRef.current.emit("message:edit", { messageId, text });
124
+ }
125
+ const message = await chatApi.editMessage(messageId, text);
126
+ if (message) {
127
+ setMessages((current) => current.map((item) => (item._id === message._id ? message : item)));
128
+ }
124
129
  }, []);
125
- const toggleMessageReaction = useCallback((messageId, reaction) => {
126
- if (!socketRef.current)
127
- return;
128
- socketRef.current.emit("message:react", { messageId, reaction });
130
+ const createPoll = useCallback(async (conversationId, question, options, allowMultiple) => {
131
+ const response = await chatApi.createPoll(conversationId, { question, options, allowMultiple });
132
+ if (response.data) {
133
+ const message = normalizeChatMessage(response.data);
134
+ setMessages((current) => current.some((item) => item._id === message._id) ? current : [...current, message]);
135
+ await refreshConversations();
136
+ }
137
+ }, [refreshConversations]);
138
+ const votePoll = useCallback(async (pollId, optionIndex) => {
139
+ const response = await chatApi.votePoll(pollId, optionIndex);
140
+ return response.data || null;
129
141
  }, []);
130
- const deleteMessage = useCallback((messageId) => {
131
- if (!socketRef.current)
132
- return;
133
- socketRef.current.emit("message:delete", { messageId });
142
+ const updatePoll = useCallback(async (pollId, data) => {
143
+ const response = await chatApi.updatePoll(pollId, data);
144
+ return response.data || null;
134
145
  }, []);
135
- const createPoll = useCallback((conversationId, question, options, allowMultiple) => {
136
- if (!socketRef.current)
146
+ const toggleMessageReaction = useCallback(async (messageId, emoji) => {
147
+ if (socketRef.current?.connected) {
148
+ socketRef.current.emit("message:react", {
149
+ messageId,
150
+ emoji: emoji || "",
151
+ });
137
152
  return;
138
- socketRef.current.emit("poll:create", { conversationId, question, options, allowMultiple });
153
+ }
154
+ const message = await chatApi.addReaction(messageId, emoji || "");
155
+ if (message) {
156
+ setMessages((current) => current.map((item) => (item._id === message._id ? message : item)));
157
+ }
139
158
  }, []);
140
- const votePoll = useCallback((pollId, optionIndex) => {
141
- if (!socketRef.current)
159
+ const deleteMessage = useCallback(async (messageId) => {
160
+ if (socketRef.current?.connected) {
161
+ socketRef.current.emit("message:delete", { messageId });
142
162
  return;
143
- socketRef.current.emit("poll:vote", { pollId, optionIndex });
163
+ }
164
+ const message = await chatApi.deleteMessage(messageId);
165
+ if (message) {
166
+ setMessages((current) => current.map((item) => (item._id === message._id ? message : item)));
167
+ }
144
168
  }, []);
169
+ const markRead = useCallback(async (conversationId, messageIds) => {
170
+ if (!messageIds.length || !actorId)
171
+ return;
172
+ const readAt = new Date().toISOString();
173
+ setMessages((current) => current.map((message) => message.conversationId === conversationId && messageIds.includes(message._id) &&
174
+ !message.readBy.some((receipt) => isSameChatActor(receipt, { actorId, actorModel }))
175
+ ? {
176
+ ...message,
177
+ readBy: [
178
+ ...message.readBy,
179
+ { actorId, actorModel, readAt },
180
+ ],
181
+ }
182
+ : message));
183
+ setConversations((current) => current.map((conversation) => conversation._id === conversationId
184
+ ? {
185
+ ...conversation,
186
+ participants: conversation.participants.map((participant) => isSameChatActor(participant, { actorId, actorModel })
187
+ ? {
188
+ ...participant,
189
+ unreadCount: Math.max(0, participant.unreadCount - messageIds.length),
190
+ }
191
+ : participant),
192
+ preference: {
193
+ ...conversation.preference,
194
+ markedUnreadAt: null,
195
+ },
196
+ }
197
+ : conversation));
198
+ if (socketRef.current?.connected) {
199
+ socketRef.current.emit("message:read", {
200
+ conversationId,
201
+ messageIds,
202
+ });
203
+ return;
204
+ }
205
+ await chatApi.markRead(conversationId, messageIds);
206
+ await refreshConversations();
207
+ }, [actorId, actorModel, refreshConversations]);
145
208
  const clearNotifications = useCallback(() => {
146
209
  setNotifications([]);
147
210
  setUnreadNotificationCount(0);
148
211
  }, []);
149
- const markRead = useCallback((conversationId, unreadIds) => {
150
- if (!socketRef.current)
212
+ useEffect(() => {
213
+ const socket = socketRef.current;
214
+ const conversationId = activeConversation?._id;
215
+ if (!socket || !conversationId || conversationId.startsWith("draft_")) {
151
216
  return;
152
- socketRef.current.emit("message:read", { conversationId, unreadIds });
153
- }, []);
217
+ }
218
+ socket.emit("conversation:join", { conversationId });
219
+ return () => {
220
+ socket.emit("conversation:leave", { conversationId });
221
+ };
222
+ }, [activeConversation?._id]);
154
223
  useEffect(() => {
155
- if (!user) {
224
+ if (!user || !actorId || accountType === "staff") {
156
225
  setIsConnected(false);
157
226
  setConversations([]);
158
227
  return;
159
228
  }
160
229
  const socket = getSocket();
161
230
  socketRef.current = socket;
162
- function onConnect() {
231
+ const onConnect = () => {
163
232
  setIsConnected(true);
164
- refreshConversations();
165
- }
166
- function onDisconnect() {
233
+ void refreshConversations();
234
+ const activeId = activeConversationRef.current?._id;
235
+ if (activeId && !activeId.startsWith("draft_")) {
236
+ socket.emit("conversation:join", { conversationId: activeId });
237
+ }
238
+ };
239
+ const onDisconnect = () => {
167
240
  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
241
+ setOnlineUsers(new Set());
242
+ setTypingUsers(new Map());
243
+ };
244
+ const onNewMessage = (payload) => {
245
+ const message = normalizeChatMessage(payload.message);
246
+ if (activeConversationRef.current?._id === payload.conversationId) {
247
+ setMessages((current) => current.some((item) => item._id === message._id)
248
+ ? current
249
+ : [...current, message]);
250
+ }
251
+ if (!isSameChatActor({ actorId: message.senderId, actorModel: message.senderModel }, { actorId, actorModel })) {
252
+ socketRef.current?.emit("message:delivered", { messageId: message._id });
253
+ }
254
+ void refreshConversations();
255
+ };
256
+ const onDelivered = ({ messageId, actorId: deliveredActorId, actorModel: deliveredActorModel, deliveredAt, }) => {
257
+ setMessages((current) => current.map((message) => message._id === messageId && !(message.deliveredTo || []).some((receipt) => isSameChatActor(receipt, {
258
+ actorId: deliveredActorId,
259
+ actorModel: deliveredActorModel,
260
+ }))
194
261
  ? {
195
- ...c,
196
- lastMessage: {
197
- content: message.content?.text || `[${message.type}]`,
198
- senderId: message.senderId,
199
- sentAt: message.createdAt,
200
- },
201
- updatedAt: message.createdAt,
262
+ ...message,
263
+ deliveredTo: [
264
+ ...(message.deliveredTo || []),
265
+ {
266
+ actorId: deliveredActorId,
267
+ actorModel: deliveredActorModel,
268
+ deliveredAt,
269
+ },
270
+ ],
202
271
  }
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
- };
272
+ : message));
273
+ };
274
+ const onUpdatedMessage = ({ message: rawMessage }) => {
275
+ const message = normalizeChatMessage(rawMessage);
276
+ setMessages((current) => current.map((item) => (item._id === message._id ? message : item)));
277
+ };
278
+ const onRead = ({ conversationId, actorId: readerId, actorModel: readerModel, messageIds, readAt, }) => {
279
+ const readMessageIds = new Set(messageIds);
280
+ setMessages((current) => current.map((message) => message.conversationId === conversationId &&
281
+ readMessageIds.has(message._id) &&
282
+ !message.readBy.some((receipt) => isSameChatActor(receipt, {
283
+ actorId: readerId,
284
+ actorModel: readerModel,
285
+ }))
286
+ ? {
287
+ ...message,
288
+ readBy: [
289
+ ...message.readBy,
290
+ { actorId: readerId, actorModel: readerModel, readAt },
291
+ ],
292
+ deliveredTo: (message.deliveredTo || []).some((receipt) => isSameChatActor(receipt, {
293
+ actorId: readerId,
294
+ actorModel: readerModel,
295
+ }))
296
+ ? message.deliveredTo
297
+ : [
298
+ ...(message.deliveredTo || []),
299
+ {
300
+ actorId: readerId,
301
+ actorModel: readerModel,
302
+ deliveredAt: readAt,
303
+ },
304
+ ],
217
305
  }
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
- });
306
+ : message));
307
+ };
308
+ const onTypingStart = ({ conversationId, actorId: typingActorId, actorModel: typingActorModel, }) => {
309
+ const typingActorKey = chatActorKey({
310
+ actorId: typingActorId,
311
+ actorModel: typingActorModel,
243
312
  });
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);
313
+ setTypingUsers((current) => {
314
+ const next = new Map(current);
263
315
  const users = next.get(conversationId) || [];
264
- if (!users.includes(userId)) {
265
- next.set(conversationId, [...users, userId]);
316
+ if (!users.includes(typingActorKey)) {
317
+ next.set(conversationId, [...users, typingActorKey]);
266
318
  }
267
319
  return next;
268
320
  });
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));
321
+ };
322
+ const onTypingStop = ({ conversationId, actorId: typingActorId, actorModel: typingActorModel, }) => {
323
+ const typingActorKey = chatActorKey({
324
+ actorId: typingActorId,
325
+ actorModel: typingActorModel,
326
+ });
327
+ setTypingUsers((current) => {
328
+ const next = new Map(current);
329
+ next.set(conversationId, (next.get(conversationId) || []).filter((item) => item !== typingActorKey));
275
330
  return next;
276
331
  });
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);
332
+ };
333
+ const onPresenceOnline = ({ actorId: onlineId, actorModel: onlineModel, }) => {
334
+ setOnlineUsers((current) => new Set(current).add(chatActorKey({
335
+ actorId: onlineId,
336
+ actorModel: onlineModel,
337
+ })));
338
+ };
339
+ const onPresenceOffline = ({ actorId: offlineId, actorModel: offlineModel, }) => {
340
+ setOnlineUsers((current) => {
341
+ const next = new Set(current);
342
+ next.delete(chatActorKey({
343
+ actorId: offlineId,
344
+ actorModel: offlineModel,
345
+ }));
286
346
  return next;
287
347
  });
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
- });
348
+ };
349
+ const onNotification = (notification) => {
350
+ setNotifications((current) => [notification, ...current].slice(0, 50));
351
+ setUnreadNotificationCount((current) => current + 1);
352
+ };
353
+ const onPreferenceUpdated = ({ conversationId, preference, }) => {
354
+ setConversations((current) => current.map((conversation) => conversation._id === conversationId
355
+ ? { ...conversation, preference }
356
+ : conversation));
357
+ if (activeConversationRef.current?._id === conversationId) {
358
+ setActiveConversation({
359
+ ...activeConversationRef.current,
360
+ preference,
361
+ });
362
+ }
363
+ };
364
+ const onConversationMetadataChanged = () => {
365
+ void refreshConversations();
366
+ };
367
+ const onConversationEventChanged = () => {
368
+ setEventRevision((current) => current + 1);
369
+ };
370
+ const onParticipantsChanged = () => {
371
+ void refreshConversations();
372
+ };
373
+ const onPaymentChanged = () => {
374
+ setPaymentRevision((current) => current + 1);
375
+ void refreshConversations();
376
+ };
377
+ socket.on("connect", onConnect);
378
+ socket.on("disconnect", onDisconnect);
379
+ socket.on("message:new", onNewMessage);
380
+ socket.on("message:delivered", onDelivered);
381
+ socket.on("message:updated", onUpdatedMessage);
382
+ socket.on("message:read", onRead);
383
+ socket.on("typing:start", onTypingStart);
384
+ socket.on("typing:stop", onTypingStop);
385
+ socket.on("presence:online", onPresenceOnline);
386
+ socket.on("presence:offline", onPresenceOffline);
387
+ socket.on("notification", onNotification);
388
+ socket.on("conversation:preference.updated", onPreferenceUpdated);
389
+ socket.on("message:pinned", onConversationMetadataChanged);
390
+ socket.on("message:unpinned", onConversationMetadataChanged);
391
+ socket.on("conversation:event.created", onConversationEventChanged);
392
+ socket.on("conversation:event.updated", onConversationEventChanged);
393
+ socket.on("conversation:event.deleted", onConversationEventChanged);
394
+ socket.on("conversation:event.rsvp", onConversationEventChanged);
395
+ socket.on("conversation:participants.updated", onParticipantsChanged);
396
+ socket.on("conversation:payment.updated", onPaymentChanged);
397
+ if (!socket.connected)
398
+ socket.connect();
399
+ else
400
+ onConnect();
294
401
  return () => {
295
402
  socket.off("connect", onConnect);
296
403
  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.
404
+ socket.off("message:new", onNewMessage);
405
+ socket.off("message:delivered", onDelivered);
406
+ socket.off("message:updated", onUpdatedMessage);
407
+ socket.off("message:read", onRead);
408
+ socket.off("typing:start", onTypingStart);
409
+ socket.off("typing:stop", onTypingStop);
410
+ socket.off("presence:online", onPresenceOnline);
411
+ socket.off("presence:offline", onPresenceOffline);
412
+ socket.off("notification", onNotification);
413
+ socket.off("conversation:preference.updated", onPreferenceUpdated);
414
+ socket.off("message:pinned", onConversationMetadataChanged);
415
+ socket.off("message:unpinned", onConversationMetadataChanged);
416
+ socket.off("conversation:event.created", onConversationEventChanged);
417
+ socket.off("conversation:event.updated", onConversationEventChanged);
418
+ socket.off("conversation:event.deleted", onConversationEventChanged);
419
+ socket.off("conversation:event.rsvp", onConversationEventChanged);
420
+ socket.off("conversation:participants.updated", onParticipantsChanged);
421
+ socket.off("conversation:payment.updated", onPaymentChanged);
314
422
  };
315
- }, [user, refreshConversations]);
423
+ }, [accountType, actorId, actorModel, refreshConversations]);
316
424
  return (_jsx(ChatContext.Provider, { value: {
317
425
  socket: socketRef.current,
318
426
  isConnected,
@@ -331,8 +439,11 @@ export function ChatProvider({ children }) {
331
439
  markRead,
332
440
  createPoll,
333
441
  votePoll,
442
+ updatePoll,
334
443
  unreadNotificationCount,
335
444
  notifications,
336
445
  clearNotifications,
446
+ eventRevision,
447
+ paymentRevision,
337
448
  }, children: children }));
338
449
  }