@developer_tribe/react-native-comnyx 0.15.0 → 0.16.1

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 (210) hide show
  1. package/Comnyx.podspec +10 -2
  2. package/README.md +50 -0
  3. package/android/build.gradle +1 -0
  4. package/android/consumer-rules.pro +23 -0
  5. package/android/generated/java/com/comnyx/NativeComnyxSpec.java +46 -0
  6. package/android/generated/jni/RNComnyxSpec-generated.cpp +23 -1
  7. package/android/generated/jni/RNComnyxSpec.h +7 -0
  8. package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI-generated.cpp +21 -0
  9. package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI.h +70 -0
  10. package/android/src/main/AndroidManifest.xml +11 -1
  11. package/android/src/main/AndroidManifestNew.xml +11 -1
  12. package/android/src/main/java/com/comnyx/ComnyxMediaPickerModule.kt +91 -51
  13. package/android/src/main/java/com/comnyx/ComnyxModule.kt +7 -0
  14. package/android/src/main/java/com/comnyx/src/messaging/firebase/FirebaseMessagingService.kt +4 -6
  15. package/android/src/main/res/xml/comnyx_file_paths.xml +12 -0
  16. package/ios/APNService.swift +9 -9
  17. package/ios/Comnyx.swift +17 -8
  18. package/ios/ComnyxMediaPicker.swift +47 -26
  19. package/ios/ComnyxMessaging.swift +2 -0
  20. package/ios/PrivacyInfo.xcprivacy +32 -0
  21. package/ios/comnyx_post_install.rb +25 -0
  22. package/ios/generated/RCTAppDependencyProvider.h +25 -0
  23. package/ios/generated/RCTAppDependencyProvider.mm +55 -0
  24. package/ios/generated/RCTModulesConformingToProtocolsProvider.h +18 -0
  25. package/ios/generated/RCTModulesConformingToProtocolsProvider.mm +33 -0
  26. package/ios/generated/RCTThirdPartyComponentsProvider.h +16 -0
  27. package/ios/generated/RCTThirdPartyComponentsProvider.mm +23 -0
  28. package/ios/generated/RNComnyxSpec/RNComnyxSpec-generated.mm +53 -0
  29. package/ios/generated/RNComnyxSpec/RNComnyxSpec.h +67 -0
  30. package/ios/generated/RNComnyxSpecJSI-generated.cpp +38 -0
  31. package/ios/generated/RNComnyxSpecJSI.h +89 -0
  32. package/ios/generated/ReactAppDependencyProvider.podspec +34 -0
  33. package/lib/commonjs/NativeComnyxMediaPicker.js +19 -0
  34. package/lib/commonjs/NativeComnyxMediaPicker.js.map +1 -1
  35. package/lib/commonjs/api/conversations.js +6 -6
  36. package/lib/commonjs/api/conversations.js.map +1 -1
  37. package/lib/commonjs/api/customers.js +3 -2
  38. package/lib/commonjs/api/customers.js.map +1 -1
  39. package/lib/commonjs/api/media.js +20 -6
  40. package/lib/commonjs/api/media.js.map +1 -1
  41. package/lib/commonjs/api/messages.js +3 -2
  42. package/lib/commonjs/api/messages.js.map +1 -1
  43. package/lib/commonjs/components/ChatList.js +132 -90
  44. package/lib/commonjs/components/ChatList.js.map +1 -1
  45. package/lib/commonjs/components/ComnyxErrorBoundary.js +92 -0
  46. package/lib/commonjs/components/ComnyxErrorBoundary.js.map +1 -0
  47. package/lib/commonjs/components/CustomerForm.js +7 -3
  48. package/lib/commonjs/components/CustomerForm.js.map +1 -1
  49. package/lib/commonjs/components/InitFailed.js +77 -21
  50. package/lib/commonjs/components/InitFailed.js.map +1 -1
  51. package/lib/commonjs/components/MediaMessageItem.js +37 -10
  52. package/lib/commonjs/components/MediaMessageItem.js.map +1 -1
  53. package/lib/commonjs/components/MediaViewerModal.js +16 -3
  54. package/lib/commonjs/components/MediaViewerModal.js.map +1 -1
  55. package/lib/commonjs/components/MessageInput.js +83 -15
  56. package/lib/commonjs/components/MessageInput.js.map +1 -1
  57. package/lib/commonjs/components/MessageItem.js +1 -1
  58. package/lib/commonjs/components/MessageItem.js.map +1 -1
  59. package/lib/commonjs/hooks/usePolling.js +30 -21
  60. package/lib/commonjs/hooks/usePolling.js.map +1 -1
  61. package/lib/commonjs/hooks/useThemeColors.js +12 -1
  62. package/lib/commonjs/hooks/useThemeColors.js.map +1 -1
  63. package/lib/commonjs/index.js +6 -0
  64. package/lib/commonjs/index.js.map +1 -1
  65. package/lib/commonjs/notifications/initializeNotifications.js +19 -16
  66. package/lib/commonjs/notifications/initializeNotifications.js.map +1 -1
  67. package/lib/commonjs/register/Accumulator.js +19 -6
  68. package/lib/commonjs/register/Accumulator.js.map +1 -1
  69. package/lib/commonjs/register/collectData.js +1 -1
  70. package/lib/commonjs/register/collectData.js.map +1 -1
  71. package/lib/commonjs/register/login.js +5 -0
  72. package/lib/commonjs/register/login.js.map +1 -1
  73. package/lib/commonjs/store/store.js +6 -0
  74. package/lib/commonjs/store/store.js.map +1 -1
  75. package/lib/commonjs/support/ComnyxSupport.js +77 -16
  76. package/lib/commonjs/support/ComnyxSupport.js.map +1 -1
  77. package/lib/commonjs/support/SupportConfigContext.js +66 -0
  78. package/lib/commonjs/support/SupportConfigContext.js.map +1 -0
  79. package/lib/commonjs/support/index.js +7 -0
  80. package/lib/commonjs/support/index.js.map +1 -1
  81. package/lib/commonjs/types/Theme.js +30 -2
  82. package/lib/commonjs/types/Theme.js.map +1 -1
  83. package/lib/commonjs/version.js +1 -1
  84. package/lib/module/NativeComnyxMediaPicker.js +18 -0
  85. package/lib/module/NativeComnyxMediaPicker.js.map +1 -1
  86. package/lib/module/api/conversations.js +6 -6
  87. package/lib/module/api/conversations.js.map +1 -1
  88. package/lib/module/api/customers.js +3 -2
  89. package/lib/module/api/customers.js.map +1 -1
  90. package/lib/module/api/media.js +21 -6
  91. package/lib/module/api/media.js.map +1 -1
  92. package/lib/module/api/messages.js +3 -2
  93. package/lib/module/api/messages.js.map +1 -1
  94. package/lib/module/components/ChatList.js +133 -91
  95. package/lib/module/components/ChatList.js.map +1 -1
  96. package/lib/module/components/ComnyxErrorBoundary.js +87 -0
  97. package/lib/module/components/ComnyxErrorBoundary.js.map +1 -0
  98. package/lib/module/components/CustomerForm.js +7 -3
  99. package/lib/module/components/CustomerForm.js.map +1 -1
  100. package/lib/module/components/InitFailed.js +79 -23
  101. package/lib/module/components/InitFailed.js.map +1 -1
  102. package/lib/module/components/MediaMessageItem.js +37 -11
  103. package/lib/module/components/MediaMessageItem.js.map +1 -1
  104. package/lib/module/components/MediaViewerModal.js +15 -3
  105. package/lib/module/components/MediaViewerModal.js.map +1 -1
  106. package/lib/module/components/MessageInput.js +84 -16
  107. package/lib/module/components/MessageInput.js.map +1 -1
  108. package/lib/module/components/MessageItem.js +1 -1
  109. package/lib/module/components/MessageItem.js.map +1 -1
  110. package/lib/module/hooks/usePolling.js +30 -21
  111. package/lib/module/hooks/usePolling.js.map +1 -1
  112. package/lib/module/hooks/useThemeColors.js +13 -2
  113. package/lib/module/hooks/useThemeColors.js.map +1 -1
  114. package/lib/module/index.js +1 -0
  115. package/lib/module/index.js.map +1 -1
  116. package/lib/module/notifications/initializeNotifications.js +19 -16
  117. package/lib/module/notifications/initializeNotifications.js.map +1 -1
  118. package/lib/module/register/Accumulator.js +19 -6
  119. package/lib/module/register/Accumulator.js.map +1 -1
  120. package/lib/module/register/collectData.js +1 -1
  121. package/lib/module/register/collectData.js.map +1 -1
  122. package/lib/module/register/login.js +5 -0
  123. package/lib/module/register/login.js.map +1 -1
  124. package/lib/module/store/store.js +6 -0
  125. package/lib/module/store/store.js.map +1 -1
  126. package/lib/module/support/ComnyxSupport.js +79 -18
  127. package/lib/module/support/ComnyxSupport.js.map +1 -1
  128. package/lib/module/support/SupportConfigContext.js +59 -0
  129. package/lib/module/support/SupportConfigContext.js.map +1 -0
  130. package/lib/module/support/index.js +1 -0
  131. package/lib/module/support/index.js.map +1 -1
  132. package/lib/module/types/Theme.js +30 -2
  133. package/lib/module/types/Theme.js.map +1 -1
  134. package/lib/module/version.js +1 -1
  135. package/lib/typescript/src/NativeComnyxMediaPicker.d.ts +9 -0
  136. package/lib/typescript/src/NativeComnyxMediaPicker.d.ts.map +1 -1
  137. package/lib/typescript/src/api/conversations.d.ts +2 -2
  138. package/lib/typescript/src/api/conversations.d.ts.map +1 -1
  139. package/lib/typescript/src/api/customers.d.ts +1 -1
  140. package/lib/typescript/src/api/customers.d.ts.map +1 -1
  141. package/lib/typescript/src/api/media.d.ts +3 -3
  142. package/lib/typescript/src/api/media.d.ts.map +1 -1
  143. package/lib/typescript/src/api/messages.d.ts +1 -1
  144. package/lib/typescript/src/api/messages.d.ts.map +1 -1
  145. package/lib/typescript/src/components/ChatList.d.ts.map +1 -1
  146. package/lib/typescript/src/components/ComnyxErrorBoundary.d.ts +18 -0
  147. package/lib/typescript/src/components/ComnyxErrorBoundary.d.ts.map +1 -0
  148. package/lib/typescript/src/components/CustomerForm.d.ts.map +1 -1
  149. package/lib/typescript/src/components/InitFailed.d.ts +5 -2
  150. package/lib/typescript/src/components/InitFailed.d.ts.map +1 -1
  151. package/lib/typescript/src/components/MediaMessageItem.d.ts.map +1 -1
  152. package/lib/typescript/src/components/MediaViewerModal.d.ts.map +1 -1
  153. package/lib/typescript/src/components/MessageInput.d.ts.map +1 -1
  154. package/lib/typescript/src/components/MessageItem.d.ts.map +1 -1
  155. package/lib/typescript/src/hooks/usePolling.d.ts.map +1 -1
  156. package/lib/typescript/src/hooks/useThemeColors.d.ts.map +1 -1
  157. package/lib/typescript/src/index.d.ts +3 -0
  158. package/lib/typescript/src/index.d.ts.map +1 -1
  159. package/lib/typescript/src/notifications/initializeNotifications.d.ts.map +1 -1
  160. package/lib/typescript/src/register/Accumulator.d.ts.map +1 -1
  161. package/lib/typescript/src/register/collectData.d.ts +4 -1
  162. package/lib/typescript/src/register/collectData.d.ts.map +1 -1
  163. package/lib/typescript/src/register/login.d.ts.map +1 -1
  164. package/lib/typescript/src/store/store.d.ts +6 -2
  165. package/lib/typescript/src/store/store.d.ts.map +1 -1
  166. package/lib/typescript/src/support/ComnyxSupport.d.ts +80 -3
  167. package/lib/typescript/src/support/ComnyxSupport.d.ts.map +1 -1
  168. package/lib/typescript/src/support/SupportConfigContext.d.ts +98 -0
  169. package/lib/typescript/src/support/SupportConfigContext.d.ts.map +1 -0
  170. package/lib/typescript/src/support/index.d.ts +2 -0
  171. package/lib/typescript/src/support/index.d.ts.map +1 -1
  172. package/lib/typescript/src/types/Conversation.d.ts +2 -2
  173. package/lib/typescript/src/types/Conversation.d.ts.map +1 -1
  174. package/lib/typescript/src/types/Customer.d.ts +1 -1
  175. package/lib/typescript/src/types/Customer.d.ts.map +1 -1
  176. package/lib/typescript/src/types/MessageResponse.d.ts +7 -4
  177. package/lib/typescript/src/types/MessageResponse.d.ts.map +1 -1
  178. package/lib/typescript/src/types/Theme.d.ts +26 -0
  179. package/lib/typescript/src/types/Theme.d.ts.map +1 -1
  180. package/lib/typescript/src/version.d.ts +1 -1
  181. package/package.json +19 -30
  182. package/src/NativeComnyxMediaPicker.ts +18 -0
  183. package/src/api/conversations.ts +6 -4
  184. package/src/api/customers.ts +3 -1
  185. package/src/api/media.ts +32 -10
  186. package/src/api/messages.ts +3 -1
  187. package/src/components/ChatList.tsx +147 -99
  188. package/src/components/ComnyxErrorBoundary.tsx +91 -0
  189. package/src/components/CustomerForm.tsx +7 -3
  190. package/src/components/InitFailed.tsx +80 -16
  191. package/src/components/MediaMessageItem.tsx +48 -11
  192. package/src/components/MediaViewerModal.tsx +21 -8
  193. package/src/components/MessageInput.tsx +108 -17
  194. package/src/components/MessageItem.tsx +12 -13
  195. package/src/hooks/usePolling.ts +26 -11
  196. package/src/hooks/useThemeColors.ts +11 -2
  197. package/src/index.ts +16 -0
  198. package/src/notifications/initializeNotifications.ts +22 -20
  199. package/src/register/Accumulator.ts +26 -9
  200. package/src/register/collectData.ts +10 -2
  201. package/src/register/login.ts +5 -0
  202. package/src/store/store.ts +11 -3
  203. package/src/support/ComnyxSupport.tsx +172 -23
  204. package/src/support/SupportConfigContext.tsx +157 -0
  205. package/src/support/index.ts +11 -0
  206. package/src/types/Conversation.ts +2 -2
  207. package/src/types/Customer.ts +1 -2
  208. package/src/types/MessageResponse.ts +4 -4
  209. package/src/types/Theme.ts +38 -0
  210. package/src/version.ts +1 -1
