rn-erxes-sdk 0.1.22 → 0.1.24

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 (233) hide show
  1. package/README.md +40 -2
  2. package/lib/commonjs/App.js +41 -21
  3. package/lib/commonjs/App.js.map +1 -1
  4. package/lib/commonjs/Widget.js +158 -31
  5. package/lib/commonjs/Widget.js.map +1 -1
  6. package/lib/commonjs/components/Avatar.js +24 -27
  7. package/lib/commonjs/components/Avatar.js.map +1 -1
  8. package/lib/commonjs/components/AvatarWithStatus.js +56 -0
  9. package/lib/commonjs/components/AvatarWithStatus.js.map +1 -0
  10. package/lib/commonjs/components/ConversationItem.js +161 -0
  11. package/lib/commonjs/components/ConversationItem.js.map +1 -0
  12. package/lib/commonjs/components/Icons.js +558 -0
  13. package/lib/commonjs/components/Icons.js.map +1 -0
  14. package/lib/commonjs/components/InputTools.js +329 -38
  15. package/lib/commonjs/components/InputTools.js.map +1 -1
  16. package/lib/commonjs/components/MessengerShell.js +64 -0
  17. package/lib/commonjs/components/MessengerShell.js.map +1 -0
  18. package/lib/commonjs/components/conversation/DateSeparator.js +47 -0
  19. package/lib/commonjs/components/conversation/DateSeparator.js.map +1 -0
  20. package/lib/commonjs/components/conversation/EmojiPicker.js +62 -0
  21. package/lib/commonjs/components/conversation/EmojiPicker.js.map +1 -0
  22. package/lib/commonjs/components/conversation/PersistentMenu.js +64 -0
  23. package/lib/commonjs/components/conversation/PersistentMenu.js.map +1 -0
  24. package/lib/commonjs/components/conversation/TypingStatus.js +122 -0
  25. package/lib/commonjs/components/conversation/TypingStatus.js.map +1 -0
  26. package/lib/commonjs/components/conversation/WelcomeMessage.js +85 -0
  27. package/lib/commonjs/components/conversation/WelcomeMessage.js.map +1 -0
  28. package/lib/commonjs/components/nav/Navigation.js +130 -0
  29. package/lib/commonjs/components/nav/Navigation.js.map +1 -0
  30. package/lib/commonjs/components/nav/constants.js +23 -0
  31. package/lib/commonjs/components/nav/constants.js.map +1 -0
  32. package/lib/commonjs/graphql/ApolloContainer.js +1 -2
  33. package/lib/commonjs/graphql/ApolloContainer.js.map +1 -1
  34. package/lib/commonjs/graphql/mutation.js +58 -23
  35. package/lib/commonjs/graphql/mutation.js.map +1 -1
  36. package/lib/commonjs/graphql/query.js +85 -55
  37. package/lib/commonjs/graphql/query.js.map +1 -1
  38. package/lib/commonjs/graphql/subscription.js +32 -28
  39. package/lib/commonjs/graphql/subscription.js.map +1 -1
  40. package/lib/commonjs/screen/conversation/Attachment.js +77 -12
  41. package/lib/commonjs/screen/conversation/Attachment.js.map +1 -1
  42. package/lib/commonjs/screen/conversation/ConversationDetail.js +307 -136
  43. package/lib/commonjs/screen/conversation/ConversationDetail.js.map +1 -1
  44. package/lib/commonjs/screen/conversation/Message.js +145 -82
  45. package/lib/commonjs/screen/conversation/Message.js.map +1 -1
  46. package/lib/commonjs/screen/faq/Faq.js +373 -0
  47. package/lib/commonjs/screen/faq/Faq.js.map +1 -0
  48. package/lib/commonjs/screen/greetings/Social.js +19 -9
  49. package/lib/commonjs/screen/greetings/Social.js.map +1 -1
  50. package/lib/commonjs/screen/greetings/Supporters.js +49 -46
  51. package/lib/commonjs/screen/greetings/Supporters.js.map +1 -1
  52. package/lib/commonjs/screen/home/Home.js +246 -0
  53. package/lib/commonjs/screen/home/Home.js.map +1 -0
  54. package/lib/commonjs/screen/messages/Messages.js +312 -0
  55. package/lib/commonjs/screen/messages/Messages.js.map +1 -0
  56. package/lib/commonjs/theme.js +132 -0
  57. package/lib/commonjs/theme.js.map +1 -0
  58. package/lib/commonjs/utils/messages.js +93 -0
  59. package/lib/commonjs/utils/messages.js.map +1 -0
  60. package/lib/commonjs/utils/onlineHours.js +127 -0
  61. package/lib/commonjs/utils/onlineHours.js.map +1 -0
  62. package/lib/commonjs/utils/upload.js +245 -0
  63. package/lib/commonjs/utils/upload.js.map +1 -0
  64. package/lib/module/App.js +41 -21
  65. package/lib/module/App.js.map +1 -1
  66. package/lib/module/Widget.js +161 -34
  67. package/lib/module/Widget.js.map +1 -1
  68. package/lib/module/components/Avatar.js +25 -28
  69. package/lib/module/components/Avatar.js.map +1 -1
  70. package/lib/module/components/AvatarWithStatus.js +47 -0
  71. package/lib/module/components/AvatarWithStatus.js.map +1 -0
  72. package/lib/module/components/ConversationItem.js +153 -0
  73. package/lib/module/components/ConversationItem.js.map +1 -0
  74. package/lib/module/components/Icons.js +537 -0
  75. package/lib/module/components/Icons.js.map +1 -0
  76. package/lib/module/components/InputTools.js +331 -39
  77. package/lib/module/components/InputTools.js.map +1 -1
  78. package/lib/module/components/MessengerShell.js +55 -0
  79. package/lib/module/components/MessengerShell.js.map +1 -0
  80. package/lib/module/components/conversation/DateSeparator.js +40 -0
  81. package/lib/module/components/conversation/DateSeparator.js.map +1 -0
  82. package/lib/module/components/conversation/EmojiPicker.js +55 -0
  83. package/lib/module/components/conversation/EmojiPicker.js.map +1 -0
  84. package/lib/module/components/conversation/PersistentMenu.js +57 -0
  85. package/lib/module/components/conversation/PersistentMenu.js.map +1 -0
  86. package/lib/module/components/conversation/TypingStatus.js +113 -0
  87. package/lib/module/components/conversation/TypingStatus.js.map +1 -0
  88. package/lib/module/components/conversation/WelcomeMessage.js +76 -0
  89. package/lib/module/components/conversation/WelcomeMessage.js.map +1 -0
  90. package/lib/module/components/nav/Navigation.js +121 -0
  91. package/lib/module/components/nav/Navigation.js.map +1 -0
  92. package/lib/module/components/nav/constants.js +16 -0
  93. package/lib/module/components/nav/constants.js.map +1 -0
  94. package/lib/module/graphql/ApolloContainer.js +1 -2
  95. package/lib/module/graphql/ApolloContainer.js.map +1 -1
  96. package/lib/module/graphql/mutation.js +57 -23
  97. package/lib/module/graphql/mutation.js.map +1 -1
  98. package/lib/module/graphql/query.js +83 -52
  99. package/lib/module/graphql/query.js.map +1 -1
  100. package/lib/module/graphql/subscription.js +31 -28
  101. package/lib/module/graphql/subscription.js.map +1 -1
  102. package/lib/module/screen/conversation/Attachment.js +79 -14
  103. package/lib/module/screen/conversation/Attachment.js.map +1 -1
  104. package/lib/module/screen/conversation/ConversationDetail.js +311 -139
  105. package/lib/module/screen/conversation/ConversationDetail.js.map +1 -1
  106. package/lib/module/screen/conversation/Message.js +145 -82
  107. package/lib/module/screen/conversation/Message.js.map +1 -1
  108. package/lib/module/screen/faq/Faq.js +363 -0
  109. package/lib/module/screen/faq/Faq.js.map +1 -0
  110. package/lib/module/screen/greetings/Social.js +19 -9
  111. package/lib/module/screen/greetings/Social.js.map +1 -1
  112. package/lib/module/screen/greetings/Supporters.js +50 -46
  113. package/lib/module/screen/greetings/Supporters.js.map +1 -1
  114. package/lib/module/screen/home/Home.js +236 -0
  115. package/lib/module/screen/home/Home.js.map +1 -0
  116. package/lib/module/screen/messages/Messages.js +303 -0
  117. package/lib/module/screen/messages/Messages.js.map +1 -0
  118. package/lib/module/theme.js +122 -0
  119. package/lib/module/theme.js.map +1 -0
  120. package/lib/module/utils/messages.js +81 -0
  121. package/lib/module/utils/messages.js.map +1 -0
  122. package/lib/module/utils/onlineHours.js +118 -0
  123. package/lib/module/utils/onlineHours.js.map +1 -0
  124. package/lib/module/utils/upload.js +233 -0
  125. package/lib/module/utils/upload.js.map +1 -0
  126. package/lib/typescript/App.d.ts +2 -1
  127. package/lib/typescript/App.d.ts.map +1 -1
  128. package/lib/typescript/Widget.d.ts.map +1 -1
  129. package/lib/typescript/components/Avatar.d.ts +7 -1
  130. package/lib/typescript/components/Avatar.d.ts.map +1 -1
  131. package/lib/typescript/components/AvatarWithStatus.d.ts +11 -0
  132. package/lib/typescript/components/AvatarWithStatus.d.ts.map +1 -0
  133. package/lib/typescript/components/ConversationItem.d.ts +9 -0
  134. package/lib/typescript/components/ConversationItem.d.ts.map +1 -0
  135. package/lib/typescript/components/Icons.d.ts +33 -0
  136. package/lib/typescript/components/Icons.d.ts.map +1 -0
  137. package/lib/typescript/components/InputTools.d.ts.map +1 -1
  138. package/lib/typescript/components/MessengerShell.d.ts +4 -0
  139. package/lib/typescript/components/MessengerShell.d.ts.map +1 -0
  140. package/lib/typescript/components/conversation/DateSeparator.d.ts +6 -0
  141. package/lib/typescript/components/conversation/DateSeparator.d.ts.map +1 -0
  142. package/lib/typescript/components/conversation/EmojiPicker.d.ts +7 -0
  143. package/lib/typescript/components/conversation/EmojiPicker.d.ts.map +1 -0
  144. package/lib/typescript/components/conversation/PersistentMenu.d.ts +13 -0
  145. package/lib/typescript/components/conversation/PersistentMenu.d.ts.map +1 -0
  146. package/lib/typescript/components/conversation/TypingStatus.d.ts +4 -0
  147. package/lib/typescript/components/conversation/TypingStatus.d.ts.map +1 -0
  148. package/lib/typescript/components/conversation/WelcomeMessage.d.ts +6 -0
  149. package/lib/typescript/components/conversation/WelcomeMessage.d.ts.map +1 -0
  150. package/lib/typescript/components/nav/Navigation.d.ts +12 -0
  151. package/lib/typescript/components/nav/Navigation.d.ts.map +1 -0
  152. package/lib/typescript/components/nav/constants.d.ts +7 -0
  153. package/lib/typescript/components/nav/constants.d.ts.map +1 -0
  154. package/lib/typescript/graphql/ApolloContainer.d.ts.map +1 -1
  155. package/lib/typescript/graphql/mutation.d.ts +2 -1
  156. package/lib/typescript/graphql/mutation.d.ts.map +1 -1
  157. package/lib/typescript/graphql/query.d.ts +2 -3
  158. package/lib/typescript/graphql/query.d.ts.map +1 -1
  159. package/lib/typescript/graphql/subscription.d.ts +2 -1
  160. package/lib/typescript/graphql/subscription.d.ts.map +1 -1
  161. package/lib/typescript/screen/conversation/Attachment.d.ts.map +1 -1
  162. package/lib/typescript/screen/conversation/ConversationDetail.d.ts.map +1 -1
  163. package/lib/typescript/screen/conversation/Message.d.ts.map +1 -1
  164. package/lib/typescript/screen/faq/Faq.d.ts +4 -0
  165. package/lib/typescript/screen/faq/Faq.d.ts.map +1 -0
  166. package/lib/typescript/screen/greetings/Social.d.ts +1 -1
  167. package/lib/typescript/screen/greetings/Social.d.ts.map +1 -1
  168. package/lib/typescript/screen/greetings/Supporters.d.ts.map +1 -1
  169. package/lib/typescript/screen/home/Home.d.ts +4 -0
  170. package/lib/typescript/screen/home/Home.d.ts.map +1 -0
  171. package/lib/typescript/screen/messages/Messages.d.ts +4 -0
  172. package/lib/typescript/screen/messages/Messages.d.ts.map +1 -0
  173. package/lib/typescript/theme.d.ts +93 -0
  174. package/lib/typescript/theme.d.ts.map +1 -0
  175. package/lib/typescript/utils/messages.d.ts +27 -0
  176. package/lib/typescript/utils/messages.d.ts.map +1 -0
  177. package/lib/typescript/utils/onlineHours.d.ts +19 -0
  178. package/lib/typescript/utils/onlineHours.d.ts.map +1 -0
  179. package/lib/typescript/utils/upload.d.ts +32 -0
  180. package/lib/typescript/utils/upload.d.ts.map +1 -0
  181. package/package.json +25 -21
  182. package/src/App.tsx +48 -24
  183. package/src/Widget.tsx +214 -40
  184. package/src/components/Avatar.tsx +30 -30
  185. package/src/components/AvatarWithStatus.tsx +63 -0
  186. package/src/components/ConversationItem.tsx +199 -0
  187. package/src/components/Icons.tsx +532 -0
  188. package/src/components/InputTools.tsx +387 -48
  189. package/src/components/MessengerShell.tsx +58 -0
  190. package/src/components/conversation/DateSeparator.tsx +36 -0
  191. package/src/components/conversation/EmojiPicker.tsx +111 -0
  192. package/src/components/conversation/PersistentMenu.tsx +74 -0
  193. package/src/components/conversation/TypingStatus.tsx +112 -0
  194. package/src/components/conversation/WelcomeMessage.tsx +70 -0
  195. package/src/components/nav/Navigation.tsx +137 -0
  196. package/src/components/nav/constants.ts +16 -0
  197. package/src/graphql/ApolloContainer.tsx +0 -1
  198. package/src/graphql/mutation.ts +58 -22
  199. package/src/graphql/query.ts +83 -55
  200. package/src/graphql/subscription.ts +31 -27
  201. package/src/screen/conversation/Attachment.tsx +123 -33
  202. package/src/screen/conversation/ConversationDetail.tsx +367 -134
  203. package/src/screen/conversation/Message.tsx +160 -98
  204. package/src/screen/faq/Faq.tsx +425 -0
  205. package/src/screen/greetings/Social.tsx +18 -4
  206. package/src/screen/greetings/Supporters.tsx +56 -41
  207. package/src/screen/home/Home.tsx +276 -0
  208. package/src/screen/messages/Messages.tsx +331 -0
  209. package/src/theme.ts +104 -0
  210. package/src/utils/messages.ts +105 -0
  211. package/src/utils/onlineHours.ts +167 -0
  212. package/src/utils/upload.ts +326 -0
  213. package/lib/commonjs/components/FAB.js +0 -51
  214. package/lib/commonjs/components/FAB.js.map +0 -1
  215. package/lib/commonjs/screen/conversation/Conversations.js +0 -158
  216. package/lib/commonjs/screen/conversation/Conversations.js.map +0 -1
  217. package/lib/commonjs/screen/greetings/Greetings.js +0 -96
  218. package/lib/commonjs/screen/greetings/Greetings.js.map +0 -1
  219. package/lib/module/components/FAB.js +0 -42
  220. package/lib/module/components/FAB.js.map +0 -1
  221. package/lib/module/screen/conversation/Conversations.js +0 -147
  222. package/lib/module/screen/conversation/Conversations.js.map +0 -1
  223. package/lib/module/screen/greetings/Greetings.js +0 -85
  224. package/lib/module/screen/greetings/Greetings.js.map +0 -1
  225. package/lib/typescript/components/FAB.d.ts +0 -16
  226. package/lib/typescript/components/FAB.d.ts.map +0 -1
  227. package/lib/typescript/screen/conversation/Conversations.d.ts +0 -4
  228. package/lib/typescript/screen/conversation/Conversations.d.ts.map +0 -1
  229. package/lib/typescript/screen/greetings/Greetings.d.ts +0 -4
  230. package/lib/typescript/screen/greetings/Greetings.d.ts.map +0 -1
  231. package/src/components/FAB.tsx +0 -69
  232. package/src/screen/conversation/Conversations.tsx +0 -148
  233. package/src/screen/greetings/Greetings.tsx +0 -98
