@messenger-box/platform-mobile 10.0.3-alpha.5 → 10.0.3-alpha.54

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 (98) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/lib/compute.js +2 -3
  3. package/lib/compute.js.map +1 -1
  4. package/lib/index.js.map +1 -1
  5. package/lib/queries/inboxQueries.js +65 -0
  6. package/lib/queries/inboxQueries.js.map +1 -0
  7. package/lib/routes.json +2 -3
  8. package/lib/screens/inbox/DialogMessages.js +1 -1
  9. package/lib/screens/inbox/DialogMessages.js.map +1 -1
  10. package/lib/screens/inbox/DialogThreadMessages.js +4 -8
  11. package/lib/screens/inbox/DialogThreadMessages.js.map +1 -1
  12. package/lib/screens/inbox/DialogThreads.js +57 -12
  13. package/lib/screens/inbox/DialogThreads.js.map +1 -1
  14. package/lib/screens/inbox/Inbox.js +1 -1
  15. package/lib/screens/inbox/Inbox.js.map +1 -1
  16. package/lib/screens/inbox/components/CachedImage/consts.js +1 -1
  17. package/lib/screens/inbox/components/CachedImage/consts.js.map +1 -1
  18. package/lib/screens/inbox/components/CachedImage/index.js +168 -46
  19. package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
  20. package/lib/screens/inbox/components/DialogItem.js +169 -0
  21. package/lib/screens/inbox/components/DialogItem.js.map +1 -0
  22. package/lib/screens/inbox/components/GiftedChatInboxComponent.js +313 -0
  23. package/lib/screens/inbox/components/GiftedChatInboxComponent.js.map +1 -0
  24. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +147 -31
  25. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
  26. package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js +6 -1
  27. package/lib/screens/inbox/components/SlackMessageContainer/SlackMessage.js.map +1 -1
  28. package/lib/screens/inbox/components/SubscriptionHandler.js +24 -0
  29. package/lib/screens/inbox/components/SubscriptionHandler.js.map +1 -0
  30. package/lib/screens/inbox/components/ThreadsViewItem.js +66 -55
  31. package/lib/screens/inbox/components/ThreadsViewItem.js.map +1 -1
  32. package/lib/screens/inbox/config/config.js +2 -2
  33. package/lib/screens/inbox/config/config.js.map +1 -1
  34. package/lib/screens/inbox/containers/ConversationView.js +1111 -434
  35. package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
  36. package/lib/screens/inbox/containers/Dialogs.js +193 -80
  37. package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
  38. package/lib/screens/inbox/containers/ThreadConversationView.js +725 -216
  39. package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
  40. package/lib/screens/inbox/containers/ThreadsView.js +83 -50
  41. package/lib/screens/inbox/containers/ThreadsView.js.map +1 -1
  42. package/lib/screens/inbox/hooks/useInboxMessages.js +31 -0
  43. package/lib/screens/inbox/hooks/useInboxMessages.js.map +1 -0
  44. package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js +108 -0
  45. package/lib/screens/inbox/hooks/useSafeDialogThreadsMachine.js.map +1 -0
  46. package/lib/screens/inbox/workflow/dialog-threads-xstate.js +151 -0
  47. package/lib/screens/inbox/workflow/dialog-threads-xstate.js.map +1 -0
  48. package/package.json +4 -4
  49. package/src/compute.ts +5 -6
  50. package/src/index.ts +2 -0
  51. package/src/navigation/InboxNavigation.tsx +3 -3
  52. package/src/queries/inboxQueries.ts +299 -0
  53. package/src/queries/index.d.ts +2 -0
  54. package/src/queries/index.ts +1 -0
  55. package/src/screens/inbox/DialogMessages.tsx +1 -1
  56. package/src/screens/inbox/DialogThreadMessages.tsx +7 -14
  57. package/src/screens/inbox/DialogThreads.tsx +55 -61
  58. package/src/screens/inbox/Inbox.tsx +1 -1
  59. package/src/screens/inbox/components/Actionsheet.tsx +30 -0
  60. package/src/screens/inbox/components/CachedImage/consts.ts +4 -3
  61. package/src/screens/inbox/components/CachedImage/index.tsx +232 -61
  62. package/src/screens/inbox/components/DialogItem.tsx +306 -0
  63. package/src/screens/inbox/components/DialogsHeader.tsx +6 -13
  64. package/src/screens/inbox/components/DialogsListItem.tsx +262 -198
  65. package/src/screens/inbox/components/ExpandableInput.tsx +460 -0
  66. package/src/screens/inbox/components/ExpandableInputActionSheet.tsx +518 -0
  67. package/src/screens/inbox/components/GiftedChatInboxComponent.tsx +411 -0
  68. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +337 -194
  69. package/src/screens/inbox/components/SlackInput.tsx +23 -0
  70. package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +233 -23
  71. package/src/screens/inbox/components/SlackMessageContainer/SlackMessage.tsx +1 -1
  72. package/src/screens/inbox/components/SmartLoader.tsx +61 -0
  73. package/src/screens/inbox/components/SubscriptionHandler.tsx +41 -0
  74. package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +53 -55
  75. package/src/screens/inbox/components/ThreadsViewItem.tsx +178 -285
  76. package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +145 -0
  77. package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +159 -0
  78. package/src/screens/inbox/config/config.ts +2 -2
  79. package/src/screens/inbox/containers/ConversationView.tsx +1843 -702
  80. package/src/screens/inbox/containers/ConversationView.tsx.bk +1467 -0
  81. package/src/screens/inbox/containers/Dialogs.tsx +402 -204
  82. package/src/screens/inbox/containers/SupportServiceDialogs.tsx +4 -4
  83. package/src/screens/inbox/containers/ThreadConversationView.tsx +1350 -319
  84. package/src/screens/inbox/containers/ThreadsView.tsx +105 -193
  85. package/src/screens/inbox/containers/workflow/apollo/handleResult.ts +20 -0
  86. package/src/screens/inbox/containers/workflow/conversation-xstate.ts +313 -0
  87. package/src/screens/inbox/containers/workflow/dialogs-xstate.ts +196 -0
  88. package/src/screens/inbox/containers/workflow/thread-conversation-xstate.ts +401 -0
  89. package/src/screens/inbox/hooks/useInboxMessages.ts +34 -0
  90. package/src/screens/inbox/hooks/useSafeDialogThreadsMachine.ts +136 -0
  91. package/src/screens/inbox/index.ts +37 -0
  92. package/src/screens/inbox/machines/threadsMachine.ts +147 -0
  93. package/src/screens/inbox/workflow/dialog-threads-xstate.ts +163 -0
  94. package/tsconfig.json +11 -54
  95. package/lib/screens/inbox/components/DialogsListItem.js +0 -171
  96. package/lib/screens/inbox/components/DialogsListItem.js.map +0 -1
  97. package/lib/screens/inbox/components/ServiceDialogsListItem.js +0 -171
  98. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +0 -1
