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
@@ -0,0 +1,105 @@
1
+ // Message list helpers ported from the web messenger SDK
2
+ // (frontline-widgets conversation-details.tsx): date keys, friendly date
3
+ // labels, and grouping messages by sender + a 5-minute time window so that only
4
+ // the last message of a run shows an avatar/timestamp and the first shows the
5
+ // sender name.
6
+ import dayjs from 'dayjs';
7
+
8
+ export const MESSAGE_GROUP_TIME_WINDOW = 5 * 60 * 1000;
9
+
10
+ export const getDateKey = (date: any): string =>
11
+ dayjs(date).format('YYYY-MM-DD');
12
+
13
+ export const formatMessageDate = (dateKey: string): string => {
14
+ const d = dayjs(dateKey);
15
+ if (d.isSame(dayjs(), 'day')) {
16
+ return 'Today';
17
+ }
18
+ if (d.isSame(dayjs().subtract(1, 'day'), 'day')) {
19
+ return 'Yesterday';
20
+ }
21
+ return d.format('MMM D, YYYY');
22
+ };
23
+
24
+ const isOutgoing = (m: any): boolean => Boolean(m?.customerId);
25
+
26
+ // Two messages belong to the same visual group when they are from the same
27
+ // side (customer vs operator), the same operator user, on the same day, and
28
+ // within the grouping time window.
29
+ const sameGroup = (a: any, b: any): boolean => {
30
+ if (isOutgoing(a) !== isOutgoing(b)) {
31
+ return false;
32
+ }
33
+ if (!isOutgoing(a) && (a?.user?._id || '') !== (b?.user?._id || '')) {
34
+ return false;
35
+ }
36
+ if (getDateKey(a?.createdAt) !== getDateKey(b?.createdAt)) {
37
+ return false;
38
+ }
39
+ return (
40
+ Math.abs(
41
+ new Date(b?.createdAt).getTime() - new Date(a?.createdAt).getTime()
42
+ ) <= MESSAGE_GROUP_TIME_WINDOW
43
+ );
44
+ };
45
+
46
+ export type ChatRow =
47
+ | { type: 'welcome'; id: string; content?: string }
48
+ | { type: 'date'; id: string; label: string }
49
+ | {
50
+ type: 'message';
51
+ id: string;
52
+ item: any;
53
+ isFirstInGroup: boolean;
54
+ isLastInGroup: boolean;
55
+ };
56
+
57
+ /**
58
+ * Build the chronological row list (welcome → date separators → grouped
59
+ * messages) for the chat screen.
60
+ *
61
+ * @param chronological messages oldest-first
62
+ * @param welcomeContent optional welcome bubble shown at the very top
63
+ */
64
+ export const buildChatRows = (
65
+ chronological: any[],
66
+ welcomeContent?: string
67
+ ): ChatRow[] => {
68
+ const rows: ChatRow[] = [];
69
+
70
+ // Hide internal notes; bot/welcome handled separately.
71
+ const visible = (chronological || []).filter((m) => !m?.internal);
72
+
73
+ let lastDateKey: string | null = null;
74
+
75
+ visible.forEach((m, i) => {
76
+ const dateKey = getDateKey(m?.createdAt);
77
+ if (dateKey !== lastDateKey) {
78
+ rows.push({
79
+ type: 'date',
80
+ id: `date-${dateKey}-${i}`,
81
+ label: formatMessageDate(dateKey),
82
+ });
83
+ lastDateKey = dateKey;
84
+ }
85
+
86
+ const prev = visible[i - 1];
87
+ const next = visible[i + 1];
88
+ const isFirstInGroup = !prev || !sameGroup(prev, m);
89
+ const isLastInGroup = !next || !sameGroup(m, next);
90
+
91
+ rows.push({
92
+ type: 'message',
93
+ id: m._id,
94
+ item: m,
95
+ isFirstInGroup,
96
+ isLastInGroup,
97
+ });
98
+ });
99
+
100
+ if (welcomeContent) {
101
+ rows.unshift({ type: 'welcome', id: 'welcome', content: welcomeContent });
102
+ }
103
+
104
+ return rows;
105
+ };
@@ -0,0 +1,167 @@
1
+ type OnlineHour = {
2
+ day: string;
3
+ from: string;
4
+ to: string;
5
+ };
6
+
7
+ type FormatOnlineHoursOptions = {
8
+ onlineHours?: OnlineHour[];
9
+ timezone?: string;
10
+ showTimezone?: boolean;
11
+ };
12
+
13
+ export const AVAILABILITY_MESSAGE =
14
+ "We're available between 9.00 am and 6.00 pm (GMT +8). We'll get back to you as soon as possible.";
15
+
16
+ const parseTime = (timeString: string): { hour: number; minute: number } => {
17
+ const normalized = timeString.toLowerCase().trim();
18
+ const colonIndex = normalized.indexOf(':');
19
+
20
+ if (colonIndex === -1) {
21
+ throw new Error(`Invalid time format: ${timeString}`);
22
+ }
23
+
24
+ let hour = Number.parseInt(normalized.substring(0, colonIndex), 10);
25
+ const minute = Number.parseInt(
26
+ normalized.substring(colonIndex + 1, colonIndex + 3),
27
+ 10
28
+ );
29
+
30
+ const isPM = normalized.includes('pm');
31
+ const isAM = normalized.includes('am');
32
+
33
+ if (isPM && hour !== 12) {
34
+ hour += 12;
35
+ } else if (isAM && hour === 12) {
36
+ hour = 0;
37
+ }
38
+
39
+ return { hour, minute };
40
+ };
41
+
42
+ const formatTimeTo12Hour = (timeString: string): string => {
43
+ const { hour, minute } = parseTime(timeString);
44
+ const hour12 = hour > 12 ? hour - 12 : hour || 12;
45
+ const period = hour < 12 ? 'am' : 'pm';
46
+ const minuteStr = minute.toString().padStart(2, '0');
47
+
48
+ return `${hour12}.${minuteStr} ${period}`;
49
+ };
50
+
51
+ const formatTimeZoneLabel = (timezone: string): string => {
52
+ try {
53
+ const parts = new Intl.DateTimeFormat('en-US', {
54
+ timeZone: timezone,
55
+ timeZoneName: 'shortOffset',
56
+ }).formatToParts(new Date());
57
+ const value = parts.find((part) => part.type === 'timeZoneName')?.value;
58
+
59
+ return value || timezone;
60
+ } catch {
61
+ return timezone;
62
+ }
63
+ };
64
+
65
+ const formatTimezone = (timezone?: string, showTimezone?: boolean): string => {
66
+ if (!showTimezone || !timezone) {
67
+ return '';
68
+ }
69
+
70
+ const timezoneLabel = formatTimeZoneLabel(timezone);
71
+ const gmtMatch = timezoneLabel.match(/GMT([+-])(\d{1,2}):?(\d{0,2})/);
72
+
73
+ if (!gmtMatch) {
74
+ return ` (${timezoneLabel})`;
75
+ }
76
+
77
+ const signPart = gmtMatch[1];
78
+ const hourPart = gmtMatch[2];
79
+
80
+ if (!signPart || !hourPart) {
81
+ return ` (${timezoneLabel})`;
82
+ }
83
+
84
+ const sign = signPart === '+' ? '+' : '-';
85
+ const hours = Number.parseInt(hourPart, 10);
86
+ const minutes = gmtMatch[3] ? Number.parseInt(gmtMatch[3], 10) : 0;
87
+
88
+ return minutes === 0
89
+ ? ` (GMT ${sign}${hours})`
90
+ : ` (GMT ${sign}${hours}:${minutes.toString().padStart(2, '0')})`;
91
+ };
92
+
93
+ export const formatOnlineHours = ({
94
+ onlineHours,
95
+ timezone,
96
+ showTimezone,
97
+ }: FormatOnlineHoursOptions): {
98
+ workHours: string;
99
+ formattedTimeZone: string;
100
+ onlineDays: string;
101
+ } | null => {
102
+ const firstHour = onlineHours?.[0];
103
+
104
+ if (!firstHour?.from || !firstHour?.to) {
105
+ return null;
106
+ }
107
+
108
+ try {
109
+ return {
110
+ workHours: `${formatTimeTo12Hour(
111
+ firstHour.from
112
+ )} and ${formatTimeTo12Hour(firstHour.to)}`,
113
+ formattedTimeZone: formatTimezone(timezone, showTimezone).trim(),
114
+ onlineDays: onlineHours?.map((item) => item.day).join(', ') || '',
115
+ };
116
+ } catch {
117
+ return {
118
+ workHours: `${firstHour.from} and ${firstHour.to}`,
119
+ formattedTimeZone: formatTimezone(timezone, showTimezone).trim(),
120
+ onlineDays: onlineHours?.map((item) => item.day).join(', ') || '',
121
+ };
122
+ }
123
+ };
124
+
125
+ export const formatOnlineHoursShort = ({
126
+ onlineHours,
127
+ timezone,
128
+ showTimezone,
129
+ }: FormatOnlineHoursOptions): string => {
130
+ const firstHour = onlineHours?.[0];
131
+
132
+ if (!firstHour?.from || !firstHour?.to) {
133
+ return '';
134
+ }
135
+
136
+ try {
137
+ const from = formatTimeTo12Hour(firstHour.from)
138
+ .replace(/\s+/g, '')
139
+ .replace('.00', '');
140
+ const to = formatTimeTo12Hour(firstHour.to)
141
+ .replace(/\s+/g, '')
142
+ .replace('.00', '');
143
+
144
+ let timezoneSuffix = '';
145
+
146
+ if (showTimezone && timezone) {
147
+ const timezoneLabel = formatTimeZoneLabel(timezone);
148
+ const gmtMatch = timezoneLabel.match(
149
+ /(?:\()?GMT([+-])(\d{1,2}):?(\d{0,2})(?:\))?/
150
+ );
151
+
152
+ if (gmtMatch?.[1] && gmtMatch[2]) {
153
+ const sign = gmtMatch[1];
154
+ const hours = Number.parseInt(gmtMatch[2], 10);
155
+ const minutes = gmtMatch[3] ? Number.parseInt(gmtMatch[3], 10) : 0;
156
+ const minutePart =
157
+ minutes === 0 ? '' : `:${minutes.toString().padStart(2, '0')}`;
158
+
159
+ timezoneSuffix = ` (GMT${sign}${hours}${minutePart})`;
160
+ }
161
+ }
162
+
163
+ return `${from}-${to}${timezoneSuffix}`;
164
+ } catch {
165
+ return `${firstHour.from}-${firstHour.to}`;
166
+ }
167
+ };
@@ -0,0 +1,326 @@
1
+ // File-upload pipeline for the chat composer.
2
+ //
3
+ // Supports BOTH picker libraries so the SDK works in either setup:
4
+ // - Expo (managed / Expo Go) -> `expo-image-picker`
5
+ // - bare React Native -> `react-native-image-picker`
6
+ // Both are optional and resolved lazily, so the composer degrades gracefully
7
+ // (the attach button hides) when neither is installed. The uploaded file key is
8
+ // returned and sent as a message attachment via the existing
9
+ // `widgetsInsertMessage` mutation (which already accepts attachments).
10
+ //
11
+ // NOTE: the upload endpoint mirrors the web uploader:
12
+ // `POST /upload-file?kind=main&maxHeight=0&maxWidth=0`.
13
+
14
+ export type PickedFile = {
15
+ uri: string;
16
+ name: string;
17
+ type: string;
18
+ size?: number;
19
+ };
20
+
21
+ export type UploadedAttachment = {
22
+ url: string;
23
+ name: string;
24
+ type: string;
25
+ size?: number;
26
+ };
27
+
28
+ export type UploadResponseType = 'text' | 'json';
29
+
30
+ export type UploadOptions = {
31
+ url?: string;
32
+ kind?: string;
33
+ userId?: string;
34
+ responseType?: UploadResponseType;
35
+ maxHeight?: number;
36
+ maxWidth?: number;
37
+ extraFormData?: Array<{ key: string; value: string }>;
38
+ };
39
+
40
+ const ALLOWED_IMAGE_MIME_TYPES = ['image/jpeg', 'image/png'];
41
+ const ALLOWED_IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png'];
42
+
43
+ export const unsupportedImageUploadMessage =
44
+ 'Only PNG and JPG images can be uploaded.';
45
+
46
+ const getCleanPath = (value?: string | null): string => {
47
+ const withoutQuery = (value || '').split('?')[0] || '';
48
+
49
+ return withoutQuery.split('#')[0] || '';
50
+ };
51
+
52
+ const getExtension = (value?: string | null): string => {
53
+ const match = getCleanPath(value).match(/\.([a-zA-Z0-9]+)$/);
54
+
55
+ return match && match[1] ? match[1].toLowerCase() : '';
56
+ };
57
+
58
+ const normalizeMimeType = (type?: string | null): string => {
59
+ if (!type) {
60
+ return '';
61
+ }
62
+
63
+ return type === 'image/jpg' ? 'image/jpeg' : type.toLowerCase();
64
+ };
65
+
66
+ const getMimeTypeFromExtension = (extension: string): string => {
67
+ if (extension === 'jpg' || extension === 'jpeg') {
68
+ return 'image/jpeg';
69
+ }
70
+
71
+ if (extension === 'png') {
72
+ return 'image/png';
73
+ }
74
+
75
+ return '';
76
+ };
77
+
78
+ const getFileNameFromUri = (uri?: string | null): string => {
79
+ const fileName = getCleanPath(uri).split('/').pop() || '';
80
+
81
+ return getExtension(fileName) ? fileName : '';
82
+ };
83
+
84
+ const getImageFileName = (
85
+ fileName?: string | null,
86
+ uri?: string | null,
87
+ type?: string | null
88
+ ): string => {
89
+ if (fileName && getExtension(fileName)) {
90
+ return fileName;
91
+ }
92
+
93
+ const uriFileName = getFileNameFromUri(uri);
94
+ if (uriFileName) {
95
+ return uriFileName;
96
+ }
97
+
98
+ const normalizedType = normalizeMimeType(type);
99
+ let extension = 'unsupported';
100
+
101
+ if (normalizedType === 'image/png') {
102
+ extension = 'png';
103
+ }
104
+
105
+ if (normalizedType === 'image/jpeg') {
106
+ extension = 'jpg';
107
+ }
108
+
109
+ return `image-${Date.now()}.${extension}`;
110
+ };
111
+
112
+ const getImageMimeType = (
113
+ type?: string | null,
114
+ fileName?: string | null,
115
+ uri?: string | null
116
+ ): string => {
117
+ const normalizedType = normalizeMimeType(type);
118
+
119
+ return (
120
+ normalizedType ||
121
+ getMimeTypeFromExtension(getExtension(fileName)) ||
122
+ getMimeTypeFromExtension(getExtension(uri))
123
+ );
124
+ };
125
+
126
+ export const isAllowedImageFile = (file: PickedFile): boolean => {
127
+ const type = normalizeMimeType(file.type);
128
+
129
+ if (type) {
130
+ return ALLOWED_IMAGE_MIME_TYPES.includes(type);
131
+ }
132
+
133
+ const extension = getExtension(file.name) || getExtension(file.uri);
134
+
135
+ return ALLOWED_IMAGE_EXTENSIONS.includes(extension);
136
+ };
137
+
138
+ // NOTE: each require must be a LITERAL string inside try/catch so Metro treats
139
+ // it as an OPTIONAL dependency — it is bundled when installed and skipped
140
+ // (without failing the build) when absent. A variable require would never be
141
+ // bundled. Expo is preferred when present (works in Expo Go without a rebuild).
142
+ const getExpoPicker = (): any | null => {
143
+ try {
144
+ return require('expo-image-picker');
145
+ } catch {
146
+ return null;
147
+ }
148
+ };
149
+
150
+ const getRNPicker = (): any | null => {
151
+ try {
152
+ return require('react-native-image-picker');
153
+ } catch {
154
+ return null;
155
+ }
156
+ };
157
+
158
+ export const isImagePickerAvailable = (): boolean =>
159
+ !!(
160
+ getExpoPicker()?.launchImageLibraryAsync ||
161
+ getRNPicker()?.launchImageLibrary
162
+ );
163
+
164
+ export const pickImage = async (): Promise<PickedFile | null> => {
165
+ const expo = getExpoPicker();
166
+ if (expo?.launchImageLibraryAsync) {
167
+ if (expo.requestMediaLibraryPermissionsAsync) {
168
+ const perm = await expo.requestMediaLibraryPermissionsAsync();
169
+ if (perm && perm.granted === false) {
170
+ return null;
171
+ }
172
+ }
173
+ // `MediaType` is a TS-only type in expo-image-picker v16+; the runtime value
174
+ // is the string-array form. Using it avoids the deprecated
175
+ // `MediaTypeOptions` (and its warning).
176
+ const result = await expo.launchImageLibraryAsync({
177
+ mediaTypes: ['images'],
178
+ preferredAssetRepresentationMode:
179
+ expo.UIImagePickerPreferredAssetRepresentationMode?.Compatible ||
180
+ 'compatible',
181
+ quality: 0.8,
182
+ });
183
+ if (result?.canceled || !result?.assets || result.assets.length === 0) {
184
+ return null;
185
+ }
186
+ const asset = result.assets[0];
187
+ const name = getImageFileName(asset.fileName, asset.uri, asset.mimeType);
188
+ return {
189
+ uri: asset.uri,
190
+ name,
191
+ type: getImageMimeType(asset.mimeType, name, asset.uri),
192
+ size: asset.fileSize,
193
+ };
194
+ }
195
+
196
+ const rn = getRNPicker();
197
+ if (rn?.launchImageLibrary) {
198
+ const result = await rn.launchImageLibrary({
199
+ mediaType: 'photo',
200
+ selectionLimit: 1,
201
+ assetRepresentationMode: 'compatible',
202
+ quality: 0.8,
203
+ });
204
+ if (result?.didCancel || !result?.assets || result.assets.length === 0) {
205
+ return null;
206
+ }
207
+ const asset = result.assets[0];
208
+ const name = getImageFileName(asset.fileName, asset.uri, asset.type);
209
+ return {
210
+ uri: asset.uri,
211
+ name,
212
+ type: getImageMimeType(asset.type, name, asset.uri),
213
+ size: asset.fileSize,
214
+ };
215
+ }
216
+
217
+ return null;
218
+ };
219
+
220
+ const getFileType = (file: PickedFile): string => {
221
+ return getImageMimeType(file.type, file.name, file.uri);
222
+ };
223
+
224
+ const normalizeUploadResponse = (
225
+ response: unknown,
226
+ responseType: UploadResponseType
227
+ ): string => {
228
+ if (responseType === 'text') {
229
+ return String(response).trim();
230
+ }
231
+
232
+ if (typeof response === 'string') {
233
+ return response.trim();
234
+ }
235
+
236
+ if (response && typeof response === 'object') {
237
+ const body = response as Record<string, any>;
238
+ return String(
239
+ body.url || body.key || body.name || body.response || body.file || ''
240
+ ).trim();
241
+ }
242
+
243
+ return '';
244
+ };
245
+
246
+ // erxes gateway upload endpoint. erxes core serves file routes at the gateway
247
+ // root: `GET /read-file` (download, see getAttachmentUrl) and `POST
248
+ // /upload-file` (upload), which returns the stored file key as plain text.
249
+ export const getUploadUrl = (
250
+ subDomain: string,
251
+ options: UploadOptions = {}
252
+ ): string => {
253
+ const {
254
+ url = `https://${subDomain}/gateway/upload-file`,
255
+ kind = 'main',
256
+ maxHeight = 0,
257
+ maxWidth = 0,
258
+ } = options;
259
+
260
+ const separator = url.includes('?') ? '&' : '?';
261
+
262
+ return `${url}${separator}kind=${encodeURIComponent(
263
+ kind
264
+ )}&maxHeight=${maxHeight}&maxWidth=${maxWidth}`;
265
+ };
266
+
267
+ export const uploadFile = async (
268
+ file: PickedFile,
269
+ subDomain: string,
270
+ options: UploadOptions = {}
271
+ ): Promise<UploadedAttachment> => {
272
+ const type = getFileType(file);
273
+ const uploadFileInfo = {
274
+ ...file,
275
+ name: getImageFileName(file.name, file.uri, type),
276
+ type,
277
+ };
278
+
279
+ if (!isAllowedImageFile(uploadFileInfo)) {
280
+ throw new Error(unsupportedImageUploadMessage);
281
+ }
282
+
283
+ const form = new FormData();
284
+ form.append('file', {
285
+ uri: uploadFileInfo.uri,
286
+ name: uploadFileInfo.name,
287
+ type: uploadFileInfo.type,
288
+ } as any);
289
+
290
+ if (options.extraFormData) {
291
+ options.extraFormData.forEach(({ key, value }) => {
292
+ form.append(key, value);
293
+ });
294
+ }
295
+
296
+ const responseType = options.responseType || 'text';
297
+ const response = await fetch(getUploadUrl(subDomain, options), {
298
+ method: 'POST',
299
+ body: form,
300
+ credentials: 'include',
301
+ ...(options.userId ? { headers: { userId: options.userId } } : {}),
302
+ });
303
+
304
+ const responseBody = await response[responseType]();
305
+
306
+ if (!response.ok) {
307
+ const detail =
308
+ typeof responseBody === 'string'
309
+ ? responseBody.slice(0, 200)
310
+ : JSON.stringify(responseBody).slice(0, 200);
311
+ throw new Error(`Upload failed (${response.status}) ${detail}`.trim());
312
+ }
313
+
314
+ const key = normalizeUploadResponse(responseBody, responseType);
315
+
316
+ if (!key) {
317
+ throw new Error('Upload failed: empty file key');
318
+ }
319
+
320
+ return {
321
+ url: key,
322
+ name: uploadFileInfo.name,
323
+ type: uploadFileInfo.type,
324
+ size: uploadFileInfo.size,
325
+ };
326
+ };
@@ -1,51 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _react = _interopRequireDefault(require("react"));
8
- var _reactNative = require("react-native");
9
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
- /* eslint-disable react-native/no-inline-styles */
11
-
12
- const FAB = props => {
13
- const {
14
- icon,
15
- onPress,
16
- backgroundColor = '#2F1F69',
17
- style,
18
- bottom = 20
19
- } = props;
20
- return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableOpacity, {
21
- onPress: onPress,
22
- style: [styles.default, {
23
- backgroundColor: backgroundColor ? backgroundColor : '#2F1F69',
24
- bottom: bottom
25
- }, style]
26
- }, icon);
27
- };
28
- const styles = _reactNative.StyleSheet.create({
29
- default: {
30
- position: 'absolute',
31
- right: 20,
32
- bottom: 20,
33
- height: 60,
34
- width: 60,
35
- borderRadius: 50,
36
- justifyContent: 'center',
37
- alignItems: 'center',
38
- elevation: 3,
39
- zIndex: 3,
40
- shadowColor: '#2F1F69',
41
- shadowOffset: {
42
- width: 0,
43
- height: 0
44
- },
45
- shadowOpacity: 0.5,
46
- shadowRadius: 3
47
- }
48
- });
49
- var _default = FAB;
50
- exports.default = _default;
51
- //# sourceMappingURL=FAB.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["_react","_interopRequireDefault","require","_reactNative","obj","__esModule","default","FAB","props","icon","onPress","backgroundColor","style","bottom","createElement","TouchableOpacity","styles","StyleSheet","create","position","right","height","width","borderRadius","justifyContent","alignItems","elevation","zIndex","shadowColor","shadowOffset","shadowOpacity","shadowRadius","_default","exports"],"sourceRoot":"../../../src","sources":["components/FAB.tsx"],"mappings":";;;;;;AACA,IAAAA,MAAA,GAAAC,sBAAA,CAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AAKsB,SAAAD,uBAAAG,GAAA,WAAAA,GAAA,IAAAA,GAAA,CAAAC,UAAA,GAAAD,GAAA,KAAAE,OAAA,EAAAF,GAAA;AAPtB;;AAqBA,MAAMG,GAAG,GAAIC,KAAY,IAAK;EAC5B,MAAM;IACJC,IAAI;IACJC,OAAO;IACPC,eAAe,GAAG,SAAS;IAC3BC,KAAK;IACLC,MAAM,GAAG;EACX,CAAC,GAAGL,KAAK;EACT,oBACER,MAAA,CAAAM,OAAA,CAAAQ,aAAA,CAACX,YAAA,CAAAY,gBAAgB;IACfL,OAAO,EAAEA,OAAQ;IACjBE,KAAK,EAAE,CACLI,MAAM,CAACV,OAAO,EACd;MACEK,eAAe,EAAEA,eAAe,GAAGA,eAAe,GAAG,SAAS;MAC9DE,MAAM,EAAEA;IACV,CAAC,EACDD,KAAK;EACL,GAEDH,IACe,CAAC;AAEvB,CAAC;AAED,MAAMO,MAAM,GAAGC,uBAAU,CAACC,MAAM,CAAC;EAC/BZ,OAAO,EAAE;IACPa,QAAQ,EAAE,UAAU;IACpBC,KAAK,EAAE,EAAE;IACTP,MAAM,EAAE,EAAE;IACVQ,MAAM,EAAE,EAAE;IACVC,KAAK,EAAE,EAAE;IACTC,YAAY,EAAE,EAAE;IAChBC,cAAc,EAAE,QAAQ;IACxBC,UAAU,EAAE,QAAQ;IACpBC,SAAS,EAAE,CAAC;IACZC,MAAM,EAAE,CAAC;IACTC,WAAW,EAAE,SAAS;IACtBC,YAAY,EAAE;MACZP,KAAK,EAAE,CAAC;MACRD,MAAM,EAAE;IACV,CAAC;IACDS,aAAa,EAAE,GAAG;IAClBC,YAAY,EAAE;EAChB;AACF,CAAC,CAAC;AAAC,IAAAC,QAAA,GAEYzB,GAAG;AAAA0B,OAAA,CAAA3B,OAAA,GAAA0B,QAAA"}