@messenger-box/platform-mobile 10.0.3-alpha.40 → 10.0.3-alpha.46

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 (54) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/lib/compute.js +2 -3
  3. package/lib/index.js.map +1 -1
  4. package/lib/queries/inboxQueries.js +77 -0
  5. package/lib/queries/inboxQueries.js.map +1 -0
  6. package/lib/routes.json +2 -3
  7. package/lib/screens/inbox/DialogThreadMessages.js +3 -7
  8. package/lib/screens/inbox/DialogThreadMessages.js.map +1 -1
  9. package/lib/screens/inbox/DialogThreads.js +3 -7
  10. package/lib/screens/inbox/DialogThreads.js.map +1 -1
  11. package/lib/screens/inbox/components/DialogsListItem.js +47 -46
  12. package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
  13. package/lib/screens/inbox/components/GiftedChatInboxComponent.js +313 -0
  14. package/lib/screens/inbox/components/GiftedChatInboxComponent.js.map +1 -0
  15. package/lib/screens/inbox/components/ServiceDialogsListItem.js +72 -57
  16. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +1 -1
  17. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +115 -14
  18. package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
  19. package/lib/screens/inbox/components/SubscriptionHandler.js +24 -0
  20. package/lib/screens/inbox/components/SubscriptionHandler.js.map +1 -0
  21. package/lib/screens/inbox/containers/ConversationView.js +640 -493
  22. package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
  23. package/lib/screens/inbox/containers/Dialogs.js +100 -181
  24. package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
  25. package/lib/screens/inbox/containers/ThreadConversationView.js +659 -245
  26. package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
  27. package/lib/screens/inbox/containers/ThreadsView.js +3 -3
  28. package/lib/screens/inbox/containers/ThreadsView.js.map +1 -1
  29. package/lib/screens/inbox/hooks/useInboxMessages.js +31 -0
  30. package/lib/screens/inbox/hooks/useInboxMessages.js.map +1 -0
  31. package/package.json +4 -4
  32. package/src/index.ts +2 -0
  33. package/src/queries/inboxQueries.ts +298 -0
  34. package/src/queries/index.d.ts +2 -0
  35. package/src/queries/index.ts +1 -0
  36. package/src/screens/inbox/DialogThreadMessages.tsx +3 -11
  37. package/src/screens/inbox/DialogThreads.tsx +3 -7
  38. package/src/screens/inbox/components/Actionsheet.tsx +30 -0
  39. package/src/screens/inbox/components/DialogsListItem.tsx +89 -148
  40. package/src/screens/inbox/components/ExpandableInput.tsx +460 -0
  41. package/src/screens/inbox/components/ExpandableInputActionSheet.tsx +518 -0
  42. package/src/screens/inbox/components/GiftedChatInboxComponent.tsx +411 -0
  43. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +202 -221
  44. package/src/screens/inbox/components/SlackInput.tsx +23 -0
  45. package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +216 -30
  46. package/src/screens/inbox/components/SubscriptionHandler.tsx +41 -0
  47. package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +6 -7
  48. package/src/screens/inbox/containers/ConversationView.tsx +1109 -669
  49. package/src/screens/inbox/containers/Dialogs.tsx +198 -342
  50. package/src/screens/inbox/containers/SupportServiceDialogs.tsx +2 -2
  51. package/src/screens/inbox/containers/ThreadConversationView.tsx +1141 -402
  52. package/src/screens/inbox/containers/ThreadsView.tsx +5 -5
  53. package/src/screens/inbox/hooks/useInboxMessages.ts +34 -0
  54. package/src/screens/inbox/machines/threadsMachine.ts +2 -2
@@ -1,15 +1,16 @@
1
- import React, { useCallback, useEffect, useRef, useState } from 'react';
1
+ import React, { useCallback, useEffect, useRef, useState, useMemo } from 'react';
2
2
  import { FlatList, Box, Heading, Input, InputField, Text, Center, Spinner } from '@admin-layout/gluestack-ui-mobile';
3
3
  import { Ionicons } from '@expo/vector-icons';
4
4
  import { useSelector } from 'react-redux';
5
5
  import { useNavigation, useRoute, useFocusEffect } from '@react-navigation/native';
6
6
  import { DialogsListItem } from '../components/DialogsListItem';