@@ -1,35 +1,47 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useMemo, useState, useCallback, useRef } from 'react';
2
2
  import {
3
3
  Text,
4
- Image,
5
4
  Pressable,
6
5
  HStack,
7
6
  Box,
8
- AvatarGroup,
9
7
  Avatar,
10
8
  AvatarFallbackText,
11
9
  AvatarImage,
12
- AvatarBadge,
13
- View,
14
10
  } from '@admin-layout/gluestack-ui-mobile';
15
11
  import { format, isToday, isYesterday } from 'date-fns';
16
12
  import { useFocusEffect } from '@react-navigation/native';
17
13
  import { IChannel, IUserAccount } from 'common';
18
- import {
19
- useOnThreadCreatedUpdatedSubscription,
20
- useOnThreadChatMessageAddedSubscription,
21
- useThreadMessagesQuery,
22
- OnThreadCreatedUpdatedDocument as THREAD_CHAT_ADDED,
23
- OnThreadChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
24
- } from 'common/lib/generated/generated.js';
14
+ import { THREAD_CREATED_UPDATED, CHAT_MESSAGE_ADDED, useThreadMessagesQuery } from '../../../queries/inboxQueries';
25
15
  import { startCase } from 'lodash-es';
16
+ import colors from 'tailwindcss/colors';
17
+ import { SubscriptionHandler } from './SubscriptionHandler';
18
+
19
+ // Helper function to safely create a Date object
20
+ const safeDate = (dateValue) => {
21
+ if (!dateValue) return null;
22
+
23
+ try {
24
+ const date = new Date(dateValue);
25
+ // Check if the date is valid by seeing if it returns a valid time value
26
+ if (isNaN(date.getTime())) {
27
+ console.warn('Invalid date value detected:', dateValue);
28
+ return null;
29
+ }
30
+ return date;
31
+ } catch (error) {
32
+ console.warn('Error creating date from value:', dateValue, error);
33
+ return null;
34
+ }
35
+ };
26
36
 
