@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.
- package/CHANGELOG.md +8 -0
- package/lib/compute.js +2 -3
- package/lib/index.js.map +1 -1
- package/lib/queries/inboxQueries.js +77 -0
- package/lib/queries/inboxQueries.js.map +1 -0
- package/lib/routes.json +2 -3
- package/lib/screens/inbox/DialogThreadMessages.js +3 -7
- package/lib/screens/inbox/DialogThreadMessages.js.map +1 -1
- package/lib/screens/inbox/DialogThreads.js +3 -7
- package/lib/screens/inbox/DialogThreads.js.map +1 -1
- package/lib/screens/inbox/components/DialogsListItem.js +47 -46
- package/lib/screens/inbox/components/DialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/GiftedChatInboxComponent.js +313 -0
- package/lib/screens/inbox/components/GiftedChatInboxComponent.js.map +1 -0
- package/lib/screens/inbox/components/ServiceDialogsListItem.js +72 -57
- package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +1 -1
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js +115 -14
- package/lib/screens/inbox/components/SlackMessageContainer/SlackBubble.js.map +1 -1
- package/lib/screens/inbox/components/SubscriptionHandler.js +24 -0
- package/lib/screens/inbox/components/SubscriptionHandler.js.map +1 -0
- package/lib/screens/inbox/containers/ConversationView.js +640 -493
- package/lib/screens/inbox/containers/ConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/Dialogs.js +100 -181
- package/lib/screens/inbox/containers/Dialogs.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadConversationView.js +659 -245
- package/lib/screens/inbox/containers/ThreadConversationView.js.map +1 -1
- package/lib/screens/inbox/containers/ThreadsView.js +3 -3
- package/lib/screens/inbox/containers/ThreadsView.js.map +1 -1
- package/lib/screens/inbox/hooks/useInboxMessages.js +31 -0
- package/lib/screens/inbox/hooks/useInboxMessages.js.map +1 -0
- package/package.json +4 -4
- package/src/index.ts +2 -0
- package/src/queries/inboxQueries.ts +298 -0
- package/src/queries/index.d.ts +2 -0
- package/src/queries/index.ts +1 -0
- package/src/screens/inbox/DialogThreadMessages.tsx +3 -11
- package/src/screens/inbox/DialogThreads.tsx +3 -7
- package/src/screens/inbox/components/Actionsheet.tsx +30 -0
- package/src/screens/inbox/components/DialogsListItem.tsx +89 -148
- package/src/screens/inbox/components/ExpandableInput.tsx +460 -0
- package/src/screens/inbox/components/ExpandableInputActionSheet.tsx +518 -0
- package/src/screens/inbox/components/GiftedChatInboxComponent.tsx +411 -0
- package/src/screens/inbox/components/ServiceDialogsListItem.tsx +202 -221
- package/src/screens/inbox/components/SlackInput.tsx +23 -0
- package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +216 -30
- package/src/screens/inbox/components/SubscriptionHandler.tsx +41 -0
- package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +6 -7
- package/src/screens/inbox/containers/ConversationView.tsx +1109 -669
- package/src/screens/inbox/containers/Dialogs.tsx +198 -342
- package/src/screens/inbox/containers/SupportServiceDialogs.tsx +2 -2
- package/src/screens/inbox/containers/ThreadConversationView.tsx +1141 -402
- package/src/screens/inbox/containers/ThreadsView.tsx +5 -5
- package/src/screens/inbox/hooks/useInboxMessages.ts +34 -0
- 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 {
|
|
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 } =
|
|
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
|
-
//
|
|
63
|
+
// Memoize processChannels and sortChannels to avoid unnecessary recalculations
|
|
65
64
|
const processChannels = useCallback(
|
|
66
65
|
(rawChannels = []) => {
|
|
67
|
-
if (!rawChannels
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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;
|
|
82
|
+
return dateB - dateA;
|
|
119
83
|
});
|
|
120
84
|
}, []);
|
|
121
85
|
|
|
122
|
-
//
|
|
123
|
-
const
|
|
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
|
-
//
|
|
126
|
-
const
|
|
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
|
-
//
|
|
129
|
-
|
|
130
|
-
if (!
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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={(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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 {
|
|
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
|
-
} =
|
|
30
|
+
} = useServiceChannelsQuery({
|
|
31
31
|
variables: {
|
|
32
32
|
criteria: { type: 'SERVICE' },
|
|
33
33
|
},
|