@@ -31,6 +31,10 @@ import { ScaledSheet } from './ScaledSheet';
31
31
  import { formatDate, getDateKey } from '../utils/formatDate';
32
32
  import { activeOpacity } from '../constants/activeOpacity';
33
33
  import { useAppStore } from '../store/store';
34
+ import {
35
+ useSupportConfig,
36
+ reportSupportError,
37
+ } from '../support/SupportConfigContext';
34
38
 
35
39
  const headphonesIcon = require('../assets/headphones-01.png');
36
40
  const closeIcon = require('../assets/x-close.png');
@@ -110,6 +114,7 @@ function CustomToast() {
110
114
  const { toastMessage } = useAppStore((s) => ({
111
115
  toastMessage: s.toastMessage,
112
116
  }));
117
+ const themeColors = useThemeColors();
113
118
 
114
119
  const fadeAnim = useRef(new Animated.Value(0)).current;
115
120
  const translateY = useRef(new Animated.Value(50)).current;
@@ -157,8 +162,16 @@ function CustomToast() {
157
162
  },
158
163
  ]}
159
164
  >
160
- <View style={styles.toastContent}>
161
- <AppText style={styles.toastText} localization={toastMessage} />
165
+ <View
166
+ style={[
167
+ styles.toastContent,
168
+ { backgroundColor: themeColors.toastBackground },
169
+ ]}
170
+ >
171
+ <AppText
172
+ style={[styles.toastText, { color: themeColors.toastText }]}
173
+ localization={toastMessage}
174
+ />
162
175
  </View>