27
37
  const createdAtText = (value: string) => {
28
38
  if (!value) return '';
29
- let date = new Date(value);
39
+ const date = safeDate(value);
40
+ if (!date) return '';
41
+
30
42
  if (isToday(date)) return 'Today';
31
43
  if (isYesterday(date)) return 'Yesterday';
32
- return format(new Date(value), 'MMM dd, yyyy');
44
+ return format(date, 'MMM dd, yyyy');
33
45
  };
34
46
 
35
47
  export interface IDialogListChannel extends IChannel {
@@ -44,8 +56,120 @@ export interface IDialogListItemProps {
44
56
  onOpen: (id: any, title: any, postParentId?: any) => void;
45
57
  refreshing: boolean;
46
58
  role: any;
59
+ visible?: boolean;
47
60
  }
48
61
 
62
+ // Helper function to compute the last message from a list of messages
63
+ const computeLastMessage = (threadMessages) => {
64
+ if (!threadMessages || !threadMessages.length) return null;
65
+
66
+ // Extract the post and replies from thread messages
67
+ let posts = [];
68
+ let replies = [];
69
+
70
+ // Helper function to check if a message is an image or contains only image content
71
+ const isTextMessage = (msg) => {
72
+ if (!msg) return false;
73
+
74
+ // If message is empty or only contains image-specific markers, ignore it
75
+ if (!msg.message || msg.message === '') return false;
76
+
77
+ // Check if message looks like an image URL or reference
78
+ const isImageMessage =
79
+ msg.message.includes('<img') ||
80
+ msg.message.includes('[Image]') ||
81
+ msg.message.includes('![') ||
82
+ (/\.(jpeg|jpg|gif|png|bmp|webp)/i.test(msg.message) &&
83
+ (msg.message.includes('http') || msg.message.includes('/images/')));
84
+
85
+ // Return true only for text messages (not image messages)
86
+ return !isImageMessage;
87
+ };
88
+
89
+ threadMessages.forEach((thread) => {
90
+ if (thread?.post && isTextMessage(thread.post)) {
91
+ posts.push(thread.post);
92
+ }
93
+ if (thread?.replies?.length) {
94
+ replies = [...replies, ...thread.replies.filter((r) => isTextMessage(r))];
95
+ }
96
+ });
97
+
98
+ // Combine and sort all messages
99
+ const allMessages = [...posts, ...replies];
100
+ if (!allMessages.length) return null;
101
+
102
+ // Return the most recent message - use safe date comparison
103
+ return allMessages.reduce((a, b) => {
104
+ const dateA = safeDate(a?.createdAt);
105
+ const dateB = safeDate(b?.createdAt);
106
+
107
+ // If either date is invalid, prefer the message with valid date
108
+ if (!dateA) return b;
109
+ if (!dateB) return a;
110
+
111
+ return dateA > dateB ? a : b;
112
+ }, allMessages[0]);
113
+ };
114
+
115
+ const ServiceChannelWithLastMessage = React.memo(({ channel, lastMessage }: { channel: any; lastMessage: any }) => {
116
+ // Debug output for component rendering
117
+ React.useEffect(() => {
118
+ console.log(`ServiceChannelWithLastMessage rendered for channel ${channel?.id}:`, {
119
+ hasLastMessage: !!lastMessage,
120
+ messageId: lastMessage?.id,
121
+ messageText: lastMessage?.message?.substring(0, 20) + (lastMessage?.message?.length > 20 ? '...' : ''),
122
+ date: lastMessage?.createdAt ? safeDate(lastMessage.createdAt)?.toISOString() : 'none',
123
+ });
124
+ }, [lastMessage, channel?.id]);
125
+
126
+ const count = 30;
127
+ const title = channel?.title.slice(0, count) + (channel?.title.length > count ? '...' : '');
128
+
129
+ // Define message display text
130
+ let displayMessage = 'No messages yet';
131
+
132
+ if (lastMessage) {
133
+ // Check for file attachments
134
+ const hasFileAttachments = lastMessage.files?.data?.length > 0;
135
+
136
+ // Check if the message appears to be an image
137
+ const isImageMessage =
138
+ lastMessage.message?.includes('<img') ||
139
+ lastMessage.message?.includes('[Image]') ||
140
+ lastMessage.message?.includes('![') ||
141
+ (/\.(jpeg|jpg|gif|png|bmp|webp)/i.test(lastMessage.message || '') &&
142
+ ((lastMessage.message || '').includes('http') || (lastMessage.message || '').includes('/images/')));
143
+
144
+ if (hasFileAttachments) {
145
+ displayMessage = '📎 File attachment';
146
+ } else if (isImageMessage) {
147
+ displayMessage = '[Image]';
148
+ } else if (lastMessage.message && lastMessage.message.trim() !== '') {
149
+ displayMessage = lastMessage.message;
150
+ } else {
151
+ displayMessage = '(Empty message)';
152
+ }
153
+ }
154
+
155
+ return (
156
+ <HStack space={'sm'} className="flex-1 justify-between">
157
+ <Box className="flex-[0.8]">
158
+ <Text color={colors.gray[600]} className="text-base text-wrap flex-wrap font-semibold">
159
+ {title}
160
+ </Text>
161
+ <Text color={colors.gray[600]} numberOfLines={1}>
162
+ {displayMessage}
163
+ </Text>
164
+ </Box>
165
+
166
+ <Box className="flex-[0.2]">
167
+ <Text color={colors.gray[500]}>{lastMessage ? createdAtText(lastMessage?.createdAt) : ''}</Text>
168
+ </Box>
169
+ </HStack>
170
+ );
171
+ });
172
+
49
173
  /**
50
174
  * TODO:
51
175
  * - Get Reservation info: reservation date, status
@@ -59,10 +183,17 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
59
183
  onOpen,
60
184
  refreshing,
61
185
  role,
186
+ visible = true,
62
187
  }) {
63
- const [servicePostParentId, setServicePostParentId] = React.useState(null);
188
+ const isMountedRef = useRef(true);
189
+ const [subscriptionsTimestamp, setSubscriptionsTimestamp] = useState(Date.now());
190
+
191
+ // Only run queries/subscriptions if visible
192
+ const shouldQuery = !!channel?.id && visible;
193
+
194
+ // Query for thread messages (minimal fields if possible)
64
195
  const {
65
- data: threadMessages,
196
+ data: threadMessagesData,
66
197
  loading: threadMessageLoading,
67
198
  error,
68
199
  refetch: refetchThreadMessages,
@@ -73,56 +204,41 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
73
204
  role,
74
205
  limit: 2,
75
206
  },
207
+ skip: !shouldQuery, // Only run if visible
76
208
  fetchPolicy: 'cache-and-network',
209
+ onCompleted: (data) => {
210
+ if (!shouldQuery) return;
211
+ },
212
+ onError: (error) => {
213
+ if (!shouldQuery) return;
214
+ console.error(`Error fetching thread messages for channel ${channel?.id}:`, error);
215
+ },
77
216
  });
78
217
 
79
- useFocusEffect(
80
- React.useCallback(() => {
81
- // Do something when the screen is focused
82
- // refetchMessages({ channelId: channel?.id?.toString(), limit: 25 });
83
- refetchThreadMessages({ channelId: channel?.id?.toString(), role, limit: 2 });
84
- return () => {
85
- // Do something when the screen is unfocused
86
- // Useful for cleanup functions
87
- };
88
- }, [refreshing]),
89
- );
218
+ // Extract threadMessages data for easier access
219
+ const threadMessages = useMemo(() => threadMessagesData?.threadMessages?.data || [], [threadMessagesData]);
90
220
 
221
+ // Calculate lastMessage directly from threadMessages
91
222
  const lastMessage = useMemo(() => {
92
- if (!threadMessages?.threadMessages?.data?.length) {
93
- return null;
94
- }
95
- const { data }: any = threadMessages.threadMessages;
96
- const replies =
97
- data
98
- ?.map((t: any) => t?.replies)
99
- ?.flat(1)
100
- ?.filter((p: any) => p?.message !== '') ?? [];
101
- if (replies?.length) {
102
- return replies[0];
103
- // return replies[replies.length - 1];
104
- } else {
105
- const post =
106
- data?.find((t: any) => {
107
- return t?.post?.message !== '';
108
- }) ??
109
- data?.find((t: any) => {
110
- return t?.post;
111
- }) ??
112
- null;
113
- return post ? post?.post : null;
114
- }
115
- }, [threadMessages]);
223
+ if (!shouldQuery) return null;
224
+ return computeLastMessage(threadMessages);
225
+ }, [shouldQuery, threadMessages]);
116
226
 
227
+ // Calculate servicePostParentId based on lastMessage
228
+ const servicePostParentId = useMemo(() => {
229
+ if (!shouldQuery || !lastMessage) return null;
230
+ return lastMessage?.parentId ? lastMessage?.parentId : lastMessage?.id;
231
+ }, [shouldQuery, lastMessage]);
232
+
233
+ // Handle component unmount
117
234
  React.useEffect(() => {
118
- if (lastMessage) {
119
- const sParentId = lastMessage?.parentId ? lastMessage?.parentId : lastMessage?.id;
120
- setServicePostParentId(sParentId);
121
- }
122
- }, [lastMessage]);
235
+ return () => {
236
+ isMountedRef.current = false;
237
+ };
238
+ }, []);
123
239
 
124
- const creatorAndMembersId = React.useMemo(() => {
125
- if (!channel?.members) return null;
240
+ const creatorAndMembersId = useMemo(() => {
241
+ if (!shouldQuery || !channel?.members) return null;
126
242
  const membersIds: any =
127
243
  channel?.members
128
244
  ?.filter((m: any) => m !== null && m?.user?.id !== currentUser?.id)
@@ -130,67 +246,64 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
130
246
  const creatorId: any = channel?.creator?.id;
131
247
  const mergedIds: any = [].concat(membersIds, creatorId) ?? [];
132
248
  return mergedIds?.filter((m: any, pos: any) => mergedIds?.indexOf(m) === pos) ?? [];
133
- }, [channel]);
134
-
135
- const postParentId = React.useMemo(() => {
136
- if (!creatorAndMembersId?.length) return null;
249
+ }, [shouldQuery, channel, currentUser]);
137
250
 
251
+ const postParentId = useMemo(() => {
252
+ if (!shouldQuery || !creatorAndMembersId?.length) return null;
138
253
  return creatorAndMembersId?.length && creatorAndMembersId?.includes(currentUser?.id)
139
254
  ? null
140
255
  : lastMessage?.parentId
141
256
  ? lastMessage?.parentId
142
257
  : lastMessage?.id ?? 0;
143
- }, [creatorAndMembersId, lastMessage]);
144
-
145
- // const { data: threadCreatedUpdated } = useOnThreadCreatedUpdatedSubscription({
146
- // variables: {
147
- // channelId: channel?.id?.toString(),
148
- // postParentId:
149
- // postParentId == null
150
- // ? null
151
- // : lastMessage?.parentId
152
- // ? lastMessage?.parentId ?? null
153
- // : lastMessage?.id ?? null,
154
- // },
155
- // });
156
-
157
- // React.useEffect(() => {
158
- // if (threadCreatedUpdated?.threadCreatedUpdated?.data) {
159
- // refetchThreadMessages({ channelId: channel?.id?.toString(), role, limit: 2 });
160
- // }
161
- // }, [threadCreatedUpdated]);
258
+ }, [shouldQuery, creatorAndMembersId, lastMessage, currentUser]);
259
+
260
+ // Function to force refresh the component state
261
+ const refreshThreadState = useCallback(() => {
262
+ if (shouldQuery && channel?.id && isMountedRef.current) {
263
+ refetchThreadMessages({
264
+ channelId: channel?.id?.toString(),
265
+ role,
266
+ limit: 2,
267
+ }).catch((err) => {
268
+ console.error('Error refreshing thread state:', err);
269
+ });
270
+ }
271
+ }, [shouldQuery, channel?.id, refetchThreadMessages, role]);
272
+
273
+ // Reset subscriptions when the component gains focus
274
+ useFocusEffect(
275
+ React.useCallback(() => {
276
+ if (!shouldQuery || !channel?.id) return;
277
+ setSubscriptionsTimestamp(Date.now());
278
+ const fetchFreshData = async () => {
279
+ try {
280
+ await refetchThreadMessages({
281
+ channelId: channel?.id?.toString(),
282
+ role,
283
+ limit: 2,
284
+ });
285
+ } catch (error) {
286
+ console.error('Error refetching thread messages on focus:', error);
287
+ }
288
+ };
289
+ fetchFreshData();
290
+ return () => {};
291
+ }, [shouldQuery, refreshing, channel?.id, refetchThreadMessages, role]),
292
+ );
162
293
 
163
294
  return (
164
295
  <Pressable
165
- onPress={() => channel?.id !== selectedChannelId && onOpen(channel?.id, channel?.title, postParentId)}
166
- borderWidth={'$1'}
167
- borderRadius={'$md'}
168
- borderColor={'$trueGray200'}
169
- flex={1}
170
- $dark-borderColor="$coolGray600"
171
- $dark-backgroundColor="$trueGray700"
172
- $light-backgroundColor="$trueGray50"
173
- $light-borderColor="$trueGray200"
296
+ // onPress={() => channel?.id !== selectedChannelId && onOpen(channel?.id, channel?.title, postParentId)}
297
+ onPress={() => onOpen(channel?.id, channel?.title, postParentId)}
298
+ className="flex-1 rounded-md border-gray-200 dark:border-gray-600 dark:bg-gray-700"
299
+ style={{ borderBottomWidth: 1, borderColor: '#e5e7eb', marginVertical: 0 }}
174
300
  >
175
- <HStack
176
- // px={2}
177
- // pl={3}
178
- py={'$3'}
179
- space={'sm'}
180
- w={'100%'}
181
- flex={1}
182
- // direction={'row'}
183
- justifyContent={'space-between'}
184
- alignItems={'center'}
185
- >
186
- <Box flex={0.1} alignItems={'flex-start'} pl={'$3'}>
301
+ <HStack space={'md'} className="flex-1 w-[100%] px-2 py-3 items-center">
302
+ <Box className="flex-[0.1] items-start pl-3">
187
303
  <Avatar
188
304
  key={'service-channels-key-' + channel?.id}
189
- bg={'transparent'}
190
305
  size={'sm'}
191
- top={'$0'}
192
- right={'$0'}
193
- zIndex={1}
306
+ className="bg-transparent top-0 right-0 z-[1]"
194
307
  >
195
308
  <AvatarFallbackText> {startCase(channel?.creator?.username?.charAt(0))}</AvatarFallbackText>
196
309
  <AvatarImage
@@ -202,102 +315,132 @@ export const ServiceDialogsListItemComponent: React.FC<IDialogListItemProps> = f
202
315
  />
203
316
  </Avatar>
204
317
  </Box>
205
- <Box flex={0.9}>
318
+ <Box className="flex-1">
206
319
  <ServiceChannelWithLastMessage
320
+ key={`channel-${channel?.id}-${subscriptionsTimestamp}`}
207
321
  channel={channel}
208
322
  lastMessage={lastMessage}
209
- subscribeToNewMessages={() =>
210
- subscribeToMore({
211
- document: THREAD_CHAT_ADDED,
212
- variables: {
213
- channelId: channel?.id?.toString(),
214
- postParentId: postParentId ? servicePostParentId : null,
215
- },
216
- updateQuery: (prev, { subscriptionData }: any) => {
217
- if (!subscriptionData.data) return prev;
218
-
219
- const newPostThreadData: any = subscriptionData?.data?.threadCreatedUpdated?.data;
220
- const newMessage: any = subscriptionData?.data?.threadCreatedUpdated?.lastMessage;
221
- const data =
222
- prev?.threadMessages?.data?.map((t: any) =>
223
- t.id === newPostThreadData?.id
224
- ? {
225
- ...t,
226
- replies: [...t?.replies, newMessage],
227
- replyCount: newPostThreadData?.replyCount,
228
- lastReplyAt: newPostThreadData?.lastReplyAt,
229
- updatedAt: newPostThreadData?.updatedAt,
230
- }
231
- : t,
232
- ) ?? [];
233
-
234
- return Object.assign({}, prev, {
235
- threadMessages: {
236
- ...prev?.threadMessages,
237
- totalCount: newPostThreadData?.totalCount ?? 0,
238
- data: data?.length > 0 ? data : [newPostThreadData],
239
- // totalCount: prev?.threadMessages?.totalCount + 1,
240
- //data: data,
241
- },
242
- });
243
- },
244
- })
245
- }
246
323
  />
247
- {/* <Text
248
- flex={1}
249
- color="gray.600"
250
- p={0}
251
- m={0}
252
- w={'100%'}
253
- justifyContent={''}
254
- fontSize="lg"
255
- fontWeight="semibold"
256
- >
257
- {title}
258
- </Text> */}
259
- {/* <Text flex={0.1} color="gray.600">
260
- {lastMessage?.message ?? ''}
261
- </Text> */}
324
+
325
+ {/* Subscription handlers for real-time updates */}
326
+ {shouldQuery && (
327
+ <>
328
+ <SubscriptionHandler
329
+ subscribeToMore={subscribeToMore}
330
+ document={THREAD_CREATED_UPDATED}
331
+ variables={{
332
+ channelId: channel?.id?.toString(),
333
+ postParentId: postParentId || servicePostParentId || null,
334
+ }}
335
+ updateQuery={(prev, { subscriptionData }: any) => {
336
+ if (!subscriptionData?.data) {
337
+ return prev;
338
+ }
339
+ try {
340
+ const newPostThreadData: any =
341
+ subscriptionData?.data?.threadCreatedUpdated?.data;
342
+ const newMessage: any =
343
+ subscriptionData?.data?.threadCreatedUpdated?.lastMessage;
344
+ if (!newPostThreadData || !newMessage) {
345
+ return prev;
346
+ }
347
+ const prevThreads = prev?.threadMessages?.data || [];
348
+ const threadExists = prevThreads.some(
349
+ (t: any) => t.id === newPostThreadData?.id,
350
+ );
351
+ let updatedThreads;
352
+ if (threadExists) {
353
+ updatedThreads = prevThreads.map((t: any) =>
354
+ t.id === newPostThreadData?.id
355
+ ? {
356
+ ...t,
357
+ replies: [...(t?.replies || []), newMessage],
358
+ replyCount: newPostThreadData?.replyCount,
359
+ lastReplyAt: newPostThreadData?.lastReplyAt,
360
+ updatedAt: newPostThreadData?.updatedAt,
361
+ }
362
+ : t,
363
+ );
364
+ } else {
365
+ updatedThreads = [...prevThreads, newPostThreadData];
366
+ }
367
+ return {
368
+ ...prev,
369
+ threadMessages: {
370
+ ...prev?.threadMessages,
371
+ totalCount:
372
+ newPostThreadData?.totalCount ??
373
+ prev?.threadMessages?.totalCount ??
374
+ 0,
375
+ data: updatedThreads,
376
+ },
377
+ };
378
+ } catch (error) {
379
+ console.error(`Thread subscription error for channel ${channel?.id}:`, error);
380
+ return prev;
381
+ }
382
+ }}
383
+ />
384
+ <SubscriptionHandler
385
+ subscribeToMore={subscribeToMore}
386
+ document={CHAT_MESSAGE_ADDED}
387
+ variables={{
388
+ channelId: channel?.id?.toString(),
389
+ postParentId: postParentId || servicePostParentId || null,
390
+ }}
391
+ updateQuery={(prev, { subscriptionData }: any) => {
392
+ if (!subscriptionData?.data) {
393
+ return prev;
394
+ }
395
+ try {
396
+ const newMessage = subscriptionData?.data?.chatMessageAdded;
397
+ if (!newMessage) {
398
+ return prev;
399
+ }
400
+ const prevThreads = prev?.threadMessages?.data || [];
401
+ const threadIndex = prevThreads.findIndex((t: any) => {
402
+ if (newMessage.parentId && t.post?.id === newMessage.parentId) {
403
+ return true;
404
+ }
405
+ if (t.replies && t.replies.some((r: any) => r.id === newMessage.parentId)) {
406
+ return true;
407
+ }
408
+ return false;
409
+ });
410
+ if (threadIndex === -1) {
411
+ return prev;
412
+ }
413
+ const updatedThreads = [...prevThreads];
414
+ const thread = { ...updatedThreads[threadIndex] };
415
+ thread.replies = [...(thread.replies || []), newMessage];
416
+ const currentReplyCount =
417
+ typeof thread.replyCount === 'number' ? thread.replyCount : 0;
418
+ thread.replyCount = currentReplyCount + 1;
419
+ thread.lastReplyAt = newMessage.createdAt;
420
+ thread.updatedAt = newMessage.createdAt;
421
+ updatedThreads[threadIndex] = thread;
422
+ return {
423
+ ...prev,
424
+ threadMessages: {
425
+ ...prev?.threadMessages,
426
+ data: updatedThreads,
427
+ },
428
+ };
429
+ } catch (error) {
430
+ console.error(
431
+ `Chat message subscription error for channel ${channel?.id}:`,
432
+ error,
433
+ );
434
+ return prev;
435
+ }
436
+ }}
437
+ />
438
+ </>
439
+ )}
262
440
  </Box>