7
7
  import { ServiceDialogsListItem } from '../components/ServiceDialogsListItem';
8
- import { useGetChannelsByUserWithServiceChannelsQuery, OnChatMessageAddedDocument } from 'common/graphql';
8
+ import { useChannelsQuery, CHAT_MESSAGE_ADDED } from '../../../queries/inboxQueries';
9
9
  import { RoomType } from 'common';
10
10
  import { userSelector } from '@adminide-stack/user-auth0-client';
11
11
  import { config } from '../config';
12
12
  import colors from 'tailwindcss/colors';
13
+ import { SubscriptionHandler } from '../components/SubscriptionHandler';
13
14
 
14
15
  export interface InboxProps {
15
16
  channelFilters?: Record<string, unknown>;
@@ -24,7 +25,7 @@ const DialogsComponent = (props: InboxProps) => {
24
25
  const { params } = useRoute<any>();
25
26
  const auth = useSelector(userSelector);
26
27
  const navigation = useNavigation<any>();
27
-
28
+ console.log('---dialogs component---orgName---', params?.orgName);
28
29
  // Local state for UI control
29
30
  const [searchQuery, setSearchQuery] = useState('');
30
31
  const [selectedChannelId, setSelectedChannelId] = useState<string | null>(params?.channelId || null);
@@ -45,7 +46,7 @@ const DialogsComponent = (props: InboxProps) => {
45
46
  const resetActiveChannelTimeoutRef = useRef<NodeJS.Timeout | null>(null);
46
47
 
47
48
  // Apollo query with pagination and optimistic updates
48
- const { data, loading, refetch, fetchMore, subscribeToMore } = useGetChannelsByUserWithServiceChannelsQuery({
49
+ const { data, loading, refetch, fetchMore, subscribeToMore } = useChannelsQuery({
49
50
  variables: {
50
51
  role: channelRole,
51
52
  criteria: channelFilters,
@@ -56,167 +57,203 @@ const DialogsComponent = (props: InboxProps) => {
56
57
  limit: 15,
57
58
  skip: 0,
58
59
  },
59
- fetchPolicy: 'cache-and-network',
60
- nextFetchPolicy: 'network-only',
61
60
  notifyOnNetworkStatusChange: true,
62
61
  });
63
62
 
64
- // Process the channels from the query response
63
+ // Memoize processChannels and sortChannels to avoid unnecessary recalculations
65
64
  const processChannels = useCallback(
66
65
  (rawChannels = []) => {
67
- if (!rawChannels || !rawChannels.length) return [];
68
-
69
- // Filter out channels without valid members
70
- const filteredChannels = rawChannels.filter((c) => {
71
- if (!c || !c.members) return false;
72
-
73
- // Early return pattern for better performance
74
- for (const member of c.members) {
75
- if (
76
- member &&
77
- member.user &&
78
- member.user.id !== auth?.id &&
79
- member.user.__typename === 'UserAccount'
80
- ) {
81
- return true;
82
- }
83
- }
84
- return false;
85
- });
86
-
87
- // Process channels to ensure lastMessage property is properly structured
88
- return filteredChannels.map((channel) => {
89
- // If channel has a lastMessage, ensure it's properly formatted
90
- if (channel.lastMessage) {
91
- return {
92
- ...channel,
93
- lastMessage: {
94
- ...channel.lastMessage,
95
- // Ensure these essential properties exist
96
- id: channel.lastMessage.id,
97
- message: channel.lastMessage.message,
98
- createdAt: channel.lastMessage.createdAt || channel.lastMessage.updatedAt,
99
- updatedAt: channel.lastMessage.updatedAt || channel.lastMessage.createdAt,
100
- userId: channel.lastMessage.userId,
101
- channelId: channel.lastMessage.channelId || channel.id,
102
- },
103
- };
104
- }
105
- return channel;
66
+ if (!rawChannels?.length) return [];
67
+ return rawChannels.filter((c) => {
68
+ if (!c?.members) return false;
69
+ return c.members.some(
70
+ (member) => member?.user && member.user.id !== auth?.id && member.user.__typename === 'UserAccount',
71
+ );
106
72
  });
107
73
  },
108
74
  [auth?.id],
109
75
  );
110
76
 
111
- // Sort channels by most recent activity
112
77
  const sortChannels = useCallback((channels) => {
113
- if (!channels || !channels.length) return [];
114
-
78
+ if (!channels?.length) return [];
115
79
  return [...channels].sort((a, b) => {
116
80
  const dateA = new Date(a?.updatedAt || a?.createdAt).getTime();
117
81
  const dateB = new Date(b?.updatedAt || b?.createdAt).getTime();
118
- return dateB - dateA; // Newest first
82
+ return dateB - dateA;
119
83
  });
120
84
  }, []);
121
85
 
122
- // Combine data from both channel types
123
- const allChannels = [...(data?.supportServiceChannels || []), ...(data?.channelsByUser || [])];
86
+ // Navigation handlers with debounce to prevent double taps
87
+ const handleSelectChannel = useCallback(
88
+ (id, title) => {
89
+ if (activeChannelRef.current === id) {
90
+ console.log('📱 Ignoring repeated tap on channel:', id);
91
+ return;
92
+ }
93
+ activeChannelRef.current = id;
94
+ if (resetActiveChannelTimeoutRef.current) {
95
+ clearTimeout(resetActiveChannelTimeoutRef.current);
96
+ }
97
+ resetActiveChannelTimeoutRef.current = setTimeout(() => {
98
+ activeChannelRef.current = null;
99
+ }, 2000);
100
+ setSelectedChannelId(id);
101
+ console.log('📱 Navigating to channel:', id);
102
+ navigation.navigate(config.INBOX_MESSEGE_PATH, {
103
+ channelId: id,
104
+ role: channelRole,
105
+ title: title,
106
+ hideTabBar: true,
107
+ timestamp: new Date().getTime(),
108
+ orgName: params?.orgName,
109
+ });
110
+ },
111
+ [navigation, channelRole],
112
+ );
113
+
114
+ const handleSelectServiceChannel = useCallback(
115
+ (id, title, postParentId) => {
116
+ if (activeChannelRef.current === id) {
117
+ console.log('📱 Ignoring repeated tap on service channel:', id);
118
+ return;
119
+ }
120
+ activeChannelRef.current = id;
121
+ if (resetActiveChannelTimeoutRef.current) {
122
+ clearTimeout(resetActiveChannelTimeoutRef.current);
123
+ }
124
+ resetActiveChannelTimeoutRef.current = setTimeout(() => {
125
+ activeChannelRef.current = null;
126
+ }, 2000);
127
+ setSelectedChannelId(id);
128
+ console.log('📱 Navigating to service channel:', id);
129
+ navigation.navigate(postParentId || postParentId === 0 ? config.THREAD_MESSEGE_PATH : config.THREADS_PATH, {
130
+ channelId: id,
131
+ role: channelRole,
132
+ title: title,
133
+ postParentId: postParentId,
134
+ hideTabBar: true,
135
+ orgName: params?.orgName,
136
+ });
137
+ },
138
+ [navigation, channelRole],
139
+ );
140
+
141
+ // Handle search query changes
142
+ const handleSearchChange = useCallback((text: string) => {
143
+ setSearchQuery(text);
144
+ }, []);
124
145
 
125
- // Process and sort the channels
126
- const channels = sortChannels(processChannels(allChannels));
146
+ // Memoize allChannels and channels
147
+ const allChannels = useMemo(
148
+ () => [...(data?.supportServiceChannels || []), ...(data?.channelsByUser || [])],
149
+ [data],
150
+ );
151
+ const channels = useMemo(
152
+ () => sortChannels(processChannels(allChannels)),
153
+ [allChannels, processChannels, sortChannels],
154
+ );
127
155
 
128
- // Set up subscription for real-time message updates
129
- useEffect(() => {
130
- if (!auth || !auth.id) return;
131
-
132
- console.log('📱 Setting up global message subscription for dialog updates');
133
-
134
- const unsubscribe = subscribeToMore({
135
- document: OnChatMessageAddedDocument,
136
- variables: {}, // No specific channel ID - we'll handle all messages
137
- updateQuery: (prev, { subscriptionData }) => {
138
- try {
139
- if (!subscriptionData.data || !isMountedRef.current) return prev;
140
-
141
- const subData = subscriptionData.data as any;
142
- const newMessage = subData.chatMessageAdded;
143
-
144
- console.log('📱 Dialog subscription received message update:', newMessage?.id);
145
-
146
- // Skip if no message or no channelId
147
- if (!newMessage || !newMessage.channelId) return prev;
148
-
149
- // Find the channel this message belongs to
150
- const channelId = newMessage.channelId.toString();
151
-
152
- // Find which array contains this channel (direct or service)
153
- let foundInDirectChannels = false;
154
- let foundInServiceChannels = false;
155
-
156
- // Check if this channel exists in direct channels
157
- const directChannelIndex = prev.channelsByUser?.findIndex((c) => c.id.toString() === channelId);
158
- if (directChannelIndex !== undefined && directChannelIndex >= 0) {
159
- foundInDirectChannels = true;
160
- }
161
-
162
- // Check if this channel exists in service channels
163
- const serviceChannelIndex = prev.supportServiceChannels?.findIndex(
164
- (c) => c.id.toString() === channelId,
165
- );
166
- if (serviceChannelIndex !== undefined && serviceChannelIndex >= 0) {
167
- foundInServiceChannels = true;
168
- }
169
-
170
- // Create a deep copy of the previous state to avoid mutating it
171
- const result = {
172
- ...prev,
173
- channelsByUser: [...(prev.channelsByUser || [])],
174
- supportServiceChannels: [...(prev.supportServiceChannels || [])],
175
- };
176
-
177
- // Optimistically update the channel with the new message
178
- if (foundInDirectChannels && directChannelIndex >= 0) {
179
- // Update the direct channel
180
- const channel = { ...result.channelsByUser[directChannelIndex] } as any;
181
-
182
- // Update lastMessage
183
- channel.lastMessage = newMessage;
184
-
185
- // Update timestamp to move to top of sorted list
186
- channel.updatedAt = newMessage.createdAt || new Date().toISOString();
187
-
188
- // Replace the channel in the array
189
- result.channelsByUser[directChannelIndex] = channel;
190
- }
191
-
192
- if (foundInServiceChannels && serviceChannelIndex >= 0) {
193
- // Update the service channel
194
- const channel = { ...result.supportServiceChannels[serviceChannelIndex] } as any;
195
-
196
- // Update lastMessage
197
- channel.lastMessage = newMessage;
198
-
199
- // Update timestamp to move to top of sorted list
200
- channel.updatedAt = newMessage.createdAt || new Date().toISOString();
201
-
202
- // Replace the channel in the array
203
- result.supportServiceChannels[serviceChannelIndex] = channel;
204
- }
205
-
206
- return result;
207
- } catch (error) {
208
- console.error('Error in dialog subscription handler:', error);
209
- return prev;
210
- }
211
- },
156
+ // Memoize filteredChannels
157
+ const displayChannels = useMemo(() => {
158
+ if (!searchQuery.trim()) return channels;
159
+ const query = searchQuery.toLowerCase();
160
+ return channels.filter((channel) => {
161
+ if (channel.title && channel.title.toLowerCase().includes(query)) return true;
162
+ if (channel.members) {
163
+ return channel.members.some((member) => {
164
+ const user = member?.user;
165
+ if (!user) return false;
166
+ const fullName = `${user.givenName || ''} ${user.familyName || ''}`.toLowerCase();
167
+ return fullName.includes(query) || (user.username && user.username.toLowerCase().includes(query));
168
+ });
169
+ }
170
+ return false;
212
171
  });
172
+ }, [channels, searchQuery]);
213
173
 
214
- // Clean up subscription when component unmounts
215
- return () => {
216
- console.log('📱 Cleaning up dialog message subscription');
217
- unsubscribe();
218
- };
219
- }, [auth?.id, subscribeToMore]);
174
+ // Memoize renderItem to avoid re-renders
175
+ const renderItem = useCallback(
176
+ ({ item: channel }) => {
177
+ const key = `${channel.type === RoomType.Service ? 'service' : 'direct'}-${channel.id}`;
178
+ return channel?.type === RoomType.Service ? (
179
+ <ServiceDialogsListItem
180
+ key={key}
181
+ onOpen={handleSelectServiceChannel}
182
+ currentUser={auth}
183
+ channel={channel}
184
+ refreshing={loading}
185
+ selectedChannelId={selectedChannelId}
186
+ role={channelRole}
187
+ />
188
+ ) : (
189
+ <DialogsListItem
190
+ key={key}
191
+ onOpen={handleSelectChannel}
192
+ currentUser={auth}
193
+ channel={channel}
194
+ selectedChannelId={selectedChannelId}
195
+ forceRefresh={true}
196
+ />
197
+ );
198
+ },
199
+ [auth, channelRole, handleSelectChannel, handleSelectServiceChannel, loading, selectedChannelId],
200
+ );
201
+
202
+ // Memoize ListFooterComponent and ListEmptyComponent
203
+ const ListFooterComponent = useMemo(
204
+ () =>
205
+ isLoadingMore ? (
206
+ <Center className="py-4">
207
+ <Spinner color={colors.blue[500]} size="small" />
208
+ </Center>
209
+ ) : null,
210
+ [isLoadingMore],
211
+ );
212
+
213
+ const ListEmptyComponent = useMemo(() => {
214
+ if (loading && displayChannels.length === 0) {
215
+ return (
216
+ <Center className="flex-1 justify-center items-center" style={{ height: 300 }}>
217
+ <Spinner color={colors.blue[500]} size="large" />
218
+ <Text className="mt-4 text-gray-500">Loading conversations...</Text>
219
+ </Center>
220
+ );
221
+ }
222
+ return (
223
+ <Box className="p-6">
224
+ <Box className="mb-6">
225
+ <Heading className="text-2xl font-bold">Direct Messages</Heading>
226
+ <Text className="text-gray-600 mt-1">Private conversations with other users</Text>
227
+ </Box>
228
+ <Input
229
+ className="mb-8 h-[50] rounded-md border-gray-300 border"
230
+ size="md"
231
+ style={{
232
+ paddingVertical: 8,
233
+ marginBottom: 10,
234
+ borderColor: '#d1d5db',
235
+ borderRadius: 10,
236
+ }}
237
+ >
238
+ <InputField
239
+ placeholder="Search messages..."
240
+ onChangeText={handleSearchChange}
241
+ value={searchQuery}
242
+ />
243
+ </Input>
244
+ <Center className="items-center" style={{ paddingVertical: 5 }}>
245
+ <Box className="w-16 h-16 rounded-full bg-blue-500 flex items-center justify-center mb-5">
246
+ <Ionicons name="chatbubble-ellipses" size={30} color="white" />
247
+ </Box>
248
+ <Text className="text-2xl font-bold text-center mb-2">No messages yet</Text>
249
+ <Text className="text-gray-600 text-center mb-8">
250
+ When you start conversations with others,{'\n'}
251
+ they'll appear here.
252
+ </Text>
253
+ </Center>
254
+ </Box>
255
+ );
256
+ }, [loading, displayChannels.length, handleSearchChange, searchQuery]);
220
257
 
221
258
  // Handle component cleanup
222
259
  useEffect(() => {
@@ -318,158 +355,27 @@ const DialogsComponent = (props: InboxProps) => {
318
355
  });
319
356
  }, [fetchMore, isLoadingMore, data, channels.length, page]);
320
357
 
321
- // Navigation handlers with debounce to prevent double taps
322
- const handleSelectChannel = useCallback(
323
- (id, title) => {
324
- // Return early if this channel is already active (prevents double navigation)
325
- if (activeChannelRef.current === id) {
326
- console.log('📱 Ignoring repeated tap on channel:', id);
327
- return;
328
- }
329
-
330
- // Set this channel as active
331
- activeChannelRef.current = id;
332
-
333
- // Clear any existing timeout
334
- if (resetActiveChannelTimeoutRef.current) {
335
- clearTimeout(resetActiveChannelTimeoutRef.current);
336
- }
337
-
338
- // Set a timeout to clear the active channel after 2 seconds
339
- // This prevents the active state from getting stuck if navigation fails
340
- resetActiveChannelTimeoutRef.current = setTimeout(() => {
341
- activeChannelRef.current = null;
342
- }, 2000);
343
-
344
- setSelectedChannelId(id);
345
-
346
- console.log('📱 Navigating to channel:', id);
347
-
348
- navigation.navigate(config.INBOX_MESSEGE_PATH, {
349
- channelId: id,
350
- role: channelRole,
351
- title: title,
352
- hideTabBar: true,
353
- timestamp: new Date().getTime(),
354
- });
355
- },
356
- [navigation, channelRole],
357
- );
358
-
359
- const handleSelectServiceChannel = useCallback(
360
- (id, title, postParentId) => {
361
- // Return early if this channel is already active (prevents double navigation)
362
- if (activeChannelRef.current === id) {
363
- console.log('📱 Ignoring repeated tap on service channel:', id);
364
- return;
365
- }
366
-
367
- // Set this channel as active
368
- activeChannelRef.current = id;
369
-
370
- // Clear any existing timeout
371
- if (resetActiveChannelTimeoutRef.current) {
372
- clearTimeout(resetActiveChannelTimeoutRef.current);
373
- }
374
-
375
- // Set a timeout to clear the active channel after 2 seconds
376
- resetActiveChannelTimeoutRef.current = setTimeout(() => {
377
- activeChannelRef.current = null;
378
- }, 2000);
379
-
380
- setSelectedChannelId(id);
381
-
382
- console.log('📱 Navigating to service channel:', id);
383
-
384
- navigation.navigate(postParentId || postParentId === 0 ? config.THREAD_MESSEGE_PATH : config.THREADS_PATH, {
385
- channelId: id,
386
- role: channelRole,
387
- title: title,
388
- postParentId: postParentId,
389
- hideTabBar: true,
390
- });
391
- },
392
- [navigation, channelRole],
393
- );
394
-
395
- // Handle search query changes
396
- const handleSearchChange = useCallback((text: string) => {
397
- setSearchQuery(text);
398
- }, []);
399
-
400
- // Filter channels by search query
401
- const filteredChannels = useCallback(() => {
402
- if (!searchQuery.trim()) return channels;
403
-
404
- const query = searchQuery.toLowerCase();
405
- return channels.filter((channel) => {
406
- // Check if the channel title contains the search query
407
- if (channel.title && channel.title.toLowerCase().includes(query)) {
408
- return true;
409
- }
410
-
411
- // Check if any member's name contains the search query
412
- if (channel.members) {
413
- for (const member of channel.members) {
414
- const user = member?.user;
415
- if (!user) continue;
416
-
417
- const fullName = `${user.givenName || ''} ${user.familyName || ''}`.toLowerCase();
418
- if (fullName.includes(query)) {
419
- return true;
420
- }
421
-
422
- if (user.username && user.username.toLowerCase().includes(query)) {
423
- return true;
424
- }
425
- }
426
- }
427
-
428
- return false;
429
- });
430
- }, [channels, searchQuery]);
431
-
432
- const displayChannels = filteredChannels();
433
-
434
358
  return (
435
359
  <Box className="p-2">
360
+ <SubscriptionHandler
361
+ subscribeToMore={subscribeToMore}
362
+ document={CHAT_MESSAGE_ADDED}
363
+ variables={{}}
364
+ updateQuery={undefined}
365
+ />
436
366
  <FlatList
437
367
  data={displayChannels}
438
368
  onRefresh={handlePullToRefresh}
439
369
  refreshing={loading && !isLoadingMore}
440
370
  contentContainerStyle={{ minHeight: '100%' }}
441
- ItemSeparatorComponent={() => <Box className="h-0.5 bg-gray-200" />}
442
- renderItem={({ item: channel }) => {
443
- const key = `${channel.type === RoomType.Service ? 'service' : 'direct'}-${channel.id}`;
444
-
445
- return channel?.type === RoomType.Service ? (
446
- <ServiceDialogsListItem
447
- key={key}
448
- onOpen={handleSelectServiceChannel}
449
- currentUser={auth}
450
- channel={channel}
451
- refreshing={loading}
452
- selectedChannelId={selectedChannelId}
453
- role={channelRole}
454
- />
455
- ) : (
456
- <DialogsListItem
457
- key={key}
458
- onOpen={handleSelectChannel}
459
- currentUser={auth}
460
- channel={channel}
461
- selectedChannelId={selectedChannelId}
462
- forceRefresh={true}
463
- />
464
- );
465
- }}
466
- ListFooterComponent={() =>
467
- isLoadingMore ? (
468
- <Center className="py-4">
469
- <Spinner color={colors.blue[500]} size="small" />
470
- </Center>
471
- ) : null
472
- }
371
+ ItemSeparatorComponent={React.useCallback(
372
+ () => (
373
+ <Box className="h-0.5 bg-gray-200" />
374
+ ),
375
+ [],
376
+ )}
377
+ renderItem={renderItem}
378
+ ListFooterComponent={ListFooterComponent}
473
379
  onEndReached={handleLoadMore}
474
380
  onEndReachedThreshold={0.5}
475
381
  initialNumToRender={5}
@@ -477,59 +383,9 @@ const DialogsComponent = (props: InboxProps) => {
477
383
  windowSize={5}
478
384
  removeClippedSubviews={true}
479
385
  updateCellsBatchingPeriod={100}
480
- getItemLayout={(data, index) => ({ length: 80, offset: 80 * index, index })}
481
- keyExtractor={(item) => `channel-${item.id}`}
482
- ListEmptyComponent={() => {
483
- // Show spinner during initial loading
484
- if (loading && displayChannels.length === 0) {
485
- return (
486
- <Center className="flex-1 justify-center items-center" style={{ height: 300 }}>
487
- <Spinner color={colors.blue[500]} size="large" />
488
- <Text className="mt-4 text-gray-500">Loading conversations...</Text>
489
- </Center>
490
- );
491
- }
492
-
493
- // Show empty state when no channels and not loading
494
- return (
495
- <Box className="p-6">
496
- <Box className="mb-6">
497
- <Heading className="text-2xl font-bold">Direct Messages</Heading>
498
- <Text className="text-gray-600 mt-1">Private conversations with other users</Text>
499
- </Box>
500
-
501
- <Input
502
- className="mb-8 h-[50] rounded-md border-gray-300 border"
503
- size="md"
504
- style={{
505
- paddingVertical: 8,
506
- marginBottom: 10,
507
- borderColor: '#d1d5db',
508
- borderRadius: 10,
509
- }}
510
- >
511
- <InputField
512
- placeholder="Search messages..."
513
- onChangeText={handleSearchChange}
514
- value={searchQuery}
515
- />
516
- </Input>
517
-
518
- <Center className="items-center" style={{ paddingVertical: 5 }}>
519
- <Box className="w-16 h-16 rounded-full bg-blue-500 flex items-center justify-center mb-5">
520
- <Ionicons name="chatbubble-ellipses" size={30} color="white" />
521
- </Box>
522
-
523
- <Text className="text-2xl font-bold text-center mb-2">No messages yet</Text>
524
-
525
- <Text className="text-gray-600 text-center mb-8">
526
- When you start conversations with others,{'\n'}
527
- they'll appear here.
528
- </Text>
529
- </Center>
530
- </Box>
531
- );
532
- }}
386
+ getItemLayout={React.useCallback((data, index) => ({ length: 80, offset: 80 * index, index }), [])}
387
+ keyExtractor={React.useCallback((item) => `channel-${item.id}`, [])}
388
+ ListEmptyComponent={ListEmptyComponent}
533
389
  />
534
390
  </Box>
535
391
  );
@@ -2,7 +2,7 @@ import React, { useCallback, useState } from 'react';
2
2
  import { FlatList, Box } from '@admin-layout/gluestack-ui-mobile';
3
3
  import { useSelector, useDispatch } from 'react-redux';
4
4
  import { useNavigation, useRoute, useIsFocused, useFocusEffect } from '@react-navigation/native';
5
- import { useSupportServiceChannelsQuery } from 'common/graphql';
5
+ import { useServiceChannelsQuery } from '../../../queries/inboxQueries';
6
6
  import { userSelector } from '@adminide-stack/user-auth0-client';
7
7
  import { CHANGE_SETTINGS_ACTION } from '@admin-layout/client';
8
8
  import { SupportServiceDialogsListItem } from '../components/SupportServiceDialogsListItem';
@@ -27,7 +27,7 @@ const SupportServiceDialogsComponent = (props: SupportServiceInboxProps) => {
27
27
  data: serviceChannels,
28
28
  loading: serviceChannelsLoading,
29
29
  refetch: getServiceChannelsRefetch,
30
- } = useSupportServiceChannelsQuery({
30
+ } = useServiceChannelsQuery({
31
31
  variables: {
32
32
  criteria: { type: 'SERVICE' },
33
33
  },