@messenger-box/platform-mobile 10.0.3-alpha.34 → 10.0.3-alpha.37

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 (34) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/lib/screens/inbox/components/CachedImage/index.js +125 -93
  3. package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
  4. package/lib/screens/inbox/components/DialogsListItem.js +80 -256
  5. package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
  6. package/lib/screens/inbox/components/ServiceDialogsListItem.js +222 -324
  7. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +1 -1
  8. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +0 -2
  9. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
  10. package/lib/screens/inbox/containers/ConversationView.js +487 -888
  11. package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
  12. package/lib/screens/inbox/containers/Dialogs.js +243 -547
  13. package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
  14. package/lib/screens/inbox/containers/ThreadConversationView.js +409 -1364
  15. package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
  16. package/package.json +4 -4
  17. package/src/screens/inbox/components/CachedImage/index.tsx +191 -140
  18. package/src/screens/inbox/components/DialogsListItem.tsx +112 -345
  19. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +316 -437
  20. package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +2 -4
  21. package/src/screens/inbox/containers/ConversationView.tsx +676 -993
  22. package/src/screens/inbox/containers/ConversationView.tsx.bk +1467 -0
  23. package/src/screens/inbox/containers/Dialogs.tsx +345 -636
  24. package/src/screens/inbox/containers/ThreadConversationView.tsx +661 -1887
  25. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js +0 -175
  26. package/lib/screens/inbox/components/workflow/dialogs-list-item-xstate.js.map +0 -1
  27. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js +0 -191
  28. package/lib/screens/inbox/components/workflow/service-dialogs-list-item-xstate.js.map +0 -1
  29. package/lib/screens/inbox/containers/workflow/conversation-xstate.js +0 -380
  30. package/lib/screens/inbox/containers/workflow/conversation-xstate.js.map +0 -1
  31. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js +0 -211
  32. package/lib/screens/inbox/containers/workflow/dialogs-xstate.js.map +0 -1
  33. package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js +0 -438
  34. package/lib/screens/inbox/containers/workflow/thread-conversation-xstate.js.map +0 -1
@@ -24,7 +24,6 @@ import {
24
24
  } from 'common/graphql';
25
25
  import { startCase } from 'lodash-es';
26
26
  import colors from 'tailwindcss/colors';
27
- import { serviceDialogsListItemXstate, Actions, BaseState } from './workflow/service-dialogs-list-item-xstate';
28
27
 
29
28
  // Helper function to safely create a Date object