263
- {/* <Text flex={0.2} color="gray.500">
264
- {lastMessage ? createdAtText(lastMessage?.createdAt) : ''}
265
- </Text> */}
266
441
  </HStack>
267
442
  </Pressable>
268
443
  );
269
444
  };
270
445
 
271
- const ServiceChannelWithLastMessage = React.memo(({ channel, lastMessage, subscribeToNewMessages }: any) => {
272
- React.useEffect(() => subscribeToNewMessages(), []);
273
- return (
274
- <HStack space={'sm'} flex={1} justifyContent={'center'} alignItems={'center'}>
275
- <Box flex={0.8}>
276
- <Text color="$trueGray600" fontSize="$lg" flexWrap={'wrap'} fontWeight="$semibold">
277
- {channel?.title}
278
- </Text>
279
- <Text color="$trueGray600" numberOfLines={1}>
280
- {/* {creatorAndMembersId?.length && creatorAndMembersId?.includes(currentUser?.id)
281
- ? lastMessageCreatorAndMembers?.message ?? ''
282
- : lastMessage?.message ?? ''} */}
283
- {lastMessage?.message ?? ''}
284
- </Text>
285
- </Box>
286
-
287
- <Box flex={0.2}>
288
- {/* {creatorAndMembersId?.length && creatorAndMembersId?.includes(currentUser?.id) ? (
289
- <Text color="gray.500">
290
- {lastMessageCreatorAndMembers
291
- ? createdAtText(lastMessageCreatorAndMembers?.createdAt)
292
- : ''}
293
- </Text>
294
- ) : (
295
- <Text color="gray.500">{lastMessage ? createdAtText(lastMessage?.createdAt) : ''}</Text>
296
- )} */}
297
- <Text color="$trueGray500">{lastMessage ? createdAtText(lastMessage?.createdAt) : ''}</Text>
298
- </Box>
299
- </HStack>
300
- );
301
- });
302
-
303
446
  export const ServiceDialogsListItem = React.memo(ServiceDialogsListItemComponent);
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { TextInput, TextInputProps } from 'react-native';
3
+
4
+ export const SlackInput = React.forwardRef<TextInput, TextInputProps>((props, ref) => {
5
+ return (
6
+ <TextInput
7
+ ref={ref}
8
+ {...props}
9
+ style={[
10
+ {
11
+ flex: 1,
12
+ fontSize: 16,
13
+ padding: 10,
14
+ borderRadius: 8,
15
+ backgroundColor: '#f8f8f8',
16
+ borderColor: '#e0e0e0',
17
+ borderWidth: 1,
18
+ },
19
+ props.style,
20
+ ]}
21
+ />
22
+ );
23
+ });