@developer_tribe/react-native-comnyx 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Comnyx.podspec +10 -2
- package/README.md +50 -0
- package/android/build.gradle +1 -0
- package/android/consumer-rules.pro +23 -0
- package/android/generated/java/com/comnyx/NativeComnyxSpec.java +46 -0
- package/android/generated/jni/RNComnyxSpec-generated.cpp +23 -1
- package/android/generated/jni/RNComnyxSpec.h +7 -0
- package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI-generated.cpp +21 -0
- package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI.h +70 -0
- package/android/src/main/AndroidManifest.xml +11 -1
- package/android/src/main/AndroidManifestNew.xml +11 -1
- package/android/src/main/java/com/comnyx/ComnyxMediaPickerModule.kt +91 -51
- package/android/src/main/java/com/comnyx/ComnyxModule.kt +7 -0
- package/android/src/main/java/com/comnyx/src/messaging/firebase/FirebaseMessagingService.kt +4 -6
- package/android/src/main/res/xml/comnyx_file_paths.xml +12 -0
- package/ios/APNService.swift +9 -9
- package/ios/Comnyx.swift +17 -8
- package/ios/ComnyxMediaPicker.swift +47 -26
- package/ios/ComnyxMessaging.swift +2 -0
- package/ios/PrivacyInfo.xcprivacy +32 -0
- package/ios/comnyx_post_install.rb +25 -0
- package/ios/generated/RCTAppDependencyProvider.h +25 -0
- package/ios/generated/RCTAppDependencyProvider.mm +55 -0
- package/ios/generated/RCTModulesConformingToProtocolsProvider.h +18 -0
- package/ios/generated/RCTModulesConformingToProtocolsProvider.mm +33 -0
- package/ios/generated/RCTThirdPartyComponentsProvider.h +16 -0
- package/ios/generated/RCTThirdPartyComponentsProvider.mm +23 -0
- package/ios/generated/RNComnyxSpec/RNComnyxSpec-generated.mm +53 -0
- package/ios/generated/RNComnyxSpec/RNComnyxSpec.h +67 -0
- package/ios/generated/RNComnyxSpecJSI-generated.cpp +38 -0
- package/ios/generated/RNComnyxSpecJSI.h +89 -0
- package/ios/generated/ReactAppDependencyProvider.podspec +34 -0
- package/lib/commonjs/NativeComnyxMediaPicker.js +19 -0
- package/lib/commonjs/NativeComnyxMediaPicker.js.map +1 -1
- package/lib/commonjs/api/conversations.js +6 -6
- package/lib/commonjs/api/conversations.js.map +1 -1
- package/lib/commonjs/api/customers.js +3 -2
- package/lib/commonjs/api/customers.js.map +1 -1
- package/lib/commonjs/api/media.js +20 -6
- package/lib/commonjs/api/media.js.map +1 -1
- package/lib/commonjs/api/messages.js +3 -2
- package/lib/commonjs/api/messages.js.map +1 -1
- package/lib/commonjs/components/ChatList.js +93 -45
- package/lib/commonjs/components/ChatList.js.map +1 -1
- package/lib/commonjs/components/ComnyxErrorBoundary.js +92 -0
- package/lib/commonjs/components/ComnyxErrorBoundary.js.map +1 -0
- package/lib/commonjs/components/CustomerForm.js +2 -2
- package/lib/commonjs/components/CustomerForm.js.map +1 -1
- package/lib/commonjs/components/MediaMessageItem.js +4 -3
- package/lib/commonjs/components/MediaMessageItem.js.map +1 -1
- package/lib/commonjs/components/MessageInput.js +63 -13
- package/lib/commonjs/components/MessageInput.js.map +1 -1
- package/lib/commonjs/components/MessageItem.js +1 -1
- package/lib/commonjs/components/MessageItem.js.map +1 -1
- package/lib/commonjs/hooks/usePolling.js +25 -21
- package/lib/commonjs/hooks/usePolling.js.map +1 -1
- package/lib/commonjs/hooks/useThemeColors.js +12 -1
- package/lib/commonjs/hooks/useThemeColors.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/notifications/initializeNotifications.js +19 -16
- package/lib/commonjs/notifications/initializeNotifications.js.map +1 -1
- package/lib/commonjs/register/Accumulator.js +19 -6
- package/lib/commonjs/register/Accumulator.js.map +1 -1
- package/lib/commonjs/register/collectData.js +1 -1
- package/lib/commonjs/register/collectData.js.map +1 -1
- package/lib/commonjs/store/store.js +6 -0
- package/lib/commonjs/store/store.js.map +1 -1
- package/lib/commonjs/support/ComnyxSupport.js +60 -15
- package/lib/commonjs/support/ComnyxSupport.js.map +1 -1
- package/lib/commonjs/support/SupportConfigContext.js +24 -0
- package/lib/commonjs/support/SupportConfigContext.js.map +1 -0
- package/lib/commonjs/types/Theme.js +30 -2
- package/lib/commonjs/types/Theme.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/module/NativeComnyxMediaPicker.js +18 -0
- package/lib/module/NativeComnyxMediaPicker.js.map +1 -1
- package/lib/module/api/conversations.js +6 -6
- package/lib/module/api/conversations.js.map +1 -1
- package/lib/module/api/customers.js +3 -2
- package/lib/module/api/customers.js.map +1 -1
- package/lib/module/api/media.js +21 -6
- package/lib/module/api/media.js.map +1 -1
- package/lib/module/api/messages.js +3 -2
- package/lib/module/api/messages.js.map +1 -1
- package/lib/module/components/ChatList.js +94 -46
- package/lib/module/components/ChatList.js.map +1 -1
- package/lib/module/components/ComnyxErrorBoundary.js +87 -0
- package/lib/module/components/ComnyxErrorBoundary.js.map +1 -0
- package/lib/module/components/CustomerForm.js +2 -2
- package/lib/module/components/CustomerForm.js.map +1 -1
- package/lib/module/components/MediaMessageItem.js +4 -3
- package/lib/module/components/MediaMessageItem.js.map +1 -1
- package/lib/module/components/MessageInput.js +64 -14
- package/lib/module/components/MessageInput.js.map +1 -1
- package/lib/module/components/MessageItem.js +1 -1
- package/lib/module/components/MessageItem.js.map +1 -1
- package/lib/module/hooks/usePolling.js +25 -21
- package/lib/module/hooks/usePolling.js.map +1 -1
- package/lib/module/hooks/useThemeColors.js +13 -2
- package/lib/module/hooks/useThemeColors.js.map +1 -1
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/notifications/initializeNotifications.js +19 -16
- package/lib/module/notifications/initializeNotifications.js.map +1 -1
- package/lib/module/register/Accumulator.js +19 -6
- package/lib/module/register/Accumulator.js.map +1 -1
- package/lib/module/register/collectData.js +1 -1
- package/lib/module/register/collectData.js.map +1 -1
- package/lib/module/store/store.js +6 -0
- package/lib/module/store/store.js.map +1 -1
- package/lib/module/support/ComnyxSupport.js +61 -16
- package/lib/module/support/ComnyxSupport.js.map +1 -1
- package/lib/module/support/SupportConfigContext.js +19 -0
- package/lib/module/support/SupportConfigContext.js.map +1 -0
- package/lib/module/types/Theme.js +30 -2
- package/lib/module/types/Theme.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/NativeComnyxMediaPicker.d.ts +9 -0
- package/lib/typescript/src/NativeComnyxMediaPicker.d.ts.map +1 -1
- package/lib/typescript/src/api/conversations.d.ts +2 -2
- package/lib/typescript/src/api/conversations.d.ts.map +1 -1
- package/lib/typescript/src/api/customers.d.ts +1 -1
- package/lib/typescript/src/api/customers.d.ts.map +1 -1
- package/lib/typescript/src/api/media.d.ts +3 -3
- package/lib/typescript/src/api/media.d.ts.map +1 -1
- package/lib/typescript/src/api/messages.d.ts +1 -1
- package/lib/typescript/src/api/messages.d.ts.map +1 -1
- package/lib/typescript/src/components/ChatList.d.ts.map +1 -1
- package/lib/typescript/src/components/ComnyxErrorBoundary.d.ts +18 -0
- package/lib/typescript/src/components/ComnyxErrorBoundary.d.ts.map +1 -0
- package/lib/typescript/src/components/MediaMessageItem.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageInput.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageItem.d.ts.map +1 -1
- package/lib/typescript/src/hooks/usePolling.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useThemeColors.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/notifications/initializeNotifications.d.ts.map +1 -1
- package/lib/typescript/src/register/Accumulator.d.ts.map +1 -1
- package/lib/typescript/src/register/collectData.d.ts +4 -1
- package/lib/typescript/src/register/collectData.d.ts.map +1 -1
- package/lib/typescript/src/store/store.d.ts +6 -2
- package/lib/typescript/src/store/store.d.ts.map +1 -1
- package/lib/typescript/src/support/ComnyxSupport.d.ts +56 -2
- package/lib/typescript/src/support/ComnyxSupport.d.ts.map +1 -1
- package/lib/typescript/src/support/SupportConfigContext.d.ts +58 -0
- package/lib/typescript/src/support/SupportConfigContext.d.ts.map +1 -0
- package/lib/typescript/src/support/index.d.ts +1 -0
- package/lib/typescript/src/support/index.d.ts.map +1 -1
- package/lib/typescript/src/types/Conversation.d.ts +2 -2
- package/lib/typescript/src/types/Conversation.d.ts.map +1 -1
- package/lib/typescript/src/types/Customer.d.ts +1 -1
- package/lib/typescript/src/types/Customer.d.ts.map +1 -1
- package/lib/typescript/src/types/MessageResponse.d.ts +7 -4
- package/lib/typescript/src/types/MessageResponse.d.ts.map +1 -1
- package/lib/typescript/src/types/Theme.d.ts +26 -0
- package/lib/typescript/src/types/Theme.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +12 -25
- package/src/NativeComnyxMediaPicker.ts +18 -0
- package/src/api/conversations.ts +6 -4
- package/src/api/customers.ts +3 -1
- package/src/api/media.ts +32 -10
- package/src/api/messages.ts +3 -1
- package/src/components/ChatList.tsx +115 -55
- package/src/components/ComnyxErrorBoundary.tsx +91 -0
- package/src/components/CustomerForm.tsx +2 -2
- package/src/components/MediaMessageItem.tsx +10 -3
- package/src/components/MessageInput.tsx +89 -16
- package/src/components/MessageItem.tsx +12 -13
- package/src/hooks/usePolling.ts +21 -11
- package/src/hooks/useThemeColors.ts +11 -2
- package/src/index.ts +12 -0
- package/src/notifications/initializeNotifications.ts +22 -20
- package/src/register/Accumulator.ts +26 -9
- package/src/register/collectData.ts +10 -2
- package/src/store/store.ts +11 -3
- package/src/support/ComnyxSupport.tsx +128 -22
- package/src/support/SupportConfigContext.tsx +79 -0
- package/src/support/index.ts +7 -0
- package/src/types/Conversation.ts +2 -2
- package/src/types/Customer.ts +1 -2
- package/src/types/MessageResponse.ts +4 -4
- package/src/types/Theme.ts +38 -0
- package/src/version.ts +1 -1
|
@@ -31,6 +31,7 @@ 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 { useSupportConfig } from '../support/SupportConfigContext';
|
|
34
35
|
|
|
35
36
|
const headphonesIcon = require('../assets/headphones-01.png');
|
|
36
37
|
const closeIcon = require('../assets/x-close.png');
|
|
@@ -110,6 +111,7 @@ function CustomToast() {
|
|
|
110
111
|
const { toastMessage } = useAppStore((s) => ({
|
|
111
112
|
toastMessage: s.toastMessage,
|
|
112
113
|
}));
|
|
114
|
+
const themeColors = useThemeColors();
|
|
113
115
|
|
|
114
116
|
const fadeAnim = useRef(new Animated.Value(0)).current;
|
|
115
117
|
const translateY = useRef(new Animated.Value(50)).current;
|
|
@@ -157,8 +159,16 @@ function CustomToast() {
|
|
|
157
159
|
},
|
|
158
160
|
]}
|
|
159
161
|
>
|
|
160
|
-
<View
|
|
161
|
-
|
|
162
|
+
<View
|
|
163
|
+
style={[
|
|
164
|
+
styles.toastContent,
|
|
165
|
+
{ backgroundColor: themeColors.toastBackground },
|
|
166
|
+
]}
|
|
167
|
+
>
|
|
168
|
+
<AppText
|
|
169
|
+
style={[styles.toastText, { color: themeColors.toastText }]}
|
|
170
|
+
localization={toastMessage}
|
|
171
|
+
/>
|
|
162
172
|
</View>
|
|
163
173
|
</Animated.View>
|
|
164
174
|
);
|
|
@@ -174,6 +184,12 @@ export function ChatList({
|
|
|
174
184
|
const themeColors = useThemeColors();
|
|
175
185
|
const { height: windowHeight } = useWindowDimensions();
|
|
176
186
|
const insets = useSafeAreaInsets();
|
|
187
|
+
const {
|
|
188
|
+
renderHeader: renderHeaderOverride,
|
|
189
|
+
renderEmptyState: renderEmptyStateOverride,
|
|
190
|
+
renderErrorState: renderErrorStateOverride,
|
|
191
|
+
renderMessage: renderMessageOverride,
|
|
192
|
+
} = useSupportConfig();
|
|
177
193
|
const MESSAGE_MIN_HEIGHT = 60;
|
|
178
194
|
const FLATLIST_PADDING = 20;
|
|
179
195
|
const MESSAGES_PER_PAGE = Math.ceil(
|
|
@@ -184,6 +200,17 @@ export function ChatList({
|
|
|
184
200
|
setLoading((l) => l && initLoading);
|
|
185
201
|
}, [initLoading]);
|
|
186
202
|
|
|
203
|
+
const lifecycleAbortRef = useRef<AbortController | null>(null);
|
|
204
|
+
if (lifecycleAbortRef.current === null) {
|
|
205
|
+
lifecycleAbortRef.current = new AbortController();
|
|
206
|
+
}
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
const controller = lifecycleAbortRef.current;
|
|
209
|
+
return () => {
|
|
210
|
+
controller?.abort();
|
|
211
|
+
};
|
|
212
|
+
}, []);
|
|
213
|
+
|
|
187
214
|
const { data, setData, customer, language } = useAppStore((s) => ({
|
|
188
215
|
data: s.data,
|
|
189
216
|
setData: s.setData,
|
|
@@ -192,7 +219,9 @@ export function ChatList({
|
|
|
192
219
|
}));
|
|
193
220
|
const ref = useRef<SectionList<AppConversationMessage>>(null);
|
|
194
221
|
const [page, setPage] = useState(1);
|
|
195
|
-
const nextPageStatus = useRef<'fail' | 'loading' | 'empty'>(
|
|
222
|
+
const nextPageStatus = useRef<'fail' | 'loading' | 'empty' | undefined>(
|
|
223
|
+
undefined
|
|
224
|
+
);
|
|
196
225
|
const [nexPageFailed, setNexPageFailed] = useState(false);
|
|
197
226
|
const [initFailed, setInitFailed] = useState(false);
|
|
198
227
|
const [showScrollDownButton, setShowScrollDownButton] = useState(false);
|
|
@@ -398,7 +427,8 @@ export function ChatList({
|
|
|
398
427
|
{
|
|
399
428
|
fake: useAppStore.getState().fake,
|
|
400
429
|
per_page: MESSAGES_PER_PAGE,
|
|
401
|
-
}
|
|
430
|
+
},
|
|
431
|
+
lifecycleAbortRef.current?.signal
|
|
402
432
|
)
|
|
403
433
|
.then((newData) => {
|
|
404
434
|
listChangedRef.current = true;
|
|
@@ -445,7 +475,7 @@ export function ChatList({
|
|
|
445
475
|
|
|
446
476
|
const renderItem = useCallback(
|
|
447
477
|
({ item }: { item: AppConversationMessage }) => {
|
|
448
|
-
|
|
478
|
+
const defaultNode = (
|
|
449
479
|
<MessageItem
|
|
450
480
|
item={item}
|
|
451
481
|
onShowPopup={() => {
|
|
@@ -454,8 +484,12 @@ export function ChatList({
|
|
|
454
484
|
}}
|
|
455
485
|
/>
|
|
456
486
|
);
|
|
487
|
+
if (renderMessageOverride) {
|
|
488
|
+
return <>{renderMessageOverride({ message: item, defaultNode })}</>;
|
|
489
|
+
}
|
|
490
|
+
return defaultNode;
|
|
457
491
|
},
|
|
458
|
-
[]
|
|
492
|
+
[renderMessageOverride]
|
|
459
493
|
);
|
|
460
494
|
|
|
461
495
|
const renderSectionFooter = useCallback(
|
|
@@ -515,10 +549,16 @@ export function ChatList({
|
|
|
515
549
|
|
|
516
550
|
useEffect(() => {
|
|
517
551
|
if (customer?.external_id && !initFailed) {
|
|
518
|
-
getCustomerConversation(
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
552
|
+
getCustomerConversation(
|
|
553
|
+
customer?.external_id,
|
|
554
|
+
new Date(),
|
|
555
|
+
1,
|
|
556
|
+
{
|
|
557
|
+
fake: useAppStore.getState().fake,
|
|
558
|
+
per_page: MESSAGES_PER_PAGE,
|
|
559
|
+
},
|
|
560
|
+
lifecycleAbortRef.current?.signal
|
|
561
|
+
)
|
|
522
562
|
.then((newData) => {
|
|
523
563
|
setData((prevData) => {
|
|
524
564
|
const newMessages = newData?.page?.data ?? [];
|
|
@@ -609,6 +649,11 @@ export function ChatList({
|
|
|
609
649
|
}
|
|
610
650
|
|
|
611
651
|
if (initFailed) {
|
|
652
|
+
if (renderErrorStateOverride) {
|
|
653
|
+
return (
|
|
654
|
+
<>{renderErrorStateOverride({ retry: () => setInitFailed(false) })}</>
|
|
655
|
+
);
|
|
656
|
+
}
|
|
612
657
|
return <InitFailed setInitFailed={setInitFailed} />;
|
|
613
658
|
}
|
|
614
659
|
|
|
@@ -633,49 +678,60 @@ export function ChatList({
|
|
|
633
678
|
},
|
|
634
679
|
]}
|
|
635
680
|
>
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
/>
|
|
681
|
+
{renderHeaderOverride ? (
|
|
682
|
+
<>{renderHeaderOverride({ onBack })}</>
|
|
683
|
+
) : (
|
|
684
|
+
<>
|
|
685
|
+
<TouchableOpacity
|
|
686
|
+
style={[styles.iconContainer, { top: insets.top || 40 }]}
|
|
687
|
+
onPress={onBack}
|
|
688
|
+
activeOpacity={activeOpacity}
|
|
689
|
+
accessibilityRole="button"
|
|
690
|
+
accessibilityLabel="Close support"
|
|
691
|
+
>
|
|
692
|
+
<Image
|
|
693
|
+
source={closeIcon}
|
|
694
|
+
style={[styles.closeIcon, { tintColor: themeColors.text }]}
|
|
695
|
+
/>
|
|
696
|
+
</TouchableOpacity>
|
|
674
697
|
<View
|
|
675
|
-
style={[
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
698
|
+
style={[
|
|
699
|
+
styles.headerContainer,
|
|
700
|
+
{
|
|
701
|
+
backgroundColor: themeColors.background,
|
|
702
|
+
borderBottomColor: themeColors.lavender,
|
|
703
|
+
},
|
|
704
|
+
]}
|
|
705
|
+
>
|
|
706
|
+
<AppText
|
|
707
|
+
localization="chat.support-team"
|
|
708
|
+
weight={'600'}
|
|
709
|
+
style={[styles.header, { color: themeColors.text }]}
|
|
710
|
+
/>
|
|
711
|
+
<View
|
|
712
|
+
style={[
|
|
713
|
+
styles.headerText,
|
|
714
|
+
{ backgroundColor: themeColors.background },
|
|
715
|
+
]}
|
|
716
|
+
>
|
|
717
|
+
<Image
|
|
718
|
+
source={headphonesIcon}
|
|
719
|
+
style={[
|
|
720
|
+
styles.headphonesIcon,
|
|
721
|
+
{ tintColor: themeColors.text },
|
|
722
|
+
]}
|
|
723
|
+
/>
|
|
724
|
+
<AppText
|
|
725
|
+
localization="chat.live"
|
|
726
|
+
style={[styles.liveChat, { color: themeColors.text }]}
|
|
727
|
+
/>
|
|
728
|
+
<View
|
|
729
|
+
style={[styles.dot, { backgroundColor: themeColors.green }]}
|
|
730
|
+
/>
|
|
731
|
+
</View>
|
|
732
|
+
</View>
|
|
733
|
+
</>
|
|
734
|
+
)}
|
|
679
735
|
|
|
680
736
|
<View style={styles.listContainer}>
|
|
681
737
|
<SectionList
|
|
@@ -691,7 +747,11 @@ export function ChatList({
|
|
|
691
747
|
style={styles.list}
|
|
692
748
|
ListEmptyComponent={
|
|
693
749
|
!loading && (!sections || sections.length === 0) ? (
|
|
694
|
-
|
|
750
|
+
renderEmptyStateOverride ? (
|
|
751
|
+
<>{renderEmptyStateOverride()}</>
|
|
752
|
+
) : (
|
|
753
|
+
<EmptyList />
|
|
754
|
+
)
|
|
695
755
|
) : null
|
|
696
756
|
}
|
|
697
757
|
ListFooterComponent={loading ? <LoadingItem /> : null}
|
|
@@ -722,6 +782,8 @@ export function ChatList({
|
|
|
722
782
|
isKeyboardVisible && styles.scrollDownButtonWithKeyboard,
|
|
723
783
|
]}
|
|
724
784
|
onPress={() => scrollToBottom(true)}
|
|
785
|
+
accessibilityRole="button"
|
|
786
|
+
accessibilityLabel="Scroll to latest message"
|
|
725
787
|
>
|
|
726
788
|
<Image
|
|
727
789
|
source={require('../assets/down.png')}
|
|
@@ -880,7 +942,6 @@ const styles = ScaledSheet.create({
|
|
|
880
942
|
zIndex: 9999,
|
|
881
943
|
},
|
|
882
944
|
toastContent: {
|
|
883
|
-
backgroundColor: '#333333',
|
|
884
945
|
borderRadius: '8@s',
|
|
885
946
|
paddingHorizontal: '16@s',
|
|
886
947
|
paddingVertical: '12@vs',
|
|
@@ -895,7 +956,6 @@ const styles = ScaledSheet.create({
|
|
|
895
956
|
maxWidth: '90%',
|
|
896
957
|
},
|
|
897
958
|
toastText: {
|
|
898
|
-
color: '#FFFFFF',
|
|
899
959
|
fontSize: '14@vs',
|
|
900
960
|
fontWeight: '400',
|
|
901
961
|
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
|
+
});
|
|
@@ -244,7 +244,7 @@ export function CustomerForm({
|
|
|
244
244
|
},
|
|
245
245
|
]}
|
|
246
246
|
>
|
|
247
|
-
|
|
247
|
+
{localize('customer.form.email')}
|
|
248
248
|
</AppText>
|
|
249
249
|
<Controller
|
|
250
250
|
control={control}
|
|
@@ -291,7 +291,7 @@ export function CustomerForm({
|
|
|
291
291
|
},
|
|
292
292
|
]}
|
|
293
293
|
>
|
|
294
|
-
|
|
294
|
+
{localize('customer.form.phone')}
|
|
295
295
|
</AppText>
|
|
296
296
|
<Controller
|
|
297
297
|
control={control}
|
|
@@ -30,6 +30,7 @@ function MediaThumbnail({
|
|
|
30
30
|
const [generatedThumb, setGeneratedThumb] = useState<string | null>(null);
|
|
31
31
|
const displayUri = file.local_uri || file.url;
|
|
32
32
|
const isVideo = file.type === 'video';
|
|
33
|
+
const themeColors = useThemeColors();
|
|
33
34
|
|
|
34
35
|
useEffect(() => {
|
|
35
36
|
if (isVideo && !file.thumbnail_uri && !isUploading && displayUri) {
|
|
@@ -62,8 +63,14 @@ function MediaThumbnail({
|
|
|
62
63
|
const wrapperStyle = single ? styles.singleMediaWrapper : styles.gridItem;
|
|
63
64
|
const imageStyle = single ? styles.singleMediaImage : styles.gridImage;
|
|
64
65
|
const placeholderStyle = single
|
|
65
|
-
? [
|
|
66
|
-
|
|
66
|
+
? [
|
|
67
|
+
styles.singleMediaImage,
|
|
68
|
+
{ backgroundColor: themeColors.videoPlaceholder },
|
|
69
|
+
]
|
|
70
|
+
: [
|
|
71
|
+
styles.videoPlaceholder,
|
|
72
|
+
{ backgroundColor: themeColors.videoPlaceholder },
|
|
73
|
+
];
|
|
67
74
|
|
|
68
75
|
return (
|
|
69
76
|
<TouchableOpacity
|
|
@@ -234,7 +241,7 @@ export function MediaMessageItem({
|
|
|
234
241
|
{
|
|
235
242
|
regex:
|
|
236
243
|
/https?:\/\/(?:[-\w.])+(?::[0-9]+)?(?:\/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:[\w.])*)?)?/g,
|
|
237
|
-
color:
|
|
244
|
+
color: themeColors.link,
|
|
238
245
|
navigate: (url) => {
|
|
239
246
|
if (url) {
|
|
240
247
|
Linking.openURL(url);
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
TouchableOpacity,
|
|
6
6
|
ScrollView,
|
|
7
7
|
} from 'react-native';
|
|
8
|
-
import { useState, useCallback } from 'react';
|
|
8
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
9
9
|
import { sendCustomerMessage } from '../api';
|
|
10
10
|
import { getUploadUrl, uploadFileToS3, sendMediaMessage } from '../api';
|
|
11
11
|
import { deleteTempFile } from '../NativeComnyxMediaPicker';
|
|
@@ -17,6 +17,7 @@ import { useIsRtl } from '../hooks/isRtl';
|
|
|
17
17
|
import { useAppStore } from '../store/store';
|
|
18
18
|
import { MediaPickerButton } from './MediaPickerButton';
|
|
19
19
|
import type { MediaAsset } from '../types/MediaTypes';
|
|
20
|
+
import { useSupportConfig } from '../support/SupportConfigContext';
|
|
20
21
|
|
|
21
22
|
const sendDark = require('../assets/arrow-right.png');
|
|
22
23
|
const circleXIcon = require('../assets/x-circle.png');
|
|
@@ -34,6 +35,20 @@ export function MessageInput({
|
|
|
34
35
|
const themeColors = useThemeColors();
|
|
35
36
|
const localize = useLocalize();
|
|
36
37
|
const isRtl = useIsRtl();
|
|
38
|
+
const { onBeforeSend } = useSupportConfig();
|
|
39
|
+
|
|
40
|
+
const uploadAbortRef = useRef<AbortController | null>(null);
|
|
41
|
+
const lifecycleAbortRef = useRef<AbortController | null>(null);
|
|
42
|
+
if (lifecycleAbortRef.current === null) {
|
|
43
|
+
lifecycleAbortRef.current = new AbortController();
|
|
44
|
+
}
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const controller = lifecycleAbortRef.current;
|
|
47
|
+
return () => {
|
|
48
|
+
uploadAbortRef.current?.abort();
|
|
49
|
+
controller?.abort();
|
|
50
|
+
};
|
|
51
|
+
}, []);
|
|
37
52
|
|
|
38
53
|
const sendTextOnlyMessage = useCallback(() => {
|
|
39
54
|
if (!value.trim()) return;
|
|
@@ -53,10 +68,14 @@ export function MessageInput({
|
|
|
53
68
|
},
|
|
54
69
|
...(data ?? []),
|
|
55
70
|
]);
|
|
56
|
-
sendCustomerMessage(
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
sendCustomerMessage(
|
|
72
|
+
customer.external_id as string,
|
|
73
|
+
value,
|
|
74
|
+
{ fake: useAppStore.getState().fake },
|
|
75
|
+
lifecycleAbortRef.current?.signal
|
|
76
|
+
)
|
|
59
77
|
.then((res) => {
|
|
78
|
+
if (lifecycleAbortRef.current?.signal.aborted) return;
|
|
60
79
|
const data = useAppStore.getState().data;
|
|
61
80
|
if (data) {
|
|
62
81
|
const itemIndex = data.findIndex((item) => item.local_id === localId);
|
|
@@ -114,6 +133,7 @@ export function MessageInput({
|
|
|
114
133
|
async (
|
|
115
134
|
asset: MediaAsset,
|
|
116
135
|
isFake: boolean,
|
|
136
|
+
signal: AbortSignal,
|
|
117
137
|
onProgress?: (percentage: number) => void
|
|
118
138
|
): Promise<string> => {
|
|
119
139
|
const presignResponse = await getUploadUrl(
|
|
@@ -124,9 +144,14 @@ export function MessageInput({
|
|
|
124
144
|
const uploadUrl = presignResponse.data.url;
|
|
125
145
|
const filePath = presignResponse.data.path;
|
|
126
146
|
|
|
127
|
-
await uploadFileToS3(
|
|
128
|
-
|
|
129
|
-
|
|
147
|
+
await uploadFileToS3(
|
|
148
|
+
uploadUrl,
|
|
149
|
+
asset.uri,
|
|
150
|
+
asset.mimeType,
|
|
151
|
+
onProgress,
|
|
152
|
+
{ fake: isFake },
|
|
153
|
+
signal
|
|
154
|
+
);
|
|
130
155
|
|
|
131
156
|
return isFake ? asset.uri : filePath;
|
|
132
157
|
},
|
|
@@ -166,6 +191,10 @@ export function MessageInput({
|
|
|
166
191
|
]);
|
|
167
192
|
scrollToBottom(false);
|
|
168
193
|
|
|
194
|
+
uploadAbortRef.current?.abort();
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
uploadAbortRef.current = controller;
|
|
197
|
+
|
|
169
198
|
try {
|
|
170
199
|
const fileProgresses = new Array(assets.length).fill(0);
|
|
171
200
|
let lastProgressUpdate = 0;
|
|
@@ -198,6 +227,7 @@ export function MessageInput({
|
|
|
198
227
|
const path = await uploadSingleMedia(
|
|
199
228
|
assets[i]!,
|
|
200
229
|
isFake,
|
|
230
|
+
controller.signal,
|
|
201
231
|
(percentage) => {
|
|
202
232
|
fileProgresses[i] = percentage;
|
|
203
233
|
updateOverallProgress();
|
|
@@ -211,7 +241,8 @@ export function MessageInput({
|
|
|
211
241
|
uploadedPaths,
|
|
212
242
|
assets[0]!.type,
|
|
213
243
|
content,
|
|
214
|
-
{ fake: isFake }
|
|
244
|
+
{ fake: isFake },
|
|
245
|
+
controller.signal
|
|
215
246
|
);
|
|
216
247
|
|
|
217
248
|
const currentData = useAppStore.getState().data;
|
|
@@ -265,6 +296,10 @@ export function MessageInput({
|
|
|
265
296
|
}
|
|
266
297
|
}
|
|
267
298
|
console.error('[Comnyx] Media upload failed:', error);
|
|
299
|
+
} finally {
|
|
300
|
+
if (uploadAbortRef.current === controller) {
|
|
301
|
+
uploadAbortRef.current = null;
|
|
302
|
+
}
|
|
268
303
|
}
|
|
269
304
|
},
|
|
270
305
|
[customer, scrollToBottom, uploadSingleMedia]
|
|
@@ -294,18 +329,45 @@ export function MessageInput({
|
|
|
294
329
|
const handleSend = useCallback(async () => {
|
|
295
330
|
if (isSending) return;
|
|
296
331
|
|
|
297
|
-
|
|
298
|
-
|
|
332
|
+
const hasMedia = pendingMedia.length > 0;
|
|
333
|
+
const hasText = !!value.trim();
|
|
334
|
+
if (!hasMedia && !hasText) return;
|
|
335
|
+
|
|
336
|
+
if (onBeforeSend) {
|
|
337
|
+
try {
|
|
338
|
+
setIsSending(true);
|
|
339
|
+
await onBeforeSend({
|
|
340
|
+
content: value.trim(),
|
|
341
|
+
mediaCount: pendingMedia.length,
|
|
342
|
+
mediaTypes: pendingMedia.map((m) => m.type),
|
|
343
|
+
});
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.warn('[Comnyx] onBeforeSend cancelled the message:', err);
|
|
346
|
+
setIsSending(false);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (hasMedia) {
|
|
352
|
+
if (!onBeforeSend) setIsSending(true);
|
|
299
353
|
const content = value.trim();
|
|
300
354
|
const assets = [...pendingMedia];
|
|
301
355
|
setPendingMedia([]);
|
|
302
356
|
setValue('');
|
|
303
357
|
await sendMediaMessages(assets, content);
|
|
304
358
|
setIsSending(false);
|
|
305
|
-
} else if (
|
|
359
|
+
} else if (hasText) {
|
|
306
360
|
sendTextOnlyMessage();
|
|
361
|
+
if (onBeforeSend) setIsSending(false);
|
|
307
362
|
}
|
|
308
|
-
}, [
|
|
363
|
+
}, [
|
|
364
|
+
isSending,
|
|
365
|
+
pendingMedia,
|
|
366
|
+
value,
|
|
367
|
+
sendMediaMessages,
|
|
368
|
+
sendTextOnlyMessage,
|
|
369
|
+
onBeforeSend,
|
|
370
|
+
]);
|
|
309
371
|
|
|
310
372
|
const hasPendingMedia = pendingMedia.length > 0;
|
|
311
373
|
|
|
@@ -335,8 +397,18 @@ export function MessageInput({
|
|
|
335
397
|
/>
|
|
336
398
|
{asset.type === 'video' && (
|
|
337
399
|
<View style={styles.playIconOverlay}>
|
|
338
|
-
<View
|
|
339
|
-
|
|
400
|
+
<View
|
|
401
|
+
style={[
|
|
402
|
+
styles.playIcon,
|
|
403
|
+
{ borderColor: themeColors.playIcon },
|
|
404
|
+
]}
|
|
405
|
+
>
|
|
406
|
+
<View
|
|
407
|
+
style={[
|
|
408
|
+
styles.playTriangle,
|
|
409
|
+
{ borderLeftColor: themeColors.playIcon },
|
|
410
|
+
]}
|
|
411
|
+
/>
|
|
340
412
|
</View>
|
|
341
413
|
</View>
|
|
342
414
|
)}
|
|
@@ -375,6 +447,9 @@ export function MessageInput({
|
|
|
375
447
|
onPress={handleSend}
|
|
376
448
|
activeOpacity={activeOpacity}
|
|
377
449
|
disabled={isSending}
|
|
450
|
+
accessibilityRole="button"
|
|
451
|
+
accessibilityLabel={localize('chat.messageInput.placeholder')}
|
|
452
|
+
accessibilityState={{ disabled: isSending, busy: isSending }}
|
|
378
453
|
>
|
|
379
454
|
<Image
|
|
380
455
|
style={[
|
|
@@ -441,7 +516,6 @@ const styles = ScaledSheet.create({
|
|
|
441
516
|
height: '22@vs',
|
|
442
517
|
borderRadius: '11@vs',
|
|
443
518
|
borderWidth: 1.5,
|
|
444
|
-
borderColor: '#fff',
|
|
445
519
|
justifyContent: 'center',
|
|
446
520
|
alignItems: 'center',
|
|
447
521
|
},
|
|
@@ -451,7 +525,6 @@ const styles = ScaledSheet.create({
|
|
|
451
525
|
borderLeftWidth: '7@vs',
|
|
452
526
|
borderTopWidth: '5@vs',
|
|
453
527
|
borderBottomWidth: '5@vs',
|
|
454
|
-
borderLeftColor: '#fff',
|
|
455
528
|
borderTopColor: 'transparent',
|
|
456
529
|
borderBottomColor: 'transparent',
|
|
457
530
|
marginLeft: '2@s',
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
View,
|
|
3
|
+
Image,
|
|
4
|
+
TouchableOpacity,
|
|
5
|
+
Linking,
|
|
6
|
+
type NativeSyntheticEvent,
|
|
7
|
+
type TextLayoutEventData,
|
|
8
|
+
} from 'react-native';
|
|
2
9
|
import { AppText } from './AppText';
|
|
3
10
|
import type { AppConversationMessage } from '../types/Conversation';
|
|
4
11
|
import { useThemeColors } from '../hooks/useThemeColors';
|
|
@@ -11,14 +18,6 @@ import { MediaMessageItem } from './MediaMessageItem';
|
|
|
11
18
|
const clockIcon = require('../assets/iconamoon_clock-fill.png');
|
|
12
19
|
const infoIcon = require('../assets/info-circle.png');
|
|
13
20
|
|
|
14
|
-
interface TextLine {
|
|
15
|
-
width: number;
|
|
16
|
-
height: number;
|
|
17
|
-
x: number;
|
|
18
|
-
y: number;
|
|
19
|
-
text: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
21
|
interface MessageLayout {
|
|
23
22
|
lines: number;
|
|
24
23
|
lastLineWidth: number;
|
|
@@ -90,7 +89,7 @@ function TextMessageItem({
|
|
|
90
89
|
}
|
|
91
90
|
};
|
|
92
91
|
|
|
93
|
-
const onTextLayout = (event:
|
|
92
|
+
const onTextLayout = (event: NativeSyntheticEvent<TextLayoutEventData>) => {
|
|
94
93
|
const { lines } = event.nativeEvent;
|
|
95
94
|
|
|
96
95
|
const numLines = lines.length;
|
|
@@ -99,9 +98,9 @@ function TextMessageItem({
|
|
|
99
98
|
|
|
100
99
|
if (numLines >= 1) {
|
|
101
100
|
// Get the width of the last line
|
|
102
|
-
lastLineWidth = lines[numLines - 1]
|
|
101
|
+
lastLineWidth = lines[numLines - 1]!.width;
|
|
103
102
|
// Use the widest line as the total width
|
|
104
|
-
totalWidth = Math.max(...lines.map((line
|
|
103
|
+
totalWidth = Math.max(...lines.map((line) => line.width));
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
layoutRef.current = {
|
|
@@ -183,7 +182,7 @@ function TextMessageItem({
|
|
|
183
182
|
{
|
|
184
183
|
regex:
|
|
185
184
|
/https?:\/\/(?:[-\w.])+(?::[0-9]+)?(?:\/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:[\w.])*)?)?/g,
|
|
186
|
-
color:
|
|
185
|
+
color: themeColors.link,
|
|
187
186
|
navigate: (url) => {
|
|
188
187
|
if (url) {
|
|
189
188
|
Linking.openURL(url);
|