30
29
  const safeDate = (dateValue) => {
@@ -68,292 +67,153 @@ export interface IDialogListItemProps {
68
67
  role: any;
69
68
  }
70
69
 
71
- // Create a safer version of useMachine to handle potential errors
72
- function useSafeMachine(machine) {
73
- // Define the state type
74
- interface SafeStateType {
75
- context: {
76
- channelId: string | null;
77
- currentUser: any;
78
- threadMessages: any[];
79
- loading: boolean;
80
- error: string | null;
81
- title: string;
82
- role: string;
83
- servicePostParentId: string | null;
84
- lastMessage: any;
85
- };
86
- value: string;
87
- matches?: (stateValue: string) => boolean;
88
- }
89
-
90
- // Initialize state with default values to prevent undefined errors
91
- const [state, setState] = useState<SafeStateType>({
92
- context: {
93
- channelId: null,
94
- currentUser: null,
95
- threadMessages: [],
96
- loading: false,
97
- error: null,
98
- title: '',
99
- role: '',
100
- servicePostParentId: null,
101
- lastMessage: null,
102
- },
103
- value: BaseState.Idle,
104
- matches: (stateValue) => stateValue === BaseState.Idle,
105
- });
106
-
107
- // Implement a send function that updates state safely
108
- const send = useCallback((event) => {
109
- try {
110
- // Handle specific events manually
111
- if (event.type === Actions.INITIAL_CONTEXT) {
112
- setState((prev) => ({
113
- ...prev,
114
- context: {
115
- ...prev.context,
116
- channelId: event.data?.channelId || null,
117
- currentUser: event.data?.currentUser || null,
118
- role: event.data?.role || null,
119
- loading: true,
120
- },
121
- value: BaseState.FetchingThreadMessages,
122
- }));
123
- } else if (event.type === Actions.UPDATE_THREAD_MESSAGES) {
124
- setState((prev) => {
125
- if (event.data?.threadMessages) {
126
- // Handling bulk messages (initial load)
127
- // If a direct last message was provided (from subscription), use it
128
- if (event.data?.directLastMessage) {
129
- console.log('Using direct last message from event:', {
130
- id: event.data.directLastMessage.id,
131
- message: event.data.directLastMessage.message?.substring(0, 20) + '...',
132
- });
133
-
134
- return {
135
- ...prev,
136
- context: {
137
- ...prev.context,
138
- threadMessages: event.data.threadMessages,
139
- lastMessage: event.data.directLastMessage,
140
- loading: false,
141
- },
142
- };
143
- }
70
+ // Helper function to compute the last message from a list of messages
71
+ const computeLastMessage = (threadMessages) => {
72
+ if (!threadMessages || !threadMessages.length) return null;
144
73
 
145
- return {
146
- ...prev,
147
- context: {
148
- ...prev.context,
149
- threadMessages: event.data.threadMessages,
150
- lastMessage: computeLastMessage(event.data.threadMessages),
151
- loading: false,
152
- },
153
- };
154
- } else if (event.data?.message) {
155
- // Handling single message (from subscription)
156
- const newMessage = event.data.message;
157
- const updatedMessages = [...prev.context.threadMessages, newMessage];
158
-
159
- // Use the new message directly as the last message
160
- // This ensures immediate update on subscription
161
- console.log('Setting new message as lastMessage from subscription:', {
162
- id: newMessage.id,
163
- message: newMessage.message?.substring(0, 20) + '...',
164
- date: newMessage.createdAt || newMessage.updatedAt,
165
- });
166
-
167
- return {
168
- ...prev,
169
- context: {
170
- ...prev.context,
171
- threadMessages: updatedMessages,
172
- lastMessage: newMessage, // Use the new message directly
173
- loading: false,
174
- },
175
- };
176
- }
177
- return prev;
178
- });
179
- } else if (event.type === Actions.SET_TITLE) {
180
- setState((prev) => ({
181
- ...prev,
182
- context: {
183
- ...prev.context,
184
- title: event.data?.title || '',
185
- },
186
- }));
187
- } else if (event.type === Actions.SET_SERVICE_POST_PARENT_ID) {
188
- setState((prev) => ({
189
- ...prev,
190
- context: {
191
- ...prev.context,
192
- servicePostParentId: event.data?.servicePostParentId || null,
193
- },
194
- }));
195
- } else if (event.type === Actions.START_LOADING) {
196
- setState((prev) => ({
197
- ...prev,
198
- context: {
199
- ...prev.context,
200
- loading: true,
201
- },
202
- }));
203
- } else if (event.type === Actions.STOP_LOADING) {
204
- setState((prev) => ({
205
- ...prev,
206
- context: {
207
- ...prev.context,
208
- loading: false,
209
- },
210
- }));
211
- }
212
- } catch (error) {
213
- console.error('Error sending event to state machine:', error);
214
- }
215
- }, []);
74
+ // Extract the post and replies from thread messages
75
+ let posts = [];
76
+ let replies = [];
216
77
 
217
- // Helper function to compute the last message from a list of messages
218
- const computeLastMessage = (threadMessages) => {
219
- if (!threadMessages || !threadMessages.length) return null;
78
+ // Helper function to check if a message is an image or contains only image content
79
+ const isTextMessage = (msg) => {
80
+ if (!msg) return false;
220
81
 
221
- // Extract the post and replies from thread messages
222
- let posts = [];
223
- let replies = [];
82
+ // If message is empty or only contains image-specific markers, ignore it
83
+ if (!msg.message || msg.message === '') return false;
224
84
 
225
- // Helper function to check if a message is an image or contains only image content
226
- const isTextMessage = (msg) => {
227
- if (!msg) return false;
85
+ // Check if message looks like an image URL or reference
86
+ const isImageMessage =
87
+ msg.message.includes('<img') ||
88
+ msg.message.includes('[Image]') ||
89
+ msg.message.includes('![') ||
90
+ (/\.(jpeg|jpg|gif|png|bmp|webp)/i.test(msg.message) &&
91
+ (msg.message.includes('http') || msg.message.includes('/images/')));
92
+
93
+ // Return true only for text messages (not image messages)
94
+ return !isImageMessage;
95
+ };
228
96
 
229
- // If message is empty or only contains image-specific markers, ignore it
230
- if (!msg.message || msg.message === '') return false;
97
+ threadMessages.forEach((thread) => {
98
+ if (thread?.post && isTextMessage(thread.post)) {
99
+ posts.push(thread.post);
100
+ }
101
+ if (thread?.replies?.length) {
102
+ replies = [...replies, ...thread.replies.filter((r) => isTextMessage(r))];
103
+ }
104
+ });
231
105
 
232
- // Check if message looks like an image URL or reference
233
- const isImageMessage =
234
- msg.message.includes('<img') ||
235
- msg.message.includes('[Image]') ||
236
- msg.message.includes('![') ||
237
- (/\.(jpeg|jpg|gif|png|bmp|webp)/i.test(msg.message) &&
238
- (msg.message.includes('http') || msg.message.includes('/images/')));
239
-
240
- // Return true only for text messages (not image messages)
241
- return !isImageMessage;
242
- };
106
+ // Combine and sort all messages
107
+ const allMessages = [...posts, ...replies];
108
+ if (!allMessages.length) return null;
243
109
 
244
- threadMessages.forEach((thread) => {
245
- if (thread?.post && isTextMessage(thread.post)) {
246
- posts.push(thread.post);
247
- }
248
- if (thread?.replies?.length) {
249
- replies = [...replies, ...thread.replies.filter((r) => isTextMessage(r))];
250
- }
251
- });
110
+ // Return the most recent message - use safe date comparison
111
+ return allMessages.reduce((a, b) => {
112
+ const dateA = safeDate(a?.createdAt);
113
+ const dateB = safeDate(b?.createdAt);
252
114
 
253
- // Combine and sort all messages
254
- const allMessages = [...posts, ...replies];
255
- if (!allMessages.length) return null;
115
+ // If either date is invalid, prefer the message with valid date
116
+ if (!dateA) return b;
117
+ if (!dateB) return a;
256
118
 
257
- // Return the most recent message - use safe date comparison
258
- return allMessages.reduce((a, b) => {
259
- const dateA = safeDate(a?.createdAt);
260
- const dateB = safeDate(b?.createdAt);
119
+ return dateA > dateB ? a : b;
120
+ }, allMessages[0]);
121
+ };
261
122
 
262
- // If either date is invalid, prefer the message with valid date
263
- if (!dateA) return b;
264
- if (!dateB) return a;
123
+ const ServiceChannelWithLastMessage = React.memo(
124
+ ({ channel, lastMessage, subscribeToNewMessages, subscribeToChatMessages }: any) => {
125
+ React.useEffect(() => {
126
+ console.log(`Setting up subscriptions for channel ${channel?.id}`);
127
+ // Subscribe and store the unsubscribe functions
128
+ let threadUnsubscribe;
129
+ let chatUnsubscribe;
265
130
 
266
- return dateA > dateB ? a : b;
267
- }, allMessages[0]);
268
- };
131
+ try {
132
+ threadUnsubscribe = subscribeToNewMessages();
133
+ chatUnsubscribe = subscribeToChatMessages();
134
+ } catch (error) {
135
+ console.error(`Error setting up subscriptions for channel ${channel?.id}:`, error);
136
+ }
269
137
 
270
- // Add a matches function that works with the current state value
271
- const stateWithMatches = useMemo(() => {
272
- return {
273
- ...state,
274
- matches: (stateValue) => {
275
- try {
276
- return state.value === stateValue;
277
- } catch (error) {
278
- console.error(`Error in matches function:`, error);
279
- return false;
138
+ return () => {
139
+ // Cleanup subscriptions on unmount
140
+ if (threadUnsubscribe && typeof threadUnsubscribe === 'function') {
141
+ try {
142
+ console.log(`Cleaning up thread subscription for channel ${channel?.id}`);
143
+ threadUnsubscribe();
144
+ } catch (error) {
145
+ console.error(`Error cleaning up thread subscription for channel ${channel?.id}:`, error);
146
+ }
280
147
  }
281
- },
282
- };
283
- }, [state]);
284
148
 
285
- // Return as a tuple to match useMachine API
286
- return [stateWithMatches, send] as const;
287
- }
288
-
289
- const ServiceChannelWithLastMessage = React.memo(({ channel, lastMessage, subscribeToNewMessages }: any) => {
290
- React.useEffect(() => {
291
- // Subscribe and store the unsubscribe function
292
- const unsubscribe = subscribeToNewMessages();
293
- return () => {
294
- // Cleanup subscription on unmount
295
- if (unsubscribe && typeof unsubscribe === 'function') {
296
- unsubscribe();
297
- }
298
- };
299
- }, [subscribeToNewMessages]);
300
-
301
- // Debug output for component rendering
302
- React.useEffect(() => {
303
- console.log(`ServiceChannelWithLastMessage rendered for channel ${channel?.id}:`, {
304
- hasLastMessage: !!lastMessage,
305
- messageId: lastMessage?.id,
306
- messageText: lastMessage?.message?.substring(0, 20) + (lastMessage?.message?.length > 20 ? '...' : ''),
307
- date: lastMessage?.createdAt ? safeDate(lastMessage.createdAt)?.toISOString() : 'none',
308
- });
309
- }, [lastMessage, channel?.id]);
149
+ if (chatUnsubscribe && typeof chatUnsubscribe === 'function') {
150
+ try {
151
+ console.log(`Cleaning up chat subscription for channel ${channel?.id}`);
152
+ chatUnsubscribe();
153
+ } catch (error) {
154
+ console.error(`Error cleaning up chat subscription for channel ${channel?.id}:`, error);
155
+ }
156
+ }
157
+ };
158
+ }, [subscribeToNewMessages, subscribeToChatMessages, channel?.id]);
159
+
160
+ // Debug output for component rendering
161
+ React.useEffect(() => {
162
+ console.log(`ServiceChannelWithLastMessage rendered for channel ${channel?.id}:`, {
163
+ hasLastMessage: !!lastMessage,
164
+ messageId: lastMessage?.id,
165
+ messageText: lastMessage?.message?.substring(0, 20) + (lastMessage?.message?.length > 20 ? '...' : ''),
166
+ date: lastMessage?.createdAt ? safeDate(lastMessage.createdAt)?.toISOString() : 'none',
167
+ });
168
+ }, [lastMessage, channel?.id]);
310
169
 
311
- const count = 30;
312
- const title = channel?.title.slice(0, count) + (channel?.title.length > count ? '...' : '');
170
+ const count = 30;
171
+ const title = channel?.title.slice(0, count) + (channel?.title.length > count ? '...' : '');
313
172
 
314
- // Define message display text
315
- let displayMessage = 'No messages yet';
173
+ // Define message display text
174
+ let displayMessage = 'No messages yet';
316
175
 
317
- if (lastMessage) {
318
- // Check for file attachments
319
- const hasFileAttachments = lastMessage.files?.data?.length > 0;
176
+ if (lastMessage) {
177
+ // Check for file attachments
178
+ const hasFileAttachments = lastMessage.files?.data?.length > 0;
320
179
 
321
- // Check if the message appears to be an image
322
- const isImageMessage =
323
- lastMessage.message?.includes('<img') ||
324
- lastMessage.message?.includes('[Image]') ||
325
- lastMessage.message?.includes('![') ||
326
- (/\.(jpeg|jpg|gif|png|bmp|webp)/i.test(lastMessage.message || '') &&
327
- ((lastMessage.message || '').includes('http') || (lastMessage.message || '').includes('/images/')));
328
-
329
- if (hasFileAttachments) {
330
- displayMessage = '📎 File attachment';
331
- } else if (isImageMessage) {
332
- displayMessage = '[Image]';
333
- } else if (lastMessage.message && lastMessage.message.trim() !== '') {
334
- displayMessage = lastMessage.message;
335
- } else {
336
- displayMessage = '(Empty message)';
180
+ // Check if the message appears to be an image
181
+ const isImageMessage =
182
+ lastMessage.message?.includes('<img') ||
183
+ lastMessage.message?.includes('[Image]') ||
184
+ lastMessage.message?.includes('![') ||
185
+ (/\.(jpeg|jpg|gif|png|bmp|webp)/i.test(lastMessage.message || '') &&
186
+ ((lastMessage.message || '').includes('http') || (lastMessage.message || '').includes('/images/')));
187
+
188
+ if (hasFileAttachments) {
189
+ displayMessage = '📎 File attachment';
190
+ } else if (isImageMessage) {
191
+ displayMessage = '[Image]';
192
+ } else if (lastMessage.message && lastMessage.message.trim() !== '') {
193
+ displayMessage = lastMessage.message;
194
+ } else {
195
+ displayMessage = '(Empty message)';
196
+ }
337
197
  }
338
- }
339
198
 
340
- return (
341
- <HStack space={'sm'} className="flex-1 justify-between">
342
- <Box className="flex-[0.8]">
343
- <Text color={colors.gray[600]} className="text-base text-wrap flex-wrap font-semibold">
344
- {title}
345
- </Text>
346
- <Text color={colors.gray[600]} numberOfLines={1}>
347
- {displayMessage}
348
- </Text>
349
- </Box>
350
-
351
- <Box className="flex-[0.2]">
352
- <Text color={colors.gray[500]}>{lastMessage ? createdAtText(lastMessage?.createdAt) : ''}</Text>
353
- </Box>
354
- </HStack>
355
- );
356
- });
199
+ return (
200
+ <HStack space={'sm'} className="flex-1 justify-between">
201
+ <Box className="flex-[0.8]">
202
+ <Text color={colors.gray[600]} className="text-base text-wrap flex-wrap font-semibold">
203
+ {title}
204
+ </Text>
205
+ <Text color={colors.gray[600]} numberOfLines={1}>
206
+ {displayMessage}
207
+ </Text>
208
+ </Box>
209
+
210
+ <Box className="flex-[0.2]">
211
+ <Text color={colors.gray[500]}>{lastMessage ? createdAtText(lastMessage?.createdAt) : ''}</Text>
212
+ </Box>
213
+ </HStack>
214
+ );
215
+ },
216
+ );
357
217
 
358
218
  /**
359
219
  * TODO:
@@ -371,35 +231,11 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
371
231
  }) {
372
232
  // Create a ref to track if component is mounted
373
233
  const isMountedRef = useRef(true);
374
- // Define safe access functions
375
- const [state, send] = useSafeMachine(serviceDialogsListItemXstate);
376
-
377
- const safeContextProperty = useCallback(
378
- (property, defaultValue = null) => {
379
- try {
380
- return state?.context?.[property] ?? defaultValue;
381
- } catch (error) {
382
- console.error(`Error accessing state.context.${property}:`, error);
383
- return defaultValue;
384
- }
385
- },
386
- [state],
387
- );
388
-
389
- const safeSend = useCallback(
390
- (event) => {
391
- try {
392
- send(event);
393
- } catch (error) {
394
- console.error('Error sending event to state machine:', error, event);
395
- }
396
- },
397
- [send],
398
- );
234
+ const [subscriptionsTimestamp, setSubscriptionsTimestamp] = useState(Date.now());
399
235
 
400
236
  // Query for thread messages
401
237
  const {
402
- data: threadMessages,
238
+ data: threadMessagesData,
403
239
  loading: threadMessageLoading,
404
240
  error,
405
241
  refetch: refetchThreadMessages,
@@ -422,66 +258,28 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
422
258
  },
423
259
  });
424
260
 
425
- // Update state when channel or currentUser changes
426
- React.useEffect(() => {
427
- if (channel?.id) {
428
- safeSend({
429
- type: Actions.INITIAL_CONTEXT,
430
- data: {
431
- channelId: channel?.id?.toString(),
432
- currentUser,
433
- role,
434
- },
435
- });
261
+ // Extract threadMessages data for easier access
262
+ const threadMessages = threadMessagesData?.threadMessages?.data || [];
436
263
 
437
- safeSend({
438
- type: Actions.SET_TITLE,
439
- data: {
440
- title: channel?.title || '',
441
- },
442
- });
443
- }
264
+ // Calculate lastMessage directly from threadMessages
265
+ const lastMessage = useMemo(() => {
266
+ return computeLastMessage(threadMessages);
267
+ }, [threadMessages]);
444
268
 
269
+ // Calculate servicePostParentId based on lastMessage
270
+ const servicePostParentId = useMemo(() => {
271
+ if (!lastMessage) return null;
272
+ return lastMessage?.parentId ? lastMessage?.parentId : lastMessage?.id;
273
+ }, [lastMessage]);
274
+
275
+ // Handle component unmount
276
+ React.useEffect(() => {
445
277
  return () => {
446
278
  isMountedRef.current = false;
447
279
  };
448
- }, [channel?.id, currentUser, role, safeSend]);
449
-
450
- // Update state when thread messages are loaded or refreshed
451
- React.useEffect(() => {
452
- if (threadMessages && isMountedRef.current) {
453
- console.log(
454
- `Updating thread messages for channel ${channel?.id}, count:`,
455
- threadMessages?.threadMessages?.data?.length || 0,
456
- );
457
-
458
- safeSend({
459
- type: Actions.UPDATE_THREAD_MESSAGES,
460
- data: {
461
- threadMessages: threadMessages?.threadMessages?.data || [],
462
- },
463
- });
464
- }
465
- }, [threadMessages, refreshing, channel?.id, safeSend]);
466
-
467
- // Use computed values from state
468
- const lastMessage = safeContextProperty('lastMessage', null);
469
- const servicePostParentId = safeContextProperty('servicePostParentId', null);
470
-
471
- // Update servicePostParentId when lastMessage changes
472
- React.useEffect(() => {
473
- if (lastMessage) {
474
- const sParentId = lastMessage?.parentId ? lastMessage?.parentId : lastMessage?.id;
475
- safeSend({
476
- type: Actions.SET_SERVICE_POST_PARENT_ID,
477
- data: {
478
- servicePostParentId: sParentId,
479
- },
480
- });
481
- }
482
- }, [lastMessage, safeSend]);
280
+ }, []);
483
281
 
484
- const creatorAndMembersId = React.useMemo(() => {
282
+ const creatorAndMembersId = useMemo(() => {
485
283
  if (!channel?.members) return null;
486
284
  const membersIds: any =
487
285
  channel?.members
@@ -492,7 +290,7 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
492
290
  return mergedIds?.filter((m: any, pos: any) => mergedIds?.indexOf(m) === pos) ?? [];
493
291
  }, [channel, currentUser]);
494
292
 
495
- const postParentId = React.useMemo(() => {
293
+ const postParentId = useMemo(() => {
496
294
  if (!creatorAndMembersId?.length) return null;
497
295
 
498
296
  return creatorAndMembersId?.length && creatorAndMembersId?.includes(currentUser?.id)
@@ -506,76 +304,40 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
506
304
  const refreshThreadState = useCallback(() => {
507
305
  if (channel?.id && isMountedRef.current) {
508
306
  console.log('Forcing thread state refresh for channel:', channel?.id);
509
- // First ensure we're in a loading state to trigger UI updates
510
- safeSend({ type: Actions.START_LOADING });
511
307
 
512
308
  // Refetch messages from server
513
309
  refetchThreadMessages({
514
310
  channelId: channel?.id?.toString(),
515
311
  role,
516
312
  limit: 2,
517
- })
518
- .then((result) => {
519
- // Update the state with fresh data
520
- if (result.data?.threadMessages?.data && isMountedRef.current) {
521
- console.log(
522
- `Refreshed ${result.data.threadMessages.data.length} thread messages for channel ${channel?.id}`,
523
- );
524
-
525
- safeSend({
526
- type: Actions.UPDATE_THREAD_MESSAGES,
527
- data: { threadMessages: result.data.threadMessages.data },
528
- });
529
- }
530
- safeSend({ type: Actions.STOP_LOADING });
531
- })
532
- .catch((err) => {
533
- console.error('Error refreshing thread state:', err);
534
- safeSend({ type: Actions.STOP_LOADING });
535
- });
313
+ }).catch((err) => {
314
+ console.error('Error refreshing thread state:', err);
315
+ });
536
316
  }
537
- }, [channel?.id, refetchThreadMessages, safeSend, isMountedRef, role]);
317
+ }, [channel?.id, refetchThreadMessages, isMountedRef, role]);
538
318
 
539
- // Refetch messages when screen is focused or refreshing
319
+ // Reset subscriptions when the component gains focus
540
320
  useFocusEffect(
541
321
  React.useCallback(() => {
322
+ // Force subscription refresh
323
+ setSubscriptionsTimestamp(Date.now());
324
+
325
+ // Existing focus effect logic...
542
326
  if (!channel?.id) return;
543
327
 
544
328
  console.log('ServiceDialogsListItem focused for channel:', channel?.id);
545
329
 
546
- // Show loading state
547
- safeSend({ type: Actions.START_LOADING });
548
-
549
330
  // Use a direct refetch with network-only policy to force fresh data
550
331
  const fetchFreshData = async () => {
551
332
  try {
552
333
  // Force a network-only fetch
553
- const result = await refetchThreadMessages({
334
+ await refetchThreadMessages({
554
335
  channelId: channel?.id?.toString(),
555
336
  role,
556
337
  limit: 2,
557
338
  });
558
-
559
- // Log the refreshed data
560
- console.log(
561
- `FOCUS EFFECT: Refetched ${
562
- result?.data?.threadMessages?.data?.length || 0
563
- } messages for channel ${channel?.id}`,
564
- );
565
-
566
- if (result?.data?.threadMessages?.data && isMountedRef.current) {
567
- // Update state with fresh data
568
- safeSend({
569
- type: Actions.UPDATE_THREAD_MESSAGES,
570
- data: { threadMessages: result.data.threadMessages.data },
571
- });
572
- }
573
339
  } catch (error) {
574
340
  console.error('Error refetching thread messages on focus:', error);
575
- } finally {
576
- if (isMountedRef.current) {
577
- safeSend({ type: Actions.STOP_LOADING });
578
- }
579
341
  }
580
342
  };
581
343
 
@@ -585,12 +347,13 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
585
347
  return () => {
586
348
  // Cleanup function when unfocused
587
349
  };
588
- }, [refreshing, channel?.id, refetchThreadMessages, safeSend, role, isMountedRef]),
350
+ }, [refreshing, channel?.id, refetchThreadMessages, role]),
589
351
  );
590
352
 
591
353
  return (
592
354
  <Pressable
593
- onPress={() => channel?.id !== selectedChannelId && onOpen(channel?.id, channel?.title, postParentId)}
355
+ // onPress={() => channel?.id !== selectedChannelId && onOpen(channel?.id, channel?.title, postParentId)}
356
+ onPress={() => onOpen(channel?.id, channel?.title, postParentId)}
594
357
  className="flex-1 rounded-md border-gray-200 dark:border-gray-600 dark:bg-gray-700"
595
358
  style={{ borderBottomWidth: 1, borderColor: '#e5e7eb', marginVertical: 0 }}
596
359
  >
@@ -613,6 +376,7 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
613
376
  </Box>
614
377
  <Box className="flex-1">
615
378
  <ServiceChannelWithLastMessage
379
+ key={`channel-${channel?.id}-${subscriptionsTimestamp}`}
616
380
  channel={channel}
617
381
  lastMessage={lastMessage}
618
382
  subscribeToNewMessages={() =>
@@ -620,52 +384,167 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
620
384
  document: THREAD_CHAT_ADDED,
621
385
  variables: {
622
386
  channelId: channel?.id?.toString(),
623
- postParentId: postParentId ? servicePostParentId : null,
387
+ postParentId: postParentId || servicePostParentId || null,
624
388
  },
625
389
  updateQuery: (prev, { subscriptionData }: any) => {
626
- if (!subscriptionData.data) return prev;
627
-
628
- const newPostThreadData: any = subscriptionData?.data?.threadCreatedUpdated?.data;
629
- const newMessage: any = subscriptionData?.data?.threadCreatedUpdated?.lastMessage;
630
-
631
- console.log('New thread message subscription update:', {
632
- channelId: channel?.id,
633
- threadId: newPostThreadData?.id,
634
- hasMessage: !!newMessage,
635
- message: newMessage?.message?.substring(0, 20) + '...',
636
- });
637
-
638
- const data =
639
- prev?.threadMessages?.data?.map((t: any) =>
640
- t.id === newPostThreadData?.id
641
- ? {
642
- ...t,
643
- replies: [...t?.replies, newMessage],
644
- replyCount: newPostThreadData?.replyCount,
645
- lastReplyAt: newPostThreadData?.lastReplyAt,
646
- updatedAt: newPostThreadData?.updatedAt,
647
- }
648
- : t,
649
- ) ?? [];
650
-
651
- // Use the safe state update function
652
- if (isMountedRef.current) {
653
- safeSend({
654
- type: Actions.UPDATE_THREAD_MESSAGES,
655
- data: {
656
- threadMessages: data?.length > 0 ? data : [newPostThreadData],
657
- directLastMessage: newMessage, // Directly set the new message
658
- },
390
+ if (!subscriptionData?.data) {
391
+ console.log(`No subscription data for channel ${channel?.id}`);
392
+ return prev;
393
+ }
394
+
395
+ try {
396
+ const newPostThreadData: any =
397
+ subscriptionData?.data?.threadCreatedUpdated?.data;
398
+ const newMessage: any =
399
+ subscriptionData?.data?.threadCreatedUpdated?.lastMessage;
400
+
401
+ if (!newPostThreadData || !newMessage) {
402
+ console.log(`Missing data in subscription for channel ${channel?.id}`);
403
+ return prev;
404
+ }
405
+
406
+ console.log('New thread message subscription update:', {
407
+ channelId: channel?.id,
408
+ threadId: newPostThreadData?.id,
409
+ hasMessage: !!newMessage,
410
+ message: newMessage?.message?.substring(0, 20) + '...',
659
411
  });
412
+
413
+ // Create a safe copy of the previous data
414
+ const prevThreads = prev?.threadMessages?.data || [];
415
+
416
+ // Check if this thread already exists
417
+ const threadExists = prevThreads.some(
418
+ (t: any) => t.id === newPostThreadData?.id,
419
+ );
420
+
421
+ let updatedThreads;
422
+ if (threadExists) {
423
+ // Update existing thread
424
+ updatedThreads = prevThreads.map((t: any) =>
425
+ t.id === newPostThreadData?.id
426
+ ? {
427
+ ...t,
428
+ replies: [...(t?.replies || []), newMessage],
429
+ replyCount: newPostThreadData?.replyCount,
430
+ lastReplyAt: newPostThreadData?.lastReplyAt,
431
+ updatedAt: newPostThreadData?.updatedAt,
432
+ }
433
+ : t,
434
+ );
435
+ } else {
436
+ // Add new thread
437
+ updatedThreads = [...prevThreads, newPostThreadData];
438
+ }
439
+
440
+ return {
441
+ ...prev,
442
+ threadMessages: {
443
+ ...prev?.threadMessages,
444
+ totalCount:
445
+ newPostThreadData?.totalCount ??
446
+ prev?.threadMessages?.totalCount ??
447
+ 0,
448
+ data: updatedThreads,
449
+ },
450
+ };
451
+ } catch (error) {
452
+ console.error(
453
+ `Error processing subscription data for channel ${channel?.id}:`,
454
+ error,
455
+ );
456
+ return prev;
457
+ }
458
+ },
459
+ onError: (error) => {
460
+ console.error(`Thread subscription error for channel ${channel?.id}:`, error);
461
+ },
462
+ })
463
+ }
464
+ subscribeToChatMessages={() =>
465
+ subscribeToMore({
466
+ document: CHAT_MESSAGE_ADDED,
467
+ variables: {
468
+ channelId: channel?.id?.toString(),
469
+ postParentId: postParentId || servicePostParentId || null,
470
+ },
471
+ updateQuery: (prev, { subscriptionData }: any) => {
472
+ if (!subscriptionData?.data) {
473
+ console.log(`No chat message subscription data for channel ${channel?.id}`);
474
+ return prev;
660
475
  }
661
476
 
662
- return Object.assign({}, prev, {
663
- threadMessages: {
664
- ...prev?.threadMessages,
665
- totalCount: newPostThreadData?.totalCount ?? 0,
666
- data: data?.length > 0 ? data : [newPostThreadData],
667
- },
668
- });
477
+ try {
478
+ const newMessage = subscriptionData?.data?.chatMessageAdded;
479
+
480
+ if (!newMessage) {
481
+ console.log(
482
+ `Missing chat message data in subscription for channel ${channel?.id}`,
483
+ );
484
+ return prev;
485
+ }
486
+
487
+ console.log('New chat message subscription update:', {
488
+ channelId: channel?.id,
489
+ messageId: newMessage?.id,
490
+ message: newMessage?.message?.substring(0, 20) + '...',
491
+ });
492
+
493
+ // Find the thread this message belongs to
494
+ const prevThreads = prev?.threadMessages?.data || [];
495
+ const threadIndex = prevThreads.findIndex((t: any) => {
496
+ // Check if this message belongs to this thread
497
+ if (newMessage.parentId && t.post?.id === newMessage.parentId) {
498
+ return true;
499
+ }
500
+ // Check replies
501
+ if (t.replies && t.replies.some((r: any) => r.id === newMessage.parentId)) {
502
+ return true;
503
+ }
504
+ return false;
505
+ });
506
+
507
+ if (threadIndex === -1) {
508
+ console.log(
509
+ `Cannot find thread for chat message in channel ${channel?.id}`,
510
+ );
511
+ return prev;
512
+ }
513
+
514
+ // Update the thread with the new message
515
+ const updatedThreads = [...prevThreads];
516
+ const thread = { ...updatedThreads[threadIndex] };
517
+
518
+ // Add message to replies
519
+ thread.replies = [...(thread.replies || []), newMessage];
520
+
521
+ // Fix for arithmetic operation type error
522
+ const currentReplyCount =
523
+ typeof thread.replyCount === 'number' ? thread.replyCount : 0;
524
+ thread.replyCount = currentReplyCount + 1;
525
+
526
+ thread.lastReplyAt = newMessage.createdAt;
527
+ thread.updatedAt = newMessage.createdAt;
528
+
529
+ updatedThreads[threadIndex] = thread;
530
+
531
+ return {
532
+ ...prev,
533
+ threadMessages: {
534
+ ...prev?.threadMessages,
535
+ data: updatedThreads,
536
+ },
537
+ };
538
+ } catch (error) {
539
+ console.error(
540
+ `Error processing chat message subscription for channel ${channel?.id}:`,
541
+ error,
542
+ );
543
+ return prev;
544
+ }
545
+ },
546
+ onError: (error) => {
547
+ console.error(`Chat message subscription error for channel ${channel?.id}:`, error);
669
548
  },
670
549
  })
671
550
  }