@messenger-box/platform-mobile 10.0.3-alpha.10 → 10.0.3-alpha.101

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 (92) hide show
  1. package/CHANGELOG.md +160 -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/module.js.map +1 -1
  6. package/lib/queries/inboxQueries.js +65 -0
  7. package/lib/queries/inboxQueries.js.map +1 -0
  8. package/lib/routes.json +2 -3
  9. package/lib/screens/inbox/DialogMessages.js.map +1 -1
  10. package/lib/screens/inbox/DialogThreadMessages.js +3 -7
  11. package/lib/screens/inbox/DialogThreadMessages.js.map +1 -1
  12. package/lib/screens/inbox/DialogThreads.js +57 -18
  13. package/lib/screens/inbox/DialogThreads.js.map +1 -1
  14. package/lib/screens/inbox/Inbox.js.map +1 -1
  15. package/lib/screens/inbox/components/CachedImage/consts.js +1 -1
  16. package/lib/screens/inbox/components/CachedImage/consts.js.map +1 -1
  17. package/lib/screens/inbox/components/CachedImage/index.js +168 -46
  18. package/lib/screens/inbox/components/CachedImage/index.js.map +1 -1
  19. package/lib/screens/inbox/components/DialogItem.js +169 -0
  20. package/lib/screens/inbox/components/DialogItem.js.map +1 -0
  21. package/lib/screens/inbox/components/GiftedChatInboxComponent.js +313 -0
  22. package/lib/screens/inbox/components/GiftedChatInboxComponent.js.map +1 -0
  23. package/lib/screens/inbox/components/SlackMessageContainer/ImageViewerModal.js.map +1 -1
  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 +5 -0
  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 -44
  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 +1105 -428
  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 +724 -207
  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/index.ts +2 -0
  50. package/src/queries/inboxQueries.ts +299 -0
  51. package/src/queries/index.d.ts +2 -0
  52. package/src/queries/index.ts +1 -0
  53. package/src/screens/inbox/DialogThreadMessages.tsx +3 -11
  54. package/src/screens/inbox/DialogThreads.tsx +53 -60
  55. package/src/screens/inbox/components/Actionsheet.tsx +30 -0
  56. package/src/screens/inbox/components/CachedImage/consts.ts +4 -3
  57. package/src/screens/inbox/components/CachedImage/index.tsx +232 -61
  58. package/src/screens/inbox/components/DialogItem.tsx +306 -0
  59. package/src/screens/inbox/components/DialogsListItem.tsx +257 -176
  60. package/src/screens/inbox/components/ExpandableInput.tsx +460 -0
  61. package/src/screens/inbox/components/ExpandableInputActionSheet.tsx +518 -0
  62. package/src/screens/inbox/components/GiftedChatInboxComponent.tsx +411 -0
  63. package/src/screens/inbox/components/ServiceDialogsListItem.tsx +334 -172
  64. package/src/screens/inbox/components/SlackInput.tsx +23 -0
  65. package/src/screens/inbox/components/SlackMessageContainer/SlackBubble.tsx +233 -23
  66. package/src/screens/inbox/components/SmartLoader.tsx +61 -0
  67. package/src/screens/inbox/components/SubscriptionHandler.tsx +41 -0
  68. package/src/screens/inbox/components/SupportServiceDialogsListItem.tsx +41 -24
  69. package/src/screens/inbox/components/ThreadsViewItem.tsx +177 -265
  70. package/src/screens/inbox/components/workflow/dialogs-list-item-xstate.ts +145 -0
  71. package/src/screens/inbox/components/workflow/service-dialogs-list-item-xstate.ts +159 -0
  72. package/src/screens/inbox/config/config.ts +2 -2
  73. package/src/screens/inbox/containers/ConversationView.tsx +1843 -702
  74. package/src/screens/inbox/containers/ConversationView.tsx.bk +1467 -0
  75. package/src/screens/inbox/containers/Dialogs.tsx +400 -197
  76. package/src/screens/inbox/containers/SupportServiceDialogs.tsx +2 -2
  77. package/src/screens/inbox/containers/ThreadConversationView.tsx +1337 -301
  78. package/src/screens/inbox/containers/ThreadsView.tsx +105 -186
  79. package/src/screens/inbox/containers/workflow/apollo/handleResult.ts +20 -0
  80. package/src/screens/inbox/containers/workflow/conversation-xstate.ts +313 -0
  81. package/src/screens/inbox/containers/workflow/dialogs-xstate.ts +196 -0
  82. package/src/screens/inbox/containers/workflow/thread-conversation-xstate.ts +401 -0
  83. package/src/screens/inbox/hooks/useInboxMessages.ts +34 -0
  84. package/src/screens/inbox/hooks/useSafeDialogThreadsMachine.ts +136 -0
  85. package/src/screens/inbox/index.ts +37 -0
  86. package/src/screens/inbox/machines/threadsMachine.ts +147 -0
  87. package/src/screens/inbox/workflow/dialog-threads-xstate.ts +163 -0
  88. package/tsconfig.json +11 -54
  89. package/lib/screens/inbox/components/DialogsListItem.js +0 -159
  90. package/lib/screens/inbox/components/DialogsListItem.js.map +0 -1
  91. package/lib/screens/inbox/components/ServiceDialogsListItem.js +0 -159
  92. package/lib/screens/inbox/components/ServiceDialogsListItem.js.map +0 -1