163
176
  </Animated.View>
164
177
  );
@@ -174,6 +187,12 @@ export function ChatList({
174
187
  const themeColors = useThemeColors();
175
188
  const { height: windowHeight } = useWindowDimensions();
176
189
  const insets = useSafeAreaInsets();
190
+ const {
191
+ renderHeader: renderHeaderOverride,
192
+ renderEmptyState: renderEmptyStateOverride,
193
+ renderErrorState: renderErrorStateOverride,
194
+ renderMessage: renderMessageOverride,
195
+ } = useSupportConfig();
177
196
  const MESSAGE_MIN_HEIGHT = 60;
178
197
  const FLATLIST_PADDING = 20;
179
198
  const MESSAGES_PER_PAGE = Math.ceil(
@@ -184,15 +203,29 @@ export function ChatList({
184
203
  setLoading((l) => l && initLoading);
185
204
  }, [initLoading]);
186
205
 
187
- const { data, setData, customer, language } = useAppStore((s) => ({
206
+ const lifecycleAbortRef = useRef<AbortController | null>(null);
207
+ if (lifecycleAbortRef.current === null) {
208
+ lifecycleAbortRef.current = new AbortController();
209
+ }
210
+ useEffect(() => {
211
+ const controller = lifecycleAbortRef.current;
212
+ return () => {
213
+ controller?.abort();
214
+ };
215
+ }, []);
216
+
217
+ const { data, setData, customer, language, theme } = useAppStore((s) => ({
188
218
  data: s.data,
189
219
  setData: s.setData,
190
220
  customer: s.customer,
191
221
  language: s.language,
222
+ theme: s.theme,
192
223
  }));
193
224
  const ref = useRef<SectionList<AppConversationMessage>>(null);
194
225
  const [page, setPage] = useState(1);
195
- const nextPageStatus = useRef<'fail' | 'loading' | 'empty'>();
226
+ const nextPageStatus = useRef<'fail' | 'loading' | 'empty' | undefined>(
227
+ undefined
228
+ );
196
229
  const [nexPageFailed, setNexPageFailed] = useState(false);
197
230
  const [initFailed, setInitFailed] = useState(false);
198
231
  const [showScrollDownButton, setShowScrollDownButton] = useState(false);
@@ -375,7 +408,11 @@ export function ChatList({
375
408
  }
376
409
  }
377
410
 
378
- console.error('Mesajı yeniden gönderme hatası:', error);
411
+ reportSupportError(error, {
412
+ section: 'send',
413
+ recoverable: true,
414
+ extras: { retry: true, contentLength: selectedMessage.length },
415
+ });
379
416
  setSelectedMessage('');
380
417
  });
381
418
  }
@@ -398,7 +435,8 @@ export function ChatList({
398
435
  {
399
436
  fake: useAppStore.getState().fake,
400
437
  per_page: MESSAGES_PER_PAGE,
401
- }
438
+ },
439
+ lifecycleAbortRef.current?.signal
402
440
  )
403
441
  .then((newData) => {
404
442
  listChangedRef.current = true;
@@ -433,9 +471,14 @@ export function ChatList({
433
471
  }
434
472
  });
435
473
  })
436
- .catch((_) => {
474
+ .catch((err) => {
437
475
  nextPageStatus.current = 'fail';
438
476
  setNexPageFailed(true);
477
+ reportSupportError(err, {
478
+ section: 'pagination',
479
+ recoverable: true,
480
+ extras: { page, perPage: MESSAGES_PER_PAGE },
481
+ });
439
482
  })
440
483
  .finally(() => {
441
484
  setLoading(false);
@@ -445,7 +488,7 @@ export function ChatList({
445
488
 
446
489
  const renderItem = useCallback(
447
490
  ({ item }: { item: AppConversationMessage }) => {
448
- return (
491
+ const defaultNode = (
449
492
  <MessageItem
450
493
  item={item}
451
494
  onShowPopup={() => {
@@ -454,8 +497,12 @@ export function ChatList({
454
497
  }}
455
498
  />
456
499
  );
500
+ if (renderMessageOverride) {
501
+ return <>{renderMessageOverride({ message: item, defaultNode })}</>;
502
+ }
503
+ return defaultNode;
457
504
  },
458
- []
505
+ [renderMessageOverride]
459
506
  );
460
507
 
461
508
  const renderSectionFooter = useCallback(
@@ -515,10 +562,16 @@ export function ChatList({
515
562
 
516
563
  useEffect(() => {
517
564
  if (customer?.external_id && !initFailed) {
518
- getCustomerConversation(customer?.external_id, new Date(), 1, {
519
- fake: useAppStore.getState().fake,
520
- per_page: MESSAGES_PER_PAGE,
521
- })
565
+ getCustomerConversation(
566
+ customer?.external_id,
567
+ new Date(),
568
+ 1,
569
+ {
570
+ fake: useAppStore.getState().fake,
571
+ per_page: MESSAGES_PER_PAGE,
572
+ },
573
+ lifecycleAbortRef.current?.signal
574
+ )
522
575
  .then((newData) => {
523
576
  setData((prevData) => {
524
577
  const newMessages = newData?.page?.data ?? [];
@@ -552,7 +605,11 @@ export function ChatList({
552
605
  })
553
606
  .catch((e) => {
554
607
  setInitFailed(true);
555
- console.error(e);
608
+ reportSupportError(e, {
609
+ section: 'init',
610
+ recoverable: true,
611
+ extras: { perPage: MESSAGES_PER_PAGE },
612
+ });
556
613
  })
557
614
  .finally(() => {
558
615
  setLoading(false);
@@ -584,38 +641,31 @@ export function ChatList({
584
641
 
585
642
  if (nexPageFailed) {
586
643
  return (
587
- <View
588
- style={[
589
- styles.retryContainer,
590
- { backgroundColor: themeColors.background },
591
- ]}
592
- >
593
- <TouchableOpacity
594
- style={[styles.retryButton, { backgroundColor: themeColors.primary }]}
595
- onPress={() => {
596
- setNexPageFailed(false);
597
- nextPageStatus.current = undefined;
598
- nextPage();
599
- }}
600
- activeOpacity={activeOpacity}
601
- >
602
- <AppText
603
- localization="chat.load.error"
604
- style={[styles.retryText, { color: themeColors.background }]}
605
- />
606
- </TouchableOpacity>
607
- </View>
644
+ <InitFailed
645
+ localization="chat.load.error"
646
+ onBack={onBack}
647
+ onRetry={() => {
648
+ setNexPageFailed(false);
649
+ nextPageStatus.current = undefined;
650
+ nextPage();
651
+ }}
652
+ />
608
653
  );
609
654
  }
610
655
 
611
656
  if (initFailed) {
612
- return <InitFailed setInitFailed={setInitFailed} />;
657
+ if (renderErrorStateOverride) {
658
+ return (
659
+ <>{renderErrorStateOverride({ retry: () => setInitFailed(false) })}</>
660
+ );
661
+ }
662
+ return <InitFailed onRetry={() => setInitFailed(false)} onBack={onBack} />;
613
663
  }
614
664
 
615
665
  return (
616
666
  <>
617
667
  <StatusBar
618
- barStyle={'dark-content'}
668
+ barStyle={theme === 'dark' ? 'light-content' : 'dark-content'}
619
669
  backgroundColor={themeColors.background}
620
670
  animated={false}
621
671
  translucent
@@ -633,49 +683,60 @@ export function ChatList({
633
683
  },
634
684
  ]}
635
685
  >
636
- <TouchableOpacity
637
- style={[styles.iconContainer, { top: insets.top || 40 }]}
638
- onPress={onBack}
639
- activeOpacity={activeOpacity}
640
- >
641
- <Image
642
- source={closeIcon}
643
- style={[styles.closeIcon, { tintColor: themeColors.text }]}
644
- />
645
- </TouchableOpacity>
646
- <View
647
- style={[
648
- styles.headerContainer,
649
- {
650
- backgroundColor: themeColors.background,
651
- borderBottomColor: themeColors.lavender,
652
- },
653
- ]}
654
- >
655
- <AppText
656
- localization="chat.support-team"
657
- weight={'600'}
658
- style={[styles.header, { color: themeColors.text }]}
659
- />
660
- <View
661
- style={[
662
- styles.headerText,
663
- { backgroundColor: themeColors.background },
664
- ]}
665
- >
666
- <Image
667
- source={headphonesIcon}
668
- style={[styles.headphonesIcon, { tintColor: themeColors.text }]}
669
- />
670
- <AppText
671
- localization="chat.live"
672
- style={[styles.liveChat, { color: themeColors.text }]}
673
- />
686
+ {renderHeaderOverride ? (
687
+ <>{renderHeaderOverride({ onBack })}</>
688
+ ) : (
689
+ <>
690
+ <TouchableOpacity
691
+ style={[styles.iconContainer, { top: insets.top || 40 }]}
692
+ onPress={onBack}
693
+ activeOpacity={activeOpacity}
694
+ accessibilityRole="button"
695
+ accessibilityLabel="Close support"
696
+ >
697
+ <Image
698
+ source={closeIcon}
699
+ style={[styles.closeIcon, { tintColor: themeColors.text }]}
700
+ />
701
+ </TouchableOpacity>
674
702
  <View
675
- style={[styles.dot, { backgroundColor: themeColors.green }]}
676
- />
677
- </View>
678
- </View>
703
+ style={[
704
+ styles.headerContainer,
705
+ {
706
+ backgroundColor: themeColors.background,
707
+ borderBottomColor: themeColors.lavender,
708
+ },
709
+ ]}
710
+ >
711
+ <AppText
712
+ localization="chat.support-team"
713
+ weight={'600'}
714
+ style={[styles.header, { color: themeColors.text }]}
715
+ />
716
+ <View
717
+ style={[
718
+ styles.headerText,
719
+ { backgroundColor: themeColors.background },
720
+ ]}
721
+ >
722
+ <Image
723
+ source={headphonesIcon}
724
+ style={[
725
+ styles.headphonesIcon,
726
+ { tintColor: themeColors.text },
727
+ ]}
728
+ />
729
+ <AppText
730
+ localization="chat.live"
731
+ style={[styles.liveChat, { color: themeColors.text }]}
732
+ />
733
+ <View
734
+ style={[styles.dot, { backgroundColor: themeColors.green }]}
735
+ />
736
+ </View>
737
+ </View>
738
+ </>
739
+ )}
679
740
 
680
741
  <View style={styles.listContainer}>
681
742
  <SectionList
@@ -691,7 +752,11 @@ export function ChatList({
691
752
  style={styles.list}
692
753
  ListEmptyComponent={
693
754
  !loading && (!sections || sections.length === 0) ? (
694
- <EmptyList />
755
+ renderEmptyStateOverride ? (
756
+ <>{renderEmptyStateOverride()}</>
757
+ ) : (
758
+ <EmptyList />
759
+ )
695
760
  ) : null
696
761
  }
697
762
  ListFooterComponent={loading ? <LoadingItem /> : null}
@@ -722,6 +787,8 @@ export function ChatList({
722
787
  isKeyboardVisible && styles.scrollDownButtonWithKeyboard,
723
788
  ]}
724
789
  onPress={() => scrollToBottom(true)}
790
+ accessibilityRole="button"
791
+ accessibilityLabel="Scroll to latest message"
725
792
  >
726
793
  <Image
727
794
  source={require('../assets/down.png')}
@@ -798,23 +865,6 @@ const styles = ScaledSheet.create({
798
865
  width: '18@vs',
799
866
  height: '18@vs',
800
867
  },
801
- retryContainer: {
802
- flex: 1,
803
- alignItems: 'center',
804
- justifyContent: 'center',
805
- paddingHorizontal: '20@s',
806
- paddingVertical: '20@vs',
807
- },
808
- retryButton: {
809
- padding: 15,
810
- borderRadius: '8@s',
811
- alignItems: 'center',
812
- justifyContent: 'center',
813
- },
814
- retryText: {
815
- fontSize: '16@vs',
816
- fontWeight: '500',
817
- },
818
868
  headphonesIcon: {
819
869
  width: '24@vs',
820
870
  height: '24@vs',
@@ -880,7 +930,6 @@ const styles = ScaledSheet.create({
880
930
  zIndex: 9999,
881
931
  },
882
932
  toastContent: {
883
- backgroundColor: '#333333',
884
933
  borderRadius: '8@s',
885
934
  paddingHorizontal: '16@s',
886
935
  paddingVertical: '12@vs',
@@ -895,7 +944,6 @@ const styles = ScaledSheet.create({
895
944
  maxWidth: '90%',
896
945
  },
897
946
  toastText: {
898
- color: '#FFFFFF',
899
947
  fontSize: '14@vs',
900
948
  fontWeight: '400',
901
949
  textAlign: 'center',
@@ -0,0 +1,91 @@
1
+ import { Component, type ReactNode, type ErrorInfo } from 'react';
2
+ import { View, TouchableOpacity, Image, Text } from 'react-native';
3
+ import { ScaledSheet } from './ScaledSheet';
4
+
5
+ const closeIcon = require('../assets/x-close.png');
6
+
7
+ interface Props {
8
+ children: ReactNode;
9
+ onBack?: () => void;
10
+ fallback?: (reset: () => void) => ReactNode;
11
+ }
12
+
13
+ interface State {
14
+ hasError: boolean;
15
+ }
16
+
17
+ export class ComnyxErrorBoundary extends Component<Props, State> {
18
+ state: State = { hasError: false };
19
+
20
+ static getDerivedStateFromError(): State {
21
+ return { hasError: true };
22
+ }
23
+
24
+ componentDidCatch(error: Error, info: ErrorInfo) {
25
+ console.error('[Comnyx] UI error caught by boundary', error, info);
26
+ }
27
+
28
+ reset = () => {
29
+ this.setState({ hasError: false });
30
+ };
31
+
32
+ render() {
33
+ if (!this.state.hasError) {
34
+ return this.props.children;
35
+ }
36
+
37
+ if (this.props.fallback) {
38
+ return this.props.fallback(this.reset);
39
+ }
40
+
41
+ return (
42
+ <View style={styles.container}>
43
+ {this.props.onBack ? (
44
+ <TouchableOpacity
45
+ activeOpacity={1}
46
+ style={styles.iconContainer}
47
+ onPress={this.props.onBack}
48
+ >
49
+ <Image source={closeIcon} style={styles.closeIcon} />
50
+ </TouchableOpacity>
51
+ ) : null}
52
+ <Text style={styles.message}>Something went wrong.</Text>
53
+ <TouchableOpacity onPress={this.reset} style={styles.retryButton}>
54
+ <Text style={styles.retryLabel}>Retry</Text>
55
+ </TouchableOpacity>
56
+ </View>
57
+ );
58
+ }
59
+ }
60
+
61
+ const styles = ScaledSheet.create({
62
+ container: {
63
+ flex: 1,
64
+ alignItems: 'center',
65
+ justifyContent: 'center',
66
+ paddingHorizontal: '20@s',
67
+ paddingVertical: '20@vs',
68
+ },
69
+ iconContainer: {
70
+ position: 'absolute',
71
+ top: '60@vs',
72
+ left: '24@s',
73
+ },
74
+ closeIcon: {
75
+ width: '24@vs',
76
+ height: '24@vs',
77
+ },
78
+ message: {
79
+ textAlign: 'center',
80
+ fontSize: '14@vs',
81
+ },
82
+ retryButton: {
83
+ marginTop: '16@vs',
84
+ paddingHorizontal: '16@s',
85
+ paddingVertical: '10@vs',
86
+ },
87
+ retryLabel: {
88
+ fontSize: '14@vs',
89
+ fontWeight: '500',
90
+ },
91
+ });
@@ -23,6 +23,7 @@ import { ScaledSheet } from './ScaledSheet';
23
23
  import type { LocalizationKeys } from '../types/LocalizationKeys';
24
24
  import { activeOpacity } from '../constants/activeOpacity';
25
25
  import { useAppStore } from '../store/store';
26
+ import { reportSupportError } from '../support/SupportConfigContext';
26
27
 
27
28
  interface CustomerFormData {
28
29
  name: string;
@@ -84,7 +85,10 @@ export function CustomerForm({
84
85
  useAppStore.getState().setForm(res.customer);
85
86
  }
86
87
  } catch (error) {
87
- console.error('Error creating customer:', error);
88
+ reportSupportError(error, {
89
+ section: 'customer-form',
90
+ recoverable: true,
91
+ });
88
92
  }
89
93
  };
90
94
 
@@ -244,7 +248,7 @@ export function CustomerForm({
244
248
  },
245
249
  ]}
246
250
  >
247
- Email
251
+ {localize('customer.form.email')}
248
252
  </AppText>
249
253
  <Controller
250
254
  control={control}
@@ -291,7 +295,7 @@ export function CustomerForm({
291
295
  },
292
296
  ]}
293
297
  >
294
- Phone
298
+ {localize('customer.form.phone')}
295
299
  </AppText>
296
300
  <Controller
297
301
  control={control}
@@ -1,28 +1,73 @@
1
- import { TouchableOpacity, View } from 'react-native';
1
+ import { Image, TouchableOpacity, View } from 'react-native';
2
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
2
3
  import { useThemeColors } from '../hooks/useThemeColors';
3
4
  import { AppText } from './AppText';
4
5
  import { ScaledSheet } from './ScaledSheet';
5
6
  import { activeOpacity } from '../constants/activeOpacity';
7
+ import type { LocalizationKeys } from '../types/LocalizationKeys';
8
+
9
+ const closeIcon = require('../assets/x-close.png');
10
+ const errorIcon = require('../assets/x-circle.png');
6
11
 
7
12
  interface InitFailedProps {
8
- setInitFailed: (value: boolean) => void;
13
+ onRetry: () => void;
14
+ onBack?: () => void;
15
+ localization?: keyof LocalizationKeys;
9
16
  }
10
17
 
11
- export function InitFailed({ setInitFailed }: InitFailedProps) {
18
+ export function InitFailed({
19
+ onRetry,
20
+ onBack,
21
+ localization = 'chat.init.error',
22
+ }: InitFailedProps) {
12
23
  const themeColors = useThemeColors();
24
+ const insets = useSafeAreaInsets();
13
25
 
14
26
  return (
15
27
  <View
16
28
  style={[styles.container, { backgroundColor: themeColors.background }]}
17
29
  >
30
+ {onBack ? (
31
+ <TouchableOpacity
32
+ activeOpacity={1}
33
+ style={[styles.closeButton, { top: Math.max(insets.top, 16) + 16 }]}
34
+ onPress={onBack}
35
+ accessibilityRole="button"
36
+ accessibilityLabel="Close support"
37
+ hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
38
+ >
39
+ <Image
40
+ source={closeIcon}
41
+ style={[styles.closeIcon, { tintColor: themeColors.text }]}
42
+ />
43
+ </TouchableOpacity>
44
+ ) : null}
45
+
18
46
  <TouchableOpacity
19
47
  activeOpacity={activeOpacity}
20
- style={[styles.retryButton, { backgroundColor: themeColors.primary }]}
21
- onPress={() => setInitFailed(false)}
48
+ style={styles.card}
49
+ onPress={onRetry}
50
+ accessibilityRole="button"
51
+ accessibilityLabel="Retry"
22
52
  >
53
+ <View
54
+ style={[
55
+ styles.iconCircle,
56
+ {
57
+ backgroundColor: themeColors.ghost,
58
+ borderColor: themeColors.lavender,
59
+ },
60
+ ]}
61
+ >
62
+ <Image
63
+ source={errorIcon}
64
+ style={[styles.errorIcon, { tintColor: themeColors.error }]}
65
+ />
66
+ </View>
67
+
23
68
  <AppText
24
- localization="chat.init.error"
25
- style={[styles.retryText, { color: themeColors.background }]}
69
+ localization={localization}
70
+ style={[styles.description, { color: themeColors.text }]}
26
71
  />
27
72
  </TouchableOpacity>
28
73
  </View>
@@ -34,18 +79,37 @@ const styles = ScaledSheet.create({
34
79
  flex: 1,
35
80
  alignItems: 'center',
36
81
  justifyContent: 'center',
37
- paddingHorizontal: '20@s',
38
- paddingVertical: '20@vs',
82
+ paddingHorizontal: '24@s',
83
+ },
84
+ closeButton: {
85
+ position: 'absolute',
86
+ left: '24@s',
39
87
  },
40
- retryButton: {
41
- paddingHorizontal: '15@s',
42
- paddingVertical: '15@vs',
43
- borderRadius: '8@s',
88
+ closeIcon: {
89
+ width: '24@vs',
90
+ height: '24@vs',
91
+ },
92
+ card: {
93
+ alignItems: 'center',
94
+ gap: '16@vs',
95
+ maxWidth: '340@s',
96
+ },
97
+ iconCircle: {
98
+ width: '72@vs',
99
+ height: '72@vs',
100
+ borderRadius: '36@vs',
101
+ borderWidth: 1,
44
102
  alignItems: 'center',
45
103
  justifyContent: 'center',
46
104
  },
47
- retryText: {
48
- fontSize: '16@vs',
49
- fontWeight: '500',
105
+ errorIcon: {
106
+ width: '36@vs',
107
+ height: '36@vs',
108
+ },
109
+ description: {
110
+ fontSize: '15@vs',
111
+ lineHeight: '22@vs',
112
+ textAlign: 'center',
113
+ opacity: 0.85,
50
114
  },
51
115
  });