@@ -1,4 +1,3 @@
1
- /* eslint-disable react-native/no-inline-styles */
2
1
  import {
3
2
  View,
4
3
  Text,
@@ -12,16 +11,87 @@ import {
12
11
  Keyboard,
13
12
  Image,
14
13
  SafeAreaView,
14
+ StatusBar,
15
15
  } from 'react-native';
16
- import React, { useContext, useEffect } from 'react';
17
- import { useMutation, useQuery } from '@apollo/client';
16
+ import React, { useContext, useEffect, useMemo, useState } from 'react';
17
+ import { useMutation, useQuery, useApolloClient } from '@apollo/client';
18
18
  import { widgetsConversationDetail } from '../../graphql/query';
19
19
  import Message from './Message';
20
20
  import InputTools from '../../components/InputTools';
21
21
  import { widgetsInsertMessage } from '../../graphql/mutation';
22
- import { conversationMessageInserted } from '../../graphql/subscription';
22
+ import {
23
+ conversationMessageInserted,
24
+ conversationBotTypingStatus,
25
+ } from '../../graphql/subscription';
23
26
  import { getAttachmentUrl } from '../../utils/utils';
27
+ import { buildChatRows } from '../../utils/messages';
24
28
  import AppContext from '../../context/Context';
29
+ import AsyncStorage from '@react-native-async-storage/async-storage';
30
+ import { messengerTheme } from '../../theme';
31
+ import { BackIcon } from '../../components/Icons';
32
+ import DateSeparator from '../../components/conversation/DateSeparator';
33
+ import WelcomeMessage from '../../components/conversation/WelcomeMessage';
34
+ import TypingStatus from '../../components/conversation/TypingStatus';
35
+
36
+ const hasUserDetails = (user: any): boolean =>
37
+ Boolean(user?.details?.fullName || user?.details?.avatar);
38
+
39
+ const mergeUserDetails = (baseUser: any, incomingUser: any): any => {
40
+ if (!baseUser && !incomingUser) {
41
+ return incomingUser;
42
+ }
43
+
44
+ return {
45
+ ...(baseUser || {}),
46
+ ...(incomingUser || {}),
47
+ details: {
48
+ ...(baseUser?.details || {}),
49
+ ...(incomingUser?.details || {}),
50
+ },
51
+ };
52
+ };
53
+
54
+ const hydrateIncomingMessageUser = (message: any, participants: any[]) => {
55
+ if (message?.customerId || hasUserDetails(message?.user)) {
56
+ return message;
57
+ }
58
+
59
+ const participant =
60
+ participants.find((user: any) => user?._id === message?.user?._id) ||
61
+ (participants.length === 1 ? participants[0] : null);
62
+
63
+ if (!participant) {
64
+ return message;
65
+ }
66
+
67
+ return {
68
+ ...message,
69
+ user: mergeUserDetails(participant, message?.user),
70
+ };
71
+ };
72
+
73
+ const mergeParticipantFromMessage = (participants: any[], message: any) => {
74
+ if (message?.customerId) {
75
+ return participants;
76
+ }
77
+
78
+ const incomingUser = message?.user;
79
+ if (!incomingUser?._id || !hasUserDetails(incomingUser)) {
80
+ return participants;
81
+ }
82
+
83
+ const existingParticipant = participants.find(
84
+ (user: any) => user?._id === incomingUser._id
85
+ );
86
+
87
+ if (!existingParticipant) {
88
+ return [...participants, incomingUser];
89
+ }
90
+
91
+ return participants.map((user: any) =>
92
+ user?._id === incomingUser._id ? mergeUserDetails(user, incomingUser) : user
93
+ );
94
+ };
25
95
 
26
96
  const ConversationDetail = () => {
27
97
  const value = useContext(AppContext);
@@ -33,16 +103,23 @@ const ConversationDetail = () => {
33
103
  integrationId,
34
104
  customerId,
35
105
  visitorId,
106
+ greetings,
107
+ setConversationId,
108
+ markConversationRead,
36
109
  //
37
- backIcon,
38
110
  sendIcon,
39
111
  //
40
112
  subDomain,
41
113
  } = value;
42
114
 
43
115
  const [messages, setMessages] = React.useState<any>([]);
116
+ const [isBotTyping, setIsBotTyping] = useState(false);
117
+ const [sending, setSending] = useState(false);
118
+ const sendingRef = React.useRef(false);
44
119
 
45
- const { data, loading, subscribeToMore } = useQuery(
120
+ const client = useApolloClient();
121
+
122
+ const { data, loading, refetch, subscribeToMore } = useQuery(
46
123
  widgetsConversationDetail,
47
124
  {
48
125
  variables: {
@@ -55,33 +132,81 @@ const ConversationDetail = () => {
55
132
  );
56
133
 
57
134
  useEffect(() => {
135
+ if (!conversationId) {
136
+ return;
137
+ }
138
+
58
139
  const unSubsToMore = subscribeToMore({
59
140
  document: conversationMessageInserted,
60
141
  variables: { _id: conversationId },
61
142
  updateQuery: (prev, { subscriptionData }) => {
143
+ if (!prev || !subscriptionData.data) {
144
+ return prev;
145
+ }
146
+
62
147
  const message = subscriptionData.data.conversationMessageInserted;
148
+ if (!message) {
149
+ return prev;
150
+ }
151
+
63
152
  const tempWidgetsConversationDetail =
64
153
  prev.widgetsConversationDetail || {};
65
154
  const tempMessages = tempWidgetsConversationDetail?.messages || [];
155
+ const tempParticipants =
156
+ tempWidgetsConversationDetail?.participatedUsers || [];
157
+
158
+ const existingParticipant = tempParticipants.find(
159
+ (user: any) => user?._id === message?.user?._id
160
+ );
161
+ const messageUser = mergeUserDetails(
162
+ existingParticipant,
163
+ message?.user
164
+ );
165
+ const hydratedMessage = {
166
+ ...message,
167
+ user: messageUser,
168
+ };
169
+
170
+ if (!message.customerId && !hasUserDetails(messageUser)) {
171
+ setTimeout(() => refetch(), 0);
172
+ }
173
+
174
+ const nextParticipants = mergeParticipantFromMessage(
175
+ tempParticipants,
176
+ hydratedMessage
177
+ );
66
178
 
67
179
  // check whether or not already inserted
68
- const prevEntry = tempMessages.find((m: any) => m._id === message?._id);
180
+ const prevEntry = tempMessages.find(
181
+ (m: any) => m._id === hydratedMessage?._id
182
+ );
69
183
 
70
184
  if (prevEntry) {
71
- return prev;
185
+ return {
186
+ ...prev,
187
+ widgetsConversationDetail: {
188
+ ...tempWidgetsConversationDetail,
189
+ participatedUsers: nextParticipants,
190
+ },
191
+ };
72
192
  }
73
193
 
74
194
  // do not show internal or bot messages
75
- if (message.internal || message.fromBot) {
195
+ if (hydratedMessage.internal || hydratedMessage.fromBot) {
76
196
  return prev;
77
197
  }
78
198
 
199
+ if (!hydratedMessage.customerId) {
200
+ setTimeout(() => markConversationRead?.(conversationId), 0);
201
+ }
202
+
79
203
  // add new message to messages list
80
204
  const next = {
81
205
  ...prev,
82
206
  widgetsConversationDetail: {
83
207
  ...tempWidgetsConversationDetail,
84
- messages: [...tempMessages, message],
208
+ messages: [...tempMessages, hydratedMessage],
209
+ participatedUsers: nextParticipants,
85
210
  },
86
211
  };
87
212
 
@@ -90,101 +215,200 @@ const ConversationDetail = () => {
90
215
  });
91
216
 
92
217
  return () => {
93
- unSubsToMore;
218
+ unSubsToMore();
94
219
  };
95
- }, [conversationId, subscribeToMore]);
220
+ }, [conversationId, markConversationRead, refetch, subscribeToMore]);
96
221
 
97
222
  useEffect(() => {
98
223
  if (data?.widgetsConversationDetail?.messages) {
224
+ const participants =
225
+ data?.widgetsConversationDetail?.participatedUsers || [];
99
226
  let reversed = data?.widgetsConversationDetail?.messages
100
227
  ?.slice()
101
- ?.reverse();
228
+ ?.reverse()
229
+ ?.map((message: any) =>
230
+ hydrateIncomingMessageUser(message, participants)
231
+ );
102
232
  setMessages(reversed);
103
233
  }
104
234
  }, [data]);
105
235
 
236
+ // Bot typing indicator — ported from the web messenger useConversationDetail.
237
+ useEffect(() => {
238
+ if (!conversationId) {
239
+ return;
240
+ }
241
+ const subscription = client
242
+ .subscribe({
243
+ query: conversationBotTypingStatus,
244
+ variables: { _id: conversationId },
245
+ fetchPolicy: 'network-only',
246
+ })
247
+ .subscribe({
248
+ next({ data: typing }: any) {
249
+ const status = typing?.conversationBotTypingStatus;
250
+ setIsBotTyping(
251
+ typeof status === 'object' && status !== null
252
+ ? Boolean(status.typing)
253
+ : Boolean(status)
254
+ );
255
+ },
256
+ error() {
257
+ setIsBotTyping(false);
258
+ },
259
+ });
260
+
261
+ return () => subscription.unsubscribe();
262
+ }, [conversationId, client]);
263
+
264
+ // Build grouped rows (welcome → date separators → grouped messages) in
265
+ // chronological order, then reverse for the inverted FlatList.
266
+ const invertedRows = useMemo(() => {
267
+ const chronological = [...messages].reverse();
268
+ const rows = buildChatRows(chronological, greetings?.messages?.welcome);
269
+ return [...rows].reverse();
270
+ }, [messages, greetings]);
271
+
106
272
  const [sendMutation] = useMutation(widgetsInsertMessage);
107
273
 
108
- const onSend = (text: string) => {
274
+ useEffect(() => {
275
+ if (!conversationId) {
276
+ return;
277
+ }
278
+
279
+ markConversationRead?.(conversationId);
280
+ }, [conversationId, markConversationRead]);
281
+
282
+ const onSend = (text: string, attachments?: any[]) => {
283
+ if (sendingRef.current) {
284
+ return;
285
+ }
286
+
287
+ sendingRef.current = true;
288
+ setSending(true);
289
+
109
290
  sendMutation({
110
291
  variables: {
111
292
  integrationId,
112
293
  customerId,
113
294
  visitorId,
114
- conversationId,
295
+ conversationId: conversationId || null,
115
296
  contentType: 'text',
116
297
  message: text,
298
+ attachments:
299
+ attachments && attachments.length > 0 ? attachments : undefined,
117
300
  },
118
301
  })
119
302
  .then((res: any) => {
120
303
  if (res.errors) {
121
304
  return console.log(res.errors);
122
305
  }
123
- if (res.data.widgetsInsertMessage) {
124
- let shouldAdd = messages?.length === 0;
125
- if (!shouldAdd) {
126
- shouldAdd = res.data.widgetsInsertMessage._id !== messages[0]._id;
127
- }
128
- if (shouldAdd) {
129
- const newArray = [res.data.widgetsInsertMessage, ...messages];
130
- setMessages(newArray);
306
+
307
+ const insertedMessage = res.data.widgetsInsertMessage;
308
+
309
+ if (insertedMessage) {
310
+ const nextConversationId = insertedMessage.conversationId;
311
+
312
+ if (nextConversationId && nextConversationId !== conversationId) {
313
+ AsyncStorage.setItem('conversationId', nextConversationId);
314
+ setConversationId(nextConversationId);
131
315
  }
316
+
317
+ setMessages((prevMessages: any[]) => {
318
+ const alreadyExists = prevMessages.some(
319
+ (message: any) => message._id === insertedMessage._id
320
+ );
321
+
322
+ return alreadyExists
323
+ ? prevMessages
324
+ : [insertedMessage, ...prevMessages];
325
+ });
132
326
  }
133
327
  })
134
328
  .catch((err: any) => {
135
329
  console.log(err);
330
+ })
331
+ .finally(() => {
332
+ sendingRef.current = false;
333
+ setSending(false);
136
334
  });
137
335
  };
138
336
 
139
337
  if (loading) {
140
338
  return (
141
- <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
142
- <ActivityIndicator color={'#686868'} size={'large'} />
339
+ <View style={styles.fullCenter}>
340
+ <ActivityIndicator color={bgColor} size={'large'} />
143
341
  </View>
144
342
  );
145
343
  }
146
344
 
147
345
  const renderItem = ({ item }: any) => {
148
- return <Message item={item} bgColor={bgColor} />;
346
+ if (item.type === 'welcome') {
347
+ return <WelcomeMessage content={item.content} />;
348
+ }
349
+ if (item.type === 'date') {
350
+ return <DateSeparator label={item.label} />;
351
+ }
352
+ return (
353
+ <Message
354
+ item={item.item}
355
+ bgColor={bgColor}
356
+ isFirstInGroup={item.isFirstInGroup}
357
+ isLastInGroup={item.isLastInGroup}
358
+ />
359
+ );
149
360
  };
150
361
 
151
362
  const renderContent = () => {
152
363
  return (
153
- <View style={{ flex: 1 }}>
364
+ <TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
154
365
  <FlatList
155
366
  contentContainerStyle={styles.contentStyle}
156
367
  showsVerticalScrollIndicator={false}
157
- data={messages}
158
- keyExtractor={(item: any) => item._id}
368
+ data={invertedRows}
369
+ keyExtractor={(item: any) => item.id}
159
370
  onEndReachedThreshold={0.2}
160
371
  renderItem={renderItem}
161
372
  ItemSeparatorComponent={() => null}
373
+ keyboardShouldPersistTaps="handled"
162
374
  inverted
163
375
  />
164
- </View>
376
+ </TouchableWithoutFeedback>
165
377
  );
166
378
  };
167
379
 
168
380
  return (
169
- <View style={{ flex: 1 }}>
381
+ <View style={styles.root}>
170
382
  <Header
171
383
  brand={brand}
172
384
  bgColor={bgColor}
173
385
  users={data?.widgetsConversationDetail?.participatedUsers}
174
- backIcon={backIcon}
386
+ isOnline={data?.widgetsConversationDetail?.isOnline}
175
387
  subDomain={subDomain}
176
388
  />
177
- <View style={{ backgroundColor: 'rgba(215,215,215,.7)', flex: 1 }}>
389
+ <KeyboardAvoidingView
390
+ style={styles.chatArea}
391
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
392
+ keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
393
+ >
178
394
  {renderContent()}
179
- <KeyboardAvoidingView
180
- behavior={Platform.OS === 'ios' ? 'padding' : undefined}
181
- keyboardVerticalOffset={100}
182
- >
183
- <TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
184
- <InputTools onSend={onSend} bgColor={bgColor} sendIcon={sendIcon} />
185
- </TouchableWithoutFeedback>
186
- </KeyboardAvoidingView>
187
- </View>
395
+ {isBotTyping ? (
396
+ <View style={styles.typingWrap}>
397
+ <TypingStatus />
398
+ </View>
399
+ ) : null}
400
+ <InputTools
401
+ onSend={onSend}
402
+ bgColor={bgColor}
403
+ sendIcon={sendIcon}
404
+ subDomain={subDomain}
405
+ sending={sending}
406
+ persistentMenus={
407
+ data?.widgetsConversationDetail?.persistentMenus ||
408
+ greetings?.persistentMenus
409
+ }
410
+ />
411
+ </KeyboardAvoidingView>
188
412
  </View>
189
413
  );
190
414
  };
@@ -192,95 +416,57 @@ const ConversationDetail = () => {
192
416
  export default ConversationDetail;
193
417
 
194
418
  const Header = (props: any) => {
195
- const { brand, bgColor, users, backIcon, subDomain } = props;
419
+ const { brand, bgColor, users, isOnline, subDomain } = props;
196
420
 
197
421
  const value = useContext(AppContext);
198
422
 
199
423
  const { setConversationId } = value;
200
424
 
201
- if (users?.length > 0) {
202
- let source;
203
- if (users[0]?.details?.avatar) {
204
- source = { uri: getAttachmentUrl(users[0]?.details?.avatar, subDomain) };
205
- } else {
206
- source = require('../../assets/images/avatar.png');
207
- }
208
- return (
209
- <SafeAreaView
210
- style={{
211
- backgroundColor: bgColor,
212
- }}
213
- >
214
- <View
215
- style={{
216
- flexDirection: 'row',
217
- alignItems: 'center',
218
- paddingBottom: 20,
219
- }}
220
- >
221
- <TouchableOpacity
222
- onPress={() => {
223
- setConversationId(null);
224
- }}
225
- style={[
226
- styles.backStyle,
227
- {
228
- backgroundColor: '#2F1F69',
229
- },
230
- ]}
231
- >
232
- {backIcon}
233
- </TouchableOpacity>
234
- <View
235
- style={[
236
- styles.title,
237
- {
238
- marginLeft: 10,
239
- alignItems: 'center',
240
- flex: 1,
241
- },
242
- ]}
243
- >
244
- <Image source={source} style={styles.avatar} resizeMode="stretch" />
245
- <View style={{ marginLeft: 10 }}>
246
- <Text style={{ fontWeight: '600' }}>
247
- {users[0]?.details?.fullName}
248
- </Text>
249
- </View>
250
- </View>
251
- </View>
252
- </SafeAreaView>
253
- );
254
- }
425
+ const agent = users?.length > 0 ? users[0] : null;
426
+ const title = agent?.details?.fullName || brand?.name || 'Support';
427
+ const subtitle = agent ? 'usually replies in a few minutes' : null;
428
+
429
+ const avatarSource = agent?.details?.avatar
430
+ ? { uri: getAttachmentUrl(agent.details.avatar, subDomain) }
431
+ : require('../../assets/images/avatar.png');
432
+
433
+ const androidTop =
434
+ Platform.OS === 'android' ? StatusBar.currentHeight || 0 : 0;
255
435
 
256
436
  return (
257
- <SafeAreaView
258
- style={{
259
- backgroundColor: bgColor,
260
- }}
261
- >
262
- <View
263
- style={{
264
- flexDirection: 'row',
265
- alignItems: 'center',
266
- paddingBottom: 20,
267
- }}
268
- >
437
+ <SafeAreaView style={{ backgroundColor: bgColor }}>
438
+ <View style={[styles.header, { paddingTop: androidTop + 6 }]}>
269
439
  <TouchableOpacity
270
440
  onPress={() => {
441
+ AsyncStorage.removeItem('conversationId');
271
442
  setConversationId(null);
272
443
  }}
273
- style={[
274
- styles.backStyle,
275
- {
276
- backgroundColor: '#2F1F69',
277
- },
278
- ]}
444
+ style={styles.backButton}
445
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
279
446
  >
280
- {backIcon}
447
+ <BackIcon color={messengerTheme.colors.primaryForeground} size={18} />
281
448
  </TouchableOpacity>
282
- <View style={[styles.title]}>
283
- <Text style={{ fontWeight: '600', fontSize: 16 }}>{brand?.name}</Text>
449
+
450
+ {agent ? (
451
+ <View style={styles.avatarWrap}>
452
+ <Image
453
+ source={avatarSource}
454
+ style={styles.avatar}
455
+ resizeMode="cover"
456
+ />
457
+ {isOnline ? <View style={styles.onlineDot} /> : null}
458
+ </View>
459
+ ) : null}
460
+
461
+ <View style={styles.headerTextWrap}>
462
+ <Text numberOfLines={1} style={styles.headerTitle}>
463
+ {title}
464
+ </Text>
465
+ {subtitle ? (
466
+ <Text numberOfLines={1} style={styles.headerSubtitle}>
467
+ {subtitle}
468
+ </Text>
469
+ ) : null}
284
470
  </View>
285
471
  </View>
286
472
  </SafeAreaView>
@@ -288,28 +474,75 @@ const Header = (props: any) => {
288
474
  };
289
475
 
290
476
  const styles = StyleSheet.create({
291
- backStyle: {
292
- width: 40,
293
- height: 40,
294
- marginLeft: 15,
295
- borderRadius: 90,
296
- justifyContent: 'center',
297
- alignItems: 'center',
477
+ root: {
478
+ flex: 1,
479
+ backgroundColor: messengerTheme.colors.background,
298
480
  },
299
- title: {
300
- paddingVertical: 10,
301
- paddingHorizontal: 15,
302
- borderTopLeftRadius: 8,
303
- borderTopRightRadius: 8,
304
- flexDirection: 'row',
481
+ chatArea: {
482
+ flex: 1,
483
+ backgroundColor: messengerTheme.colors.background,
484
+ },
485
+ fullCenter: {
486
+ flex: 1,
487
+ alignItems: 'center',
488
+ justifyContent: 'center',
489
+ backgroundColor: messengerTheme.colors.background,
305
490
  },
306
491
  contentStyle: {
307
492
  flexGrow: 1,
308
- padding: 20,
493
+ paddingHorizontal: messengerTheme.spacing.lg,
494
+ paddingVertical: messengerTheme.spacing.md,
309
495
  },
310
- avatar: {
496
+ typingWrap: {
497
+ paddingHorizontal: messengerTheme.spacing.lg,
498
+ paddingBottom: messengerTheme.spacing.xs,
499
+ },
500
+ header: {
501
+ flexDirection: 'row',
502
+ alignItems: 'center',
503
+ paddingHorizontal: messengerTheme.spacing.md,
504
+ paddingBottom: messengerTheme.spacing.md,
505
+ paddingTop: 6,
506
+ },
507
+ backButton: {
311
508
  width: 40,
312
509
  height: 40,
313
- borderRadius: 90,
510
+ borderRadius: 20,
511
+ justifyContent: 'center',
512
+ alignItems: 'center',
513
+ backgroundColor: 'rgba(255,255,255,0.16)',
514
+ },
515
+ avatarWrap: {
516
+ marginLeft: messengerTheme.spacing.md,
517
+ },
518
+ avatar: {
519
+ width: 36,
520
+ height: 36,
521
+ borderRadius: 18,
522
+ },
523
+ onlineDot: {
524
+ position: 'absolute',
525
+ right: 0,
526
+ bottom: 0,
527
+ width: 11,
528
+ height: 11,
529
+ borderRadius: 6,
530
+ backgroundColor: messengerTheme.colors.online,
531
+ borderWidth: 2,
532
+ borderColor: messengerTheme.colors.primary,
533
+ },
534
+ headerTextWrap: {
535
+ flex: 1,
536
+ marginLeft: messengerTheme.spacing.md,
537
+ },
538
+ headerTitle: {
539
+ color: messengerTheme.colors.primaryForeground,
540
+ fontWeight: '600',
541
+ fontSize: 16,
542
+ },
543
+ headerSubtitle: {
544
+ color: 'rgba(255,255,255,0.65)',
545
+ fontSize: 12,
546
+ marginTop: 1,
314
547
  },
315
548
  });