@@ -2,51 +2,47 @@ import React, { useMemo } from 'react';
2
2
  import {
3
3
  VStack,
4
4
  Text,
5
- Image,
6
5
  Pressable,
7
6
  HStack,
8
7
  Box,
9
- AvatarGroup,
10
8
  Avatar,
11
9
  AvatarFallbackText,
12
10
  AvatarImage,
13
- AvatarBadge,
14
- View,
15
11
  Button,
16
12
  ButtonText,
13
+ Icon,
14
+ Badge,
15
+ BadgeText,
16
+ Link,
17
+ LinkText,
17
18
  } from '@admin-layout/gluestack-ui-mobile';
18
19
  import { format, isToday, isYesterday } from 'date-fns';
19
20
  import { useFocusEffect } from '@react-navigation/native';
20
21
  import { IChannel, IUserAccount } from 'common';
21
- import {
22
- useThreadMessagesQuery,
23
- useMessagesQuery,
24
- useUserAccountQuery,
25
- useOnThreadChatMessageAddedSubscription,
26
- useOnThreadCreatedUpdatedSubscription,
27
- } from 'common/lib/generated/generated.js';
28
22
  import { startCase } from 'lodash-es';
29
23
  import colors from 'tailwindcss/colors';
24
+ import { useSelector } from 'react-redux';
25
+ import { userSelector } from '@adminide-stack/user-auth0-client';
30
26
 
31
- const createdAtText = (value: string) => {
27
+ const timeFormat = (value: string) => {
32
28
  if (!value) return '';
33
29
  let date = new Date(value);
34
- if (isToday(date)) return 'Today';
35
- if (isYesterday(date)) return 'Yesterday';
36
- return format(new Date(value), 'MMM dd, yyyy');
30
+ if (isToday(date)) return format(date, 'h:mm a').toUpperCase();
31
+ return format(date, 'MMM do').toUpperCase();
37
32
  };
38
33
 
39
34
  export interface IDialogListChannel extends IChannel {
40
35
  users: IUserAccount[];
41
36
  }
42
37
 
43
- export interface IDialogListItemProps {
44
- currentUser?: any;
45
- users?: any;
46
- thread?: any;
47
- onOpen: (id: any, title: any, postParentId?: any) => void;
48
- role?: any;
49
- setData?: any;
38
+ export interface ThreadViewItemProps {
39
+ id: string;
40
+ post: any;
41
+ channel: any;
42
+ replies: any[];
43
+ onPress: (id: any, title: any, postParentId?: any) => void;
44
+ channelId?: string;
45
+ channelsDetail?: any;
50
46
  }
51
47
 
52
48
  /**
@@ -54,267 +50,183 @@ export interface IDialogListItemProps {
54
50
  * - Get Reservation info: reservation date, status
55
51
  * - Add ability to get property information: name, logo
56
52
  */
57
- export const ThreadViewItemComponent: React.FC<IDialogListItemProps> = function DialogsListItem({
58
- currentUser,
59
- // users,
60
- thread,
61
- onOpen,
62
- role,
63
- setData,
53
+ export const ThreadViewItemComponent: React.FC<ThreadViewItemProps> = function ThreadViewItem({
54
+ id,
55
+ post,
56
+ channel,
57
+ replies,
58
+ onPress,
59
+ channelId,
60
+ channelsDetail,
64
61
  }) {
65
- // const { data: threadCreatedUpdated } = useOnThreadCreatedUpdatedSubscription({
66
- // variables: {
67
- // channelId: thread?.channel?.id?.toString(),
68
- // postParentId: thread?.post?.id?.toString(),
69
- // },
70
- // });
71
-
72
- useFocusEffect(
73
- React.useCallback(() => {
74
- // Do something when the screen is focused
75
- return () => {
76
- // Do something when the screen is unfocused
77
- // Useful for cleanup functions
78
- };
79
- }, []),
80
- );
81
-
82
- const threadPostReply = React.useMemo(() => {
83
- if (!thread?.replies) return null;
84
- return thread?.replies;
85
- }, [thread]);
62
+ const currentUser = useSelector(userSelector);
86
63
 
87
- // React.useEffect(() => {
88
- // if (threadCreatedUpdated?.threadCreatedUpdated?.data) setData(threadCreatedUpdated?.threadCreatedUpdated?.data);
89
- // }, [threadCreatedUpdated?.threadCreatedUpdated]);
64
+ // Prepare thread replies for display
65
+ const threadReplies = useMemo(() => {
66
+ return replies || [];
67
+ }, [replies]);
90
68
 
91
- // const threadPostReply = React.useMemo(() => {
92
- // if (!thread?.replies) return null;
93
- // if (threadCreatedUpdated?.threadCreatedUpdated?.data?.replies?.length) {
94
- // return threadCreatedUpdated?.threadCreatedUpdated?.data?.replies;
95
- // } else return thread?.replies;
96
- // }, [thread, threadCreatedUpdated]);
97
-
98
- // const lastMessage = useMemo(() => {
99
- // if (!threadPost) {
100
- // return null;
101
- // }
102
- // const replies = threadPost?.replies?.filter((p: any) => p?.message !== '') ?? [];
103
- // if (replies?.length) {
104
- // return replies[0];
105
- // // return replies[replies.length - 1];
106
- // } else {
107
- // const post = threadPost?.post ?? null;
108
- // return post ? post?.post : null;
109
- // }
110
- // }, [threadPost]);
69
+ if (!threadReplies || threadReplies.length === 0) {
70
+ return null;
71
+ }
111
72
 
112
- // const participants: any = React.useMemo(() => {
113
- // if (!thread?.participants?.length) return null;
114
- // return thread?.participants?.filter((u: any) => u?.user?.id !== currentUser?.id) ?? [];
115
- // }, [thread]);
73
+ // Get the last reply for the thread preview
74
+ const lastReply = threadReplies[0]; // Most recent reply should be first in the array
75
+ // Get total number of replies
76
+ const totalReplies = threadReplies.length;
116
77
 
117
- // const allParticipantsNames: any = React.useMemo(() => {
118
- // if (!participants?.length) return null;
119
- // return (
120
- // participants
121
- // ?.map((p: any) => {
122
- // return p?.user?.givenName + ' ' + p?.user?.familyName ?? '';
123
- // })
124
- // ?.join(', ') ?? ''
125
- // );
126
- // }, [participants]);
78
+ // Get the first user in the thread as the primary user
79
+ const primaryUser = channel?.users?.[0] || lastReply?.author;
80
+ const channelName = channel?.title || `${primaryUser?.givenName || ''} ${primaryUser?.familyName || ''}`;
127
81
 
128
- if (!threadPostReply || threadPostReply?.length == 0) {
129
- return <></>;
130
- }
82
+ // Determine if the current user is in the thread
83
+ const userIsInThread = channel?.users?.some((user: any) => user.id === currentUser?.id);
131
84
 
132
85
  return (
133
- <VStack
134
- space={'sm'}
135
- className="flex-1 py-2 px-2 border rounded-md bg-white border-gray-200 dark:border-white dark:bg-white"
86
+ <Pressable
87
+ onPress={() => onPress(channel?.id, 'Thread', post?.id)}
88
+ style={{
89
+ marginVertical: 5,
90
+ backgroundColor: 'white',
91
+ borderRadius: 10,
92
+ paddingVertical: 5,
93
+ elevation: 1,
94
+ shadowColor: '#000',
95
+ shadowOffset: { width: 0, height: 1 },
96
+ shadowOpacity: 0.1,
97
+ shadowRadius: 2,
98
+ }}
136
99
  >
137
- <HStack space={'sm'} className="py-2 w-[100%] flex-1 justify-between items-center">
138
- <Box className="flex-1">
139
- {/* <HStack flex={1} space={5} py={2} alignItems={'baseline'}>
140
- <Box>
141
- <Avatar.Badge size={4} left={0} zIndex={1} bg="green.800" />
142
- </Box>
143
- <Box>
144
- <Text color="gray.600" fontSize="lg" flexWrap={'wrap'} fontWeight="semibold">
145
- {allParticipantsNames || ''}
146
- </Text>
147
- </Box>
100
+ <Box className="mb-3">
101
+ <VStack space="md">
102
+ {/* Thread header with green dot and users */}
103
+ {/* <HStack space="sm" className="px-4 items-center">
104
+ <Box
105
+ className="bg-green-500 rounded-full"
106
+ style={{ width: 8, height: 8 }}
107
+ />
108
+ <Text className="text-base text-gray-900">{channelName}</Text>
148
109
  </HStack> */}
149
- <HStack space={'sm'} className="flex-1 justify-center items-center">
150
- <Box className="flex-1">
151
- {/* <HStack space={2} py={2}>
152
- <Box>
110
+
111
+ {/* Thread members */}
112
+ <Text className="text-sm font-medium text-gray-600 px-4 pt-2">
113
+ {channel?.users?.map((user: any) => user?.givenName || user?.username)?.join(', ')}
114
+ </Text>
115
+
116
+ {/* Thread messages preview - show up to 3 most recent messages */}
117
+ {threadReplies.slice(0, 3).map((reply: any, index: number) => {
118
+ // Group consecutive messages from the same user
119
+ const previousReply = index > 0 ? threadReplies.slice(0, 3)[index - 1] : null;
120
+ const isConsecutiveReply = previousReply && previousReply.author?.id === reply.author?.id;
121
+
122
+ return (
123
+ <HStack key={reply.id || index} space="md" className="px-4 py-1">
124
+ {!isConsecutiveReply ? (
153
125
  <Avatar
154
- bg={'transparent'}
155
- size={8}
156
- _image={{ borderRadius: 6, borderWidth: 2, borderColor: '#fff' }}
157
- source={{
158
- uri: thread?.post?.author?.picture,
126
+ size="md"
127
+ style={{
128
+ width: 40,
129
+ height: 40,
130
+ backgroundColor:
131
+ reply?.author?.id === currentUser?.id ? '#2EB67D' : '#E8A54A',
159
132
  }}
160
133
  >
161
- {startCase(thread?.post?.author?.username?.charAt(0))}
134
+ <AvatarFallbackText>
135
+ {startCase(reply?.author?.username?.charAt(0) || 'U')}
136
+ </AvatarFallbackText>
137
+ {reply?.author?.picture && (
138
+ <AvatarImage
139
+ alt={reply?.author?.username || 'User'}
140
+ source={{
141
+ uri: reply?.author?.picture,
142
+ }}
143
+ />
144
+ )}
162
145
  </Avatar>
163
- </Box>
164
- <Box>
165
- <HStack space={4}>
166
- <Text>
167
- {thread?.post?.author?.givenName + ' ' + thread?.post?.author?.familyName ??
168
- ''}
169
- </Text>
170
- <Text color="gray.500">
171
- {thread?.post ? createdAtText(thread?.post?.createdAt) : ''}
172
- </Text>
173
- </HStack>
174
- <Text color="gray.600" noOfLines={2}>
175
- {thread?.post?.message ?? ''}
176
- </Text>
177
- {!thread?.replies?.length && (
178
- <Button
179
- size={'xs'}
180
- w={150}
181
- variant={'outline'}
182
- _text={{ fontSize: 12, color: '#000' }}
183
- onPress={() =>
184
- onOpen(thread?.channel?.id, thread?.channel?.title, thread?.post?.id)
185
- }
186
- >
187
- Reply
188
- </Button>
146
+ ) : (
147
+ <Box style={{ width: 40 }} />
148
+ )}
149
+
150
+ <VStack space="xs" className="flex-1">
151
+ {!isConsecutiveReply && (
152
+ <HStack space="sm" className="items-center">
153
+ <Text className="font-bold text-gray-900">
154
+ {reply?.author?.givenName || reply?.author?.username || 'User'}
155
+ </Text>
156
+ <Text className="text-xs text-gray-500">
157
+ {timeFormat(reply?.createdAt)}
158
+ </Text>
159
+ </HStack>
189
160
  )}
190
- </Box>
191
- </HStack> */}
192
- {threadPostReply?.length > 0 && (
193
- <>
194
- {threadPostReply
195
- ?.slice(0, 2)
196
- ?.reverse()
197
- ?.map((reply: any, index: any) => (
198
- <HStack key={index} space={'sm'} className="pb-2 pt-1">
199
- <Box>
200
- <Avatar
201
- key={'thread-view-key-' + index}
202
- size={'md'}
203
- className="bg-transparent top-0 right-0 z-[1]"
204
- >
205
- <AvatarFallbackText>
206
- {' '}
207
- {startCase(reply?.author?.username?.charAt(0))}
208
- </AvatarFallbackText>
209
- <AvatarImage
210
- alt="image"
211
- style={{
212
- borderRadius: 6,
213
- borderWidth: 2,
214
- borderColor: '#fff',
215
- }}
216
- source={{
217
- uri: reply?.author?.picture,
218
- }}
219
- />
220
- </Avatar>
221
- </Box>
222
- <Box>
223
- <HStack space={'md'}>
224
- <Text>
225
- {reply?.author?.givenName ?? '' ?? ''}{' '}
226
- {reply?.author?.familyName ?? '' ?? ''}
227
- {/* {lastMessage?.author?.givenName +
228
- ' ' +
229
- lastMessage?.author?.familyName ?? ''} */}
230
- </Text>
231
- <Text color={colors.gray[500]}>
232
- {reply?.createdAt ? createdAtText(reply?.createdAt) : ''}
233
- {/* {lastMessage ? createdAtText(lastMessage?.createdAt) : ''} */}
234
- </Text>
235
- </HStack>
236
- <VStack space={'sm'}>
237
- {reply?.message && (
238
- <Text color={colors.gray[600]} numberOfLines={2}>
239
- {reply?.message.length < 70
240
- ? `${reply?.message}`
241
- : `${reply?.message.substring(0, 68)}....`}
242
- </Text>
243
- )}
244
- {/* <Text color="gray.600" noOfLines={2}>
245
- {reply?.message
246
- ? reply?.message.length < 70
247
- ? `${reply?.message}`
248
- : `${reply?.message.substring(0, 68)}....`
249
- : ''}
250
- </Text> */}
251
161
 
252
- <>
253
- {reply?.files?.data?.length > 0 &&
254
- reply?.files?.data?.map((f: any, index: any) => (
255
- <Box>
256
- <Avatar
257
- key={'thread-view-other-key-' + index}
258
- className="bg-transparent"
259
- size={'md'}
260
- >
261
- <AvatarFallbackText> I</AvatarFallbackText>
262
- <AvatarImage
263
- alt="image"
264
- style={{
265
- borderRadius: 6,
266
- borderWidth: 2,
267
- borderColor: '#fff',
268
- }}
269
- source={{
270
- uri: f?.url,
271
- }}
272
- />
273
- </Avatar>
274
- </Box>
275
- ))}
276
- </>
277
- </VStack>
162
+ {reply?.message && (
163
+ <Text color={colors.gray[700]} numberOfLines={2} className="text-base">
164
+ {reply?.message}
165
+ {reply?.edited && <Text className="text-xs text-gray-500"> (edited)</Text>}
166
+ </Text>
167
+ )}
278
168
 
279
- {(threadPostReply?.length == 1 || index !== 0) && (
280
- <Button
281
- size={'xs'}
282
- className="w-[150]"
283
- variant={'outline'}
284
- onPress={() =>
285
- onOpen(
286
- thread?.channel?.id,
287
- thread?.channel?.title,
288
- thread?.post?.id,
289
- )
290
- }
291
- >
292
- <ButtonText style={{ fontSize: 12, color: '#000' }}>
293
- Reply
294
- </ButtonText>
295
- </Button>
296
- )}
169
+ {reply.email && <Text className="text-blue-500">{reply.email}</Text>}
170
+
171
+ {reply?.files?.data?.length > 0 && (
172
+ <HStack space="sm" className="my-1">
173
+ {reply?.files?.data?.map((file: any, fileIndex: number) => (
174
+ <Box
175
+ key={fileIndex}
176
+ className="overflow-hidden"
177
+ style={{ width: 80, height: 80 }}
178
+ >
179
+ <AvatarImage
180
+ alt="attachment"
181
+ className="rounded-none border-none"
182
+ style={{
183
+ width: '100%',
184
+ height: '100%',
185
+ }}
186
+ source={{
187
+ uri: file?.url,
188
+ }}
189
+ />
297
190
  </Box>
298
- </HStack>
299
- ))}
300
- </>
301
- )}
302
- </Box>
303
- </HStack>
304
- </Box>
305
- </HStack>
306
- {/* <HStack>
307
- <Box flex={0.06} alignItems={'flex-start'}></Box>
308
- <Button
309
- size={'xs'}
310
- variant={'outline'}
311
- _text={{ fontSize: 12, color: '#000' }}
312
- onPress={() => onOpen(thread?.channel?.id, thread?.channel?.title, postParentId)}
313
- >
314
- Reply
315
- </Button>
316
- </HStack> */}
317
- </VStack>
191
+ ))}
192
+ </HStack>
193
+ )}
194
+ </VStack>
195
+ </HStack>
196
+ );
197
+ })}
198
+
199
+ {/* Show more replies indicator */}
200
+ {totalReplies > 3 && (
201
+ <HStack className="px-4 items-center" space="sm">
202
+ <Box style={{ width: 40 }} />
203
+ <Link onPress={() => onPress(channel?.id, 'Thread', post?.id)}>
204
+ <LinkText className="text-blue-600 mt-1">
205
+ {totalReplies - 3} more {totalReplies - 3 === 1 ? 'reply' : 'replies'}
206
+ </LinkText>
207
+ </Link>
208
+ </HStack>
209
+ )}
210
+
211
+ {/* Reply button */}
212
+ <Box className="px-4 pb-2">
213
+ <Button
214
+ size="sm"
215
+ className="self-start rounded-full"
216
+ variant="outline"
217
+ style={{
218
+ borderColor: '#E2E8F0',
219
+ paddingHorizontal: 16,
220
+ paddingVertical: 6,
221
+ }}
222
+ onPress={() => onPress(channel?.id, 'Thread', post?.id)}
223
+ >
224
+ <ButtonText style={{ fontSize: 14, color: colors.gray[800] }}>Reply</ButtonText>
225
+ </Button>
226
+ </Box>
227
+ </VStack>
228
+ </Box>
229
+ </Pressable>
318
230
  );
319
231
  };
320
232
 
@@ -0,0 +1,145 @@
1
+ import { assign, setup } from 'xstate';
2
+ import { merge } from 'lodash-es';
3
+
4
+ export const enum Actions {
5
+ INITIAL_CONTEXT = 'INITIAL_CONTEXT',
6
+ ERROR_HANDLED = 'ERROR_HANDLED',
7
+ FETCH_MESSAGES = 'FETCH_MESSAGES',
8
+ UPDATE_MESSAGES = 'UPDATE_MESSAGES',
9
+ SUBSCRIBE_TO_MESSAGES = 'SUBSCRIBE_TO_MESSAGES',
10
+ SET_TITLE = 'SET_TITLE',
11
+ START_LOADING = 'START_LOADING',
12
+ STOP_LOADING = 'STOP_LOADING',
13
+ }
14
+
15
+ export const enum BaseState {
16
+ Idle = 'idle',
17
+ Error = 'error',
18
+ FetchingMessages = 'fetchingMessages',
19
+ }
20
+
21
+ export const dialogsListItemXstate = setup({
22
+ types: {
23
+ context: {} as {
24
+ channelId: string | null;
25
+ currentUser: any;
26
+ messages: any[];
27
+ loading: boolean;
28
+ error: string | null;
29
+ title: string;
30
+ channelMembers: any[];
31
+ lastMessage: any;
32
+ },
33
+ },
34
+ actions: {
35
+ errorState: assign(({ context, event }) => {
36
+ return {
37
+ ...context,
38
+ error: event.data?.message || 'An error occurred',
39
+ loading: false,
40
+ };
41
+ }),
42
+ setInitialContext: assign(({ context, event }) => {
43
+ return merge({
44
+ ...context,
45
+ channelId: event.data?.channelId || null,
46
+ currentUser: event.data?.currentUser || null,
47
+ loading: true,
48
+ });
49
+ }),
50
+ setMessages: assign(({ context, event }) => {
51
+ return {
52
+ ...context,
53
+ messages: event.data?.messages || [],
54
+ loading: false,
55
+ };
56
+ }),
57
+ updateMessages: assign(({ context, event }) => {
58
+ const newMessage = event.data?.message;
59
+ if (!newMessage) return context;
60
+
61
+ return {
62
+ ...context,
63
+ messages: [...context.messages, newMessage],
64
+ };
65
+ }),
66
+ setTitle: assign(({ context, event }) => {
67
+ return {
68
+ ...context,
69
+ title: event.data?.title || '',
70
+ };
71
+ }),
72
+ startLoading: assign(({ context }) => {
73
+ return {
74
+ ...context,
75
+ loading: true,
76
+ };
77
+ }),
78
+ stopLoading: assign(({ context }) => {
79
+ return {
80
+ ...context,
81
+ loading: false,
82
+ };
83
+ }),
84
+ },
85
+ }).createMachine({
86
+ id: 'dialogs-list-item',
87
+ initial: BaseState.Idle,
88
+ context: {
89
+ channelId: null,
90
+ currentUser: null,
91
+ messages: [],
92
+ loading: false,
93
+ error: null,
94
+ title: '',
95
+ channelMembers: [],
96
+ lastMessage: null,
97
+ },
98
+ states: {
99
+ [BaseState.Idle]: {
100
+ on: {
101
+ [Actions.INITIAL_CONTEXT]: {
102
+ target: BaseState.FetchingMessages,
103
+ actions: ['setInitialContext'],
104
+ },
105
+ [Actions.UPDATE_MESSAGES]: {
106
+ target: BaseState.Idle,
107
+ actions: ['updateMessages'],
108
+ },
109
+ [Actions.SET_TITLE]: {
110
+ target: BaseState.Idle,
111
+ actions: ['setTitle'],
112
+ },
113
+ [Actions.START_LOADING]: {
114
+ target: BaseState.Idle,
115
+ actions: ['startLoading'],
116
+ },
117
+ [Actions.STOP_LOADING]: {
118
+ target: BaseState.Idle,
119
+ actions: ['stopLoading'],
120
+ },
121
+ },
122
+ },
123
+ [BaseState.Error]: {
124
+ entry: ['errorState'],
125
+ on: {
126
+ [Actions.ERROR_HANDLED]: {
127
+ target: BaseState.Idle,
128
+ },
129
+ },
130
+ },
131
+ [BaseState.FetchingMessages]: {
132
+ invoke: {
133
+ src: 'fetchMessages',
134
+ input: ({ context, event }) => ({ context, event }),
135
+ onDone: {
136
+ target: BaseState.Idle,
137
+ actions: ['setMessages'],
138
+ },
139
+ onError: {
140
+ target: BaseState.Error,
141
+ },
142
+ },
143
+ },
144
+ },
145
+ } as any);