@capillarytech/creatives-library 8.0.328 → 8.0.330-alpha.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/constants/unified.js +4 -0
- package/package.json +1 -1
- package/services/api.js +17 -0
- package/services/tests/api.test.js +85 -0
- package/utils/commonUtils.js +28 -0
- package/utils/tagValidations.js +2 -3
- package/utils/templateVarUtils.js +35 -6
- package/utils/tests/commonUtil.test.js +169 -0
- package/utils/tests/tagValidations.test.js +1 -1
- package/utils/tests/templateVarUtils.test.js +44 -0
- package/v2Components/CommonTestAndPreview/AddTestCustomer.js +42 -0
- package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +155 -0
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +93 -0
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +79 -51
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +134 -34
- package/v2Components/CommonTestAndPreview/actions.js +10 -0
- package/v2Components/CommonTestAndPreview/constants.js +15 -1
- package/v2Components/CommonTestAndPreview/index.js +364 -72
- package/v2Components/CommonTestAndPreview/messages.js +106 -0
- package/v2Components/CommonTestAndPreview/reducer.js +10 -0
- package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +66 -0
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +648 -0
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +24 -0
- package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +174 -0
- package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +114 -0
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +52 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +31 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +36 -0
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +71 -0
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +17 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +14 -3
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +16 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +5 -0
- package/v2Containers/CreativesContainer/index.js +15 -10
- package/v2Containers/Rcs/constants.js +6 -2
- package/v2Containers/Rcs/index.js +219 -91
- package/v2Containers/Rcs/messages.js +2 -1
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +20 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2370 -1758
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +67 -0
- package/v2Containers/Rcs/tests/utils.test.js +56 -0
- package/v2Containers/Rcs/utils.js +53 -6
- package/v2Containers/SmsTrai/Edit/index.js +27 -0
- package/v2Containers/SmsTrai/Edit/messages.js +5 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +357 -324
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +5586 -5212
|
@@ -17,6 +17,9 @@ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
|
17
17
|
import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
|
|
18
18
|
import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
|
|
19
19
|
import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
20
|
+
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
21
|
+
import CustomerCreationModal from './CustomerCreationModal';
|
|
22
|
+
import { createTestCustomer } from '../../services/api';
|
|
20
23
|
|
|
21
24
|
// Import messages and styles
|
|
22
25
|
import messages from './messages';
|
|
@@ -30,8 +33,16 @@ import PreviewSection from './PreviewSection';
|
|
|
30
33
|
|
|
31
34
|
import * as Api from '../../services/api';
|
|
32
35
|
import { extractTemplateVariables } from '../../utils/templateVarUtils';
|
|
36
|
+
import AddTestCustomerButton from './AddTestCustomer';
|
|
37
|
+
import ExistingCustomerModal from './ExistingCustomerModal';
|
|
38
|
+
// Import constants
|
|
33
39
|
import {
|
|
34
40
|
CHANNELS,
|
|
41
|
+
CUSTOMER_MODAL_NEW,
|
|
42
|
+
CUSTOMER_MODAL_EXISTING,
|
|
43
|
+
IDENTIFIER_TYPE_EMAIL,
|
|
44
|
+
IDENTIFIER_TYPE_MOBILE,
|
|
45
|
+
IDENTIFIER_TYPE_PHONE,
|
|
35
46
|
TEST,
|
|
36
47
|
DESKTOP,
|
|
37
48
|
ANDROID,
|
|
@@ -86,6 +97,10 @@ import {
|
|
|
86
97
|
extractPreviewFromLiquidResponse,
|
|
87
98
|
getSmsFallbackTextForTagExtraction,
|
|
88
99
|
} from './previewApiUtils';
|
|
100
|
+
import { pickFirstSmsFallbackTemplateString } from '../../v2Containers/Rcs/rcsLibraryHydrationUtils';
|
|
101
|
+
|
|
102
|
+
import { isValidEmail, isValidMobile, formatPhoneNumber } from '../../utils/commonUtils';
|
|
103
|
+
import { getMembersLookup } from '../../services/api';
|
|
89
104
|
|
|
90
105
|
/**
|
|
91
106
|
* Drop empty GSM rows. RCS/DLT responses often set gsm_sender_id equal to domainName — keep those rows
|
|
@@ -186,6 +201,20 @@ const mapRcsSuggestionForTestMeta = (suggestionRow, index) => ({
|
|
|
186
201
|
: '',
|
|
187
202
|
});
|
|
188
203
|
|
|
204
|
+
/**
|
|
205
|
+
* CapTreeSelect and group resolution use strict equality; API data may mix numeric and string ids.
|
|
206
|
+
*/
|
|
207
|
+
const normalizeTestEntityId = (id) => {
|
|
208
|
+
if (id === undefined || id === null) return id;
|
|
209
|
+
return String(id);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const testEntityIdsEqual = (a, b) => {
|
|
213
|
+
if (a == null && b == null) return true;
|
|
214
|
+
if (a == null || b == null) return false;
|
|
215
|
+
return String(a) === String(b);
|
|
216
|
+
};
|
|
217
|
+
|
|
189
218
|
/**
|
|
190
219
|
* Preview Component Factory - REMOVED IN PHASE 5
|
|
191
220
|
* Now using UnifiedPreview component for all channels
|
|
@@ -252,6 +281,11 @@ const CommonTestAndPreview = (props) => {
|
|
|
252
281
|
const [tagsExtracted, setTagsExtracted] = useState(false);
|
|
253
282
|
|
|
254
283
|
const initialDevice = CHANNELS_USING_ANDROID_PREVIEW_DEVICE.includes(channel) ? ANDROID : DESKTOP;
|
|
284
|
+
const [searchValue, setSearchValue] = useState("");
|
|
285
|
+
const [customerModal, setCustomerModal] = useState([false, ""]);
|
|
286
|
+
const [isCustomerDataLoading, setIsCustomerDataLoading] = useState(false);
|
|
287
|
+
const [customerData, setCustomerData] = useState({ name: '', email: '', mobile: '', customerId: '' });
|
|
288
|
+
|
|
255
289
|
const [previewDevice, setPreviewDevice] = useState(initialDevice);
|
|
256
290
|
const [activePreviewTab, setActivePreviewTab] = useState(PREVIEW_TAB_RCS);
|
|
257
291
|
const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
|
|
@@ -634,19 +668,26 @@ const CommonTestAndPreview = (props) => {
|
|
|
634
668
|
}, [channel, smsFallbackContent, smsFallbackPreviewText]);
|
|
635
669
|
|
|
636
670
|
// Build test entities tree data from testCustomers prop
|
|
671
|
+
// Build test entities tree data from testCustomers prop (includes customers added via addTestCustomer action)
|
|
637
672
|
const testEntitiesTreeData = useMemo(() => {
|
|
638
673
|
const groupsNode = {
|
|
639
674
|
title: 'Groups',
|
|
640
675
|
value: 'groups-node',
|
|
641
676
|
selectable: false,
|
|
642
|
-
children: testGroups?.map((group) => ({
|
|
677
|
+
children: testGroups?.map((group) => ({
|
|
678
|
+
title: group?.groupName,
|
|
679
|
+
value: normalizeTestEntityId(group?.groupId),
|
|
680
|
+
})),
|
|
643
681
|
};
|
|
644
682
|
|
|
645
683
|
const customersNode = {
|
|
646
684
|
title: 'Individuals',
|
|
647
685
|
value: 'customers-node',
|
|
648
686
|
selectable: false,
|
|
649
|
-
children: testCustomers?.map((customer) => ({
|
|
687
|
+
children: testCustomers?.map((customer) => ({
|
|
688
|
+
title: customer?.name?.trim() || customer?.email?.trim() || customer?.mobile?.trim() || customer?.userId || customer?.customerId,
|
|
689
|
+
value: normalizeTestEntityId(customer?.userId ?? customer?.customerId),
|
|
690
|
+
})) || [],
|
|
650
691
|
};
|
|
651
692
|
|
|
652
693
|
return [groupsNode, customersNode];
|
|
@@ -673,6 +714,93 @@ const CommonTestAndPreview = (props) => {
|
|
|
673
714
|
return resolvedText;
|
|
674
715
|
};
|
|
675
716
|
|
|
717
|
+
/**
|
|
718
|
+
* Common handler for saving test customers (both new and existing)
|
|
719
|
+
*/
|
|
720
|
+
const handleSaveTestCustomer = async (validationErrors = {}, setIsLoading = () => {}) => {
|
|
721
|
+
// Check for validation errors before saving (for new customers)
|
|
722
|
+
if (customerModal[1] === CUSTOMER_MODAL_NEW && (validationErrors.email || validationErrors.mobile)) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
setIsLoading(true);
|
|
727
|
+
|
|
728
|
+
try {
|
|
729
|
+
let payload;
|
|
730
|
+
|
|
731
|
+
if (customerModal[1] === CUSTOMER_MODAL_EXISTING) {
|
|
732
|
+
// For existing customers, use customerId
|
|
733
|
+
payload = {
|
|
734
|
+
campaignUserId: customerData.customerId
|
|
735
|
+
};
|
|
736
|
+
} else {
|
|
737
|
+
// For new customers, use customer object
|
|
738
|
+
payload = {
|
|
739
|
+
customer: {
|
|
740
|
+
firstName: customerData.name || "",
|
|
741
|
+
mobile: customerData.mobile || "",
|
|
742
|
+
email: customerData.email || ""
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const response = await createTestCustomer(payload);
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
// Handle success: add to test customers list and selection (existing and new)
|
|
751
|
+
if (response && response.success) {
|
|
752
|
+
CapNotification.success({
|
|
753
|
+
message: formatMessage(messages.newTestCustomerAddedSuccess),
|
|
754
|
+
});
|
|
755
|
+
// API may return customerId in response.response (e.g. { response: { customerId: 438845651 } })
|
|
756
|
+
const res = response?.response || response;
|
|
757
|
+
const addedId = customerModal[1] === CUSTOMER_MODAL_EXISTING
|
|
758
|
+
? customerData?.customerId || customerData?.campaignUserId
|
|
759
|
+
: res?.customerId || res?.campaignUserId;
|
|
760
|
+
if (addedId) {
|
|
761
|
+
const normalizedAddedId = normalizeTestEntityId(addedId);
|
|
762
|
+
actions.addTestCustomer({
|
|
763
|
+
userId: normalizedAddedId,
|
|
764
|
+
customerId: normalizedAddedId,
|
|
765
|
+
name: customerData?.name?.trim() || '',
|
|
766
|
+
email: customerData?.email || '',
|
|
767
|
+
mobile: customerData?.mobile || '',
|
|
768
|
+
});
|
|
769
|
+
setSelectedTestEntities((prev) => (
|
|
770
|
+
prev.some((id) => testEntityIdsEqual(id, normalizedAddedId))
|
|
771
|
+
? prev
|
|
772
|
+
: [...prev, normalizedAddedId]
|
|
773
|
+
));
|
|
774
|
+
}
|
|
775
|
+
handleCloseCustomerModal();
|
|
776
|
+
} else {
|
|
777
|
+
// Show error notification for unsuccessful response
|
|
778
|
+
CapNotification.error({
|
|
779
|
+
message: formatMessage(messages.errorTitle),
|
|
780
|
+
description: response?.message || formatMessage(messages.failedToAddTestCustomer),
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
} catch (error) {
|
|
784
|
+
CapNotification.error({
|
|
785
|
+
message: formatMessage(messages.errorTitle),
|
|
786
|
+
description: error?.message || formatMessage(messages.errorAddingTestCustomer),
|
|
787
|
+
});
|
|
788
|
+
} finally {
|
|
789
|
+
setIsLoading(false);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
const handleCloseCustomerModal = () => {
|
|
794
|
+
setCustomerModal([false, ""]);
|
|
795
|
+
setSearchValue('');
|
|
796
|
+
setCustomerData({
|
|
797
|
+
name: '',
|
|
798
|
+
email: '',
|
|
799
|
+
mobile: '',
|
|
800
|
+
customerId: '',
|
|
801
|
+
});
|
|
802
|
+
};
|
|
803
|
+
|
|
676
804
|
/**
|
|
677
805
|
* Prepare payload for preview API based on channel
|
|
678
806
|
*/
|
|
@@ -912,78 +1040,77 @@ const CommonTestAndPreview = (props) => {
|
|
|
912
1040
|
* rcsMessageContent: { channel, accountId?, rcsRichCardContent: { contentType, cardType, cardSettings, cardContent }, smsFallBackContent? }
|
|
913
1041
|
* Then rcsDeliverySettings, executionParams, clientName last.
|
|
914
1042
|
*/
|
|
915
|
-
const buildRcsTestMessagePayload = (
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1043
|
+
const buildRcsTestMessagePayload = (
|
|
1044
|
+
creativeFormData,
|
|
1045
|
+
_unusedEditorContentString,
|
|
1046
|
+
_customValuesObj,
|
|
1047
|
+
deliverySettingsOverride,
|
|
1048
|
+
basePayload,
|
|
1049
|
+
_rcsTestMetaExtras = {},
|
|
1050
|
+
) => {
|
|
1051
|
+
const rcsSectionFromForm =
|
|
1052
|
+
creativeFormData?.versions?.base?.content?.RCS ?? creativeFormData?.content?.RCS ?? {};
|
|
1053
|
+
const rcsContentFromForm = rcsSectionFromForm?.rcsContent || {};
|
|
1054
|
+
const smsFallbackFromCreativeForm = rcsSectionFromForm?.smsFallBackContent || {};
|
|
1055
|
+
let rcsCardPayloadList = [];
|
|
1056
|
+
if (Array.isArray(rcsContentFromForm?.cardContent)) {
|
|
1057
|
+
rcsCardPayloadList = rcsContentFromForm.cardContent;
|
|
1058
|
+
} else if (rcsContentFromForm?.cardContent) {
|
|
1059
|
+
rcsCardPayloadList = [rcsContentFromForm.cardContent];
|
|
931
1060
|
}
|
|
932
|
-
//
|
|
933
|
-
const
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1061
|
+
// Raw title/description with template tags; SMS fallback uses tagged template fields (pickFirst…).
|
|
1062
|
+
const cardContentForTestMetaApi = rcsCardPayloadList.map((singleRcsCardPayload) => {
|
|
1063
|
+
const normalizedCardMediaForTestApi = singleRcsCardPayload?.media
|
|
1064
|
+
? normalizeRcsTestCardMedia(singleRcsCardPayload.media)
|
|
1065
|
+
: undefined;
|
|
1066
|
+
const suggestionsFromCard = Array.isArray(singleRcsCardPayload?.suggestions)
|
|
1067
|
+
? singleRcsCardPayload.suggestions
|
|
1068
|
+
: [];
|
|
1069
|
+
const suggestionsFormattedForTestMeta = suggestionsFromCard.map((suggestionItem, index) =>
|
|
1070
|
+
mapRcsSuggestionForTestMeta(suggestionItem, index));
|
|
941
1071
|
return {
|
|
942
|
-
title:
|
|
943
|
-
description:
|
|
944
|
-
mediaType:
|
|
945
|
-
...(
|
|
946
|
-
...(
|
|
947
|
-
|
|
1072
|
+
title: singleRcsCardPayload?.title ?? '',
|
|
1073
|
+
description: singleRcsCardPayload?.description ?? '',
|
|
1074
|
+
mediaType: singleRcsCardPayload?.mediaType ?? MEDIA_TYPE_TEXT,
|
|
1075
|
+
...(normalizedCardMediaForTestApi && { media: normalizedCardMediaForTestApi }),
|
|
1076
|
+
...(suggestionsFormattedForTestMeta.length > 0 && {
|
|
1077
|
+
suggestions: suggestionsFormattedForTestMeta,
|
|
1078
|
+
}),
|
|
948
1079
|
};
|
|
949
1080
|
});
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
const hasResolvedFallbackBodyFromRcsExtra =
|
|
954
|
-
rcsExtraFallbackTemplate != null
|
|
955
|
-
&& String(rcsExtraFallbackTemplate).trim() !== '';
|
|
956
|
-
const smsMessageRaw = hasResolvedFallbackBodyFromRcsExtra
|
|
957
|
-
? String(rcsExtraFallbackTemplate)
|
|
958
|
-
: (smsFallback?.smsContent ?? smsFallback?.message ?? '');
|
|
1081
|
+
const smsFallbackTaggedTemplateBody = pickFirstSmsFallbackTemplateString(
|
|
1082
|
+
smsFallbackFromCreativeForm,
|
|
1083
|
+
) || '';
|
|
959
1084
|
const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
|
|
960
1085
|
? deliverySettingsOverride.cdmaSenderId.split('|')[1]
|
|
961
1086
|
: deliverySettingsOverride?.cdmaSenderId;
|
|
962
1087
|
const deliveryFallbackSmsId =
|
|
963
1088
|
typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
|
|
964
1089
|
const creativeFallbackSmsId =
|
|
965
|
-
|
|
1090
|
+
smsFallbackFromCreativeForm?.senderId != null
|
|
1091
|
+
? String(smsFallbackFromCreativeForm.senderId).trim()
|
|
1092
|
+
: '';
|
|
966
1093
|
const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
|
|
967
1094
|
|
|
968
1095
|
const smsFallBackContent =
|
|
969
|
-
|
|
970
|
-
? { message:
|
|
1096
|
+
smsFallbackTaggedTemplateBody.trim() !== ''
|
|
1097
|
+
? { message: smsFallbackTaggedTemplateBody }
|
|
971
1098
|
: undefined;
|
|
972
1099
|
|
|
973
1100
|
// accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
|
|
974
1101
|
const accountIdForMeta =
|
|
975
|
-
|
|
976
|
-
? String(
|
|
1102
|
+
rcsContentFromForm?.accountId != null && String(rcsContentFromForm.accountId).trim() !== ''
|
|
1103
|
+
? String(rcsContentFromForm.accountId)
|
|
977
1104
|
: undefined;
|
|
978
1105
|
|
|
979
1106
|
const rcsRichCardContent = {
|
|
980
1107
|
contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
981
|
-
cardType:
|
|
982
|
-
cardSettings:
|
|
1108
|
+
cardType: rcsContentFromForm?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
1109
|
+
cardSettings: rcsContentFromForm?.cardSettings ?? {
|
|
983
1110
|
cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
984
1111
|
cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
985
1112
|
},
|
|
986
|
-
...(
|
|
1113
|
+
...(cardContentForTestMetaApi.length > 0 && { cardContent: cardContentForTestMetaApi }),
|
|
987
1114
|
};
|
|
988
1115
|
|
|
989
1116
|
const rcsMessageContent = {
|
|
@@ -2763,7 +2890,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
2763
2890
|
* Handle slidebox close
|
|
2764
2891
|
*/
|
|
2765
2892
|
const handleClose = () => {
|
|
2766
|
-
// Reset state when closing
|
|
2893
|
+
// Reset state when closing (includes add-customer modal + tree search state)
|
|
2894
|
+
handleCloseCustomerModal();
|
|
2767
2895
|
setSelectedCustomer(null);
|
|
2768
2896
|
setRequiredTags([]);
|
|
2769
2897
|
setOptionalTags([]);
|
|
@@ -3023,16 +3151,132 @@ const CommonTestAndPreview = (props) => {
|
|
|
3023
3151
|
* Handle test entities change
|
|
3024
3152
|
*/
|
|
3025
3153
|
const handleTestEntitiesChange = (value) => {
|
|
3026
|
-
|
|
3154
|
+
if (!Array.isArray(value)) {
|
|
3155
|
+
if (value == null || value === '') {
|
|
3156
|
+
setSelectedTestEntities([]);
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
setSelectedTestEntities([normalizeTestEntityId(value)]);
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
setSelectedTestEntities(value.map((v) => normalizeTestEntityId(v)));
|
|
3027
3163
|
};
|
|
3028
3164
|
|
|
3165
|
+
/**
|
|
3166
|
+
* Map API customerDetails item to our customerData shape
|
|
3167
|
+
*/
|
|
3168
|
+
const mapCustomerDetailsToCustomerData = (detail, identifierValue) => {
|
|
3169
|
+
const firstName = detail.firstName || '';
|
|
3170
|
+
const lastName = detail.lastName || '';
|
|
3171
|
+
const name = [firstName, lastName].filter(Boolean).join(' ').trim() || '';
|
|
3172
|
+
const getIdentifierValue = (type) => {
|
|
3173
|
+
const fromIdentifiers = detail.identifiers?.find((i) => i.type === type)?.value;
|
|
3174
|
+
if (fromIdentifiers) return fromIdentifiers;
|
|
3175
|
+
const fromCommChannels = detail.commChannels?.find((c) => c.type === type)?.value;
|
|
3176
|
+
return fromCommChannels || (channel === CHANNELS.EMAIL && type === IDENTIFIER_TYPE_EMAIL ? identifierValue : channel === CHANNELS.SMS && type === IDENTIFIER_TYPE_MOBILE ? identifierValue : '');
|
|
3177
|
+
};
|
|
3178
|
+
return {
|
|
3179
|
+
name,
|
|
3180
|
+
email: channel === CHANNELS.EMAIL ? (getIdentifierValue(IDENTIFIER_TYPE_EMAIL) || identifierValue) : (getIdentifierValue(IDENTIFIER_TYPE_EMAIL) || ''),
|
|
3181
|
+
mobile: channel === CHANNELS.SMS ? (getIdentifierValue(IDENTIFIER_TYPE_MOBILE) || getIdentifierValue(IDENTIFIER_TYPE_PHONE) || identifierValue) : (getIdentifierValue(IDENTIFIER_TYPE_MOBILE) || getIdentifierValue(IDENTIFIER_TYPE_PHONE) || ''),
|
|
3182
|
+
customerId: detail.userId != null ? String(detail.userId) : '',
|
|
3183
|
+
};
|
|
3184
|
+
};
|
|
3185
|
+
|
|
3186
|
+
const handleAddTestCustomer = async () => {
|
|
3187
|
+
const identifierType = channel === CHANNELS.EMAIL ? IDENTIFIER_TYPE_EMAIL : IDENTIFIER_TYPE_MOBILE;
|
|
3188
|
+
const searchValueToCheck = channel === CHANNELS.SMS
|
|
3189
|
+
? formatPhoneNumber((searchValue || '').trim())
|
|
3190
|
+
: (searchValue || '').trim();
|
|
3191
|
+
|
|
3192
|
+
// Check if this customer is already in the test customers list
|
|
3193
|
+
const existingTestCustomer = testCustomers?.find(customer => {
|
|
3194
|
+
if (channel === CHANNELS.EMAIL) {
|
|
3195
|
+
return customer.email === searchValueToCheck;
|
|
3196
|
+
} else if (channel === CHANNELS.SMS) {
|
|
3197
|
+
return customer.mobile === searchValueToCheck;
|
|
3198
|
+
}
|
|
3199
|
+
return false;
|
|
3200
|
+
});
|
|
3201
|
+
|
|
3202
|
+
if (existingTestCustomer) {
|
|
3203
|
+
const entityId = existingTestCustomer.userId ?? existingTestCustomer.customerId;
|
|
3204
|
+
if (entityId != null) {
|
|
3205
|
+
const id = normalizeTestEntityId(entityId);
|
|
3206
|
+
setSelectedTestEntities((prev) => (
|
|
3207
|
+
prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
|
|
3208
|
+
));
|
|
3209
|
+
}
|
|
3210
|
+
setSearchValue('');
|
|
3211
|
+
CapNotification.success({
|
|
3212
|
+
message: formatMessage(messages.customerAlreadyInTestList),
|
|
3213
|
+
});
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
setIsCustomerDataLoading(true);
|
|
3218
|
+
|
|
3219
|
+
try {
|
|
3220
|
+
const response = await getMembersLookup(identifierType, searchValueToCheck);
|
|
3221
|
+
const success = response?.success && !response?.status?.isError;
|
|
3222
|
+
const res = response?.response || {};
|
|
3223
|
+
const exists = res.exists || false;
|
|
3224
|
+
const details = res.customerDetails || [];
|
|
3225
|
+
|
|
3226
|
+
if (!success) {
|
|
3227
|
+
const errorMessage = response?.message || response?.status?.message || formatMessage(messages.memberLookupError);
|
|
3228
|
+
CapNotification.error({
|
|
3229
|
+
message: formatMessage(messages.memberLookupError),
|
|
3230
|
+
description: errorMessage,
|
|
3231
|
+
});
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
if (exists && details.length > 0) {
|
|
3236
|
+
const mapped = mapCustomerDetailsToCustomerData(details[0], searchValueToCheck);
|
|
3237
|
+
const customerIdFromLookup = mapped.customerId;
|
|
3238
|
+
const alreadyInTestListByCustomerId = customerIdFromLookup && testCustomers?.some(
|
|
3239
|
+
(c) => String(c?.customerId) === customerIdFromLookup || String(c?.userId) === customerIdFromLookup
|
|
3240
|
+
);
|
|
3241
|
+
if (alreadyInTestListByCustomerId) {
|
|
3242
|
+
const id = normalizeTestEntityId(customerIdFromLookup);
|
|
3243
|
+
setSelectedTestEntities((prev) => (
|
|
3244
|
+
prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
|
|
3245
|
+
));
|
|
3246
|
+
setSearchValue('');
|
|
3247
|
+
CapNotification.success({
|
|
3248
|
+
message: formatMessage(messages.customerAlreadyInTestList),
|
|
3249
|
+
});
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
setCustomerData(mapped);
|
|
3253
|
+
setCustomerModal([true, CUSTOMER_MODAL_EXISTING]);
|
|
3254
|
+
} else {
|
|
3255
|
+
setCustomerData({
|
|
3256
|
+
name: '',
|
|
3257
|
+
email: channel === CHANNELS.EMAIL ? searchValueToCheck : '',
|
|
3258
|
+
mobile: channel === CHANNELS.SMS ? searchValueToCheck : '',
|
|
3259
|
+
customerId: '',
|
|
3260
|
+
});
|
|
3261
|
+
setCustomerModal([true, CUSTOMER_MODAL_NEW]);
|
|
3262
|
+
}
|
|
3263
|
+
} catch {
|
|
3264
|
+
CapNotification.error({
|
|
3265
|
+
message: formatMessage(messages.memberLookupError),
|
|
3266
|
+
description: formatMessage(messages.memberLookupError),
|
|
3267
|
+
});
|
|
3268
|
+
} finally {
|
|
3269
|
+
setIsCustomerDataLoading(false);
|
|
3270
|
+
}
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3029
3273
|
/**
|
|
3030
3274
|
* Handle send test message
|
|
3031
3275
|
*/
|
|
3032
3276
|
const handleSendTestMessage = () => {
|
|
3033
3277
|
const allUserIds = [];
|
|
3034
3278
|
selectedTestEntities.forEach((entityId) => {
|
|
3035
|
-
const group = testGroups.find((
|
|
3279
|
+
const group = testGroups.find((g) => testEntityIdsEqual(g.groupId, entityId));
|
|
3036
3280
|
if (group) {
|
|
3037
3281
|
allUserIds.push(...group.userIds);
|
|
3038
3282
|
} else {
|
|
@@ -3055,15 +3299,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
3055
3299
|
uniqueUserIds,
|
|
3056
3300
|
previewData,
|
|
3057
3301
|
deliveryOverride,
|
|
3058
|
-
|
|
3059
|
-
? {
|
|
3060
|
-
smsFallbackTemplateContent:
|
|
3061
|
-
smsFallbackTextForTagExtraction
|
|
3062
|
-
|| smsFallbackContent?.templateContent
|
|
3063
|
-
|| smsFallbackContent?.content
|
|
3064
|
-
|| '',
|
|
3065
|
-
}
|
|
3066
|
-
: {},
|
|
3302
|
+
{},
|
|
3067
3303
|
);
|
|
3068
3304
|
|
|
3069
3305
|
actions.createMessageMetaRequested(
|
|
@@ -3155,6 +3391,20 @@ const CommonTestAndPreview = (props) => {
|
|
|
3155
3391
|
}));
|
|
3156
3392
|
};
|
|
3157
3393
|
|
|
3394
|
+
/** Trim pasted emails (trailing CR/LF). SMS: strip non-digits so pasted formatted numbers match isValidMobile / API. */
|
|
3395
|
+
const handleTestCustomersSearch = useCallback((value) => {
|
|
3396
|
+
if (value == null || value === '') {
|
|
3397
|
+
setSearchValue('');
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3400
|
+
const raw = String(value).trim();
|
|
3401
|
+
if (channel === CHANNELS.SMS) {
|
|
3402
|
+
setSearchValue(formatPhoneNumber(raw));
|
|
3403
|
+
} else {
|
|
3404
|
+
setSearchValue(raw);
|
|
3405
|
+
}
|
|
3406
|
+
}, [channel]);
|
|
3407
|
+
|
|
3158
3408
|
const renderSendTestMessage = () => (
|
|
3159
3409
|
<SendTestMessage
|
|
3160
3410
|
isFetchingTestCustomers={isFetchingTestCustomers}
|
|
@@ -3167,6 +3417,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
3167
3417
|
content={getCurrentContent}
|
|
3168
3418
|
channel={channel}
|
|
3169
3419
|
isSendingTestMessage={isSendingTestMessage}
|
|
3420
|
+
renderAddTestCustomerButton={renderAddTestCustomerButton}
|
|
3170
3421
|
formatMessage={formatMessage}
|
|
3171
3422
|
deliverySettings={testPreviewDeliverySettings[channel]}
|
|
3172
3423
|
senderDetailsByChannel={senderDetailsByChannel}
|
|
@@ -3176,6 +3427,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
3176
3427
|
smsTraiDltEnabled={smsTraiDltEnabled}
|
|
3177
3428
|
registeredSenderIds={registeredSenderIds}
|
|
3178
3429
|
isChannelSmsFallbackPreviewEnabled={channel === CHANNELS.RCS && !!smsFallbackContent?.templateContent}
|
|
3430
|
+
searchValue={searchValue}
|
|
3431
|
+
setSearchValue={handleTestCustomersSearch}
|
|
3179
3432
|
/>
|
|
3180
3433
|
);
|
|
3181
3434
|
|
|
@@ -3185,6 +3438,21 @@ const CommonTestAndPreview = (props) => {
|
|
|
3185
3438
|
/>
|
|
3186
3439
|
);
|
|
3187
3440
|
|
|
3441
|
+
const renderAddTestCustomerButton = () => {
|
|
3442
|
+
const raw = (searchValue || '').trim();
|
|
3443
|
+
const value = channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw;
|
|
3444
|
+
const showAddButton =
|
|
3445
|
+
[CHANNELS.EMAIL, CHANNELS.SMS].includes(channel) &&
|
|
3446
|
+
(channel === CHANNELS.EMAIL ? isValidEmail(value) : isValidMobile(value));
|
|
3447
|
+
if (!showAddButton) return null;
|
|
3448
|
+
return (
|
|
3449
|
+
<AddTestCustomerButton
|
|
3450
|
+
searchValue={value}
|
|
3451
|
+
handleAddTestCustomer={handleAddTestCustomer}
|
|
3452
|
+
/>
|
|
3453
|
+
);
|
|
3454
|
+
};
|
|
3455
|
+
|
|
3188
3456
|
// Header content for the slidebox
|
|
3189
3457
|
const slideboxHeader = (
|
|
3190
3458
|
<CapRow className="test-preview-header">
|
|
@@ -3204,14 +3472,18 @@ const CommonTestAndPreview = (props) => {
|
|
|
3204
3472
|
show={show}
|
|
3205
3473
|
size="size-xl"
|
|
3206
3474
|
content={(
|
|
3207
|
-
<
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3475
|
+
<CapSpin
|
|
3476
|
+
spinning={isCustomerDataLoading}
|
|
3477
|
+
className={`common-test-preview-lookup-spin ${isCustomerDataLoading ? 'common-test-preview-customer-loading' : ''}`}
|
|
3478
|
+
>
|
|
3479
|
+
<CapRow className="test-preview-container">
|
|
3480
|
+
<CapRow className="test-and-preview-panels">
|
|
3481
|
+
{/* Left Panel */}
|
|
3482
|
+
<CapRow className="left-panel">
|
|
3483
|
+
{channel === CHANNELS.ZALO ? null : renderLeftPanelContent()}
|
|
3484
|
+
<CapDivider className="panel-divider" />
|
|
3485
|
+
|
|
3486
|
+
{/* Send Test Message Section */}
|
|
3215
3487
|
{config.enableTestMessage !== false && (
|
|
3216
3488
|
<CapRow className="panel-section send-test-section">
|
|
3217
3489
|
{renderSendTestMessage()}
|
|
@@ -3225,7 +3497,27 @@ const CommonTestAndPreview = (props) => {
|
|
|
3225
3497
|
{renderPreview()}
|
|
3226
3498
|
</CapRow>
|
|
3227
3499
|
</CapRow>
|
|
3228
|
-
|
|
3500
|
+
{customerModal[0] && customerModal[1] === CUSTOMER_MODAL_EXISTING && (
|
|
3501
|
+
<ExistingCustomerModal
|
|
3502
|
+
customerData={customerData}
|
|
3503
|
+
onCloseCustomerModal={handleCloseCustomerModal}
|
|
3504
|
+
customerModal={customerModal}
|
|
3505
|
+
channel={channel}
|
|
3506
|
+
onSave={handleSaveTestCustomer}
|
|
3507
|
+
/>
|
|
3508
|
+
)}
|
|
3509
|
+
{customerModal[0] && customerModal[1] === CUSTOMER_MODAL_NEW && (
|
|
3510
|
+
<CustomerCreationModal
|
|
3511
|
+
customerData={customerData}
|
|
3512
|
+
setCustomerData={setCustomerData}
|
|
3513
|
+
onCloseCustomerModal={handleCloseCustomerModal}
|
|
3514
|
+
customerModal={customerModal}
|
|
3515
|
+
onSave={handleSaveTestCustomer}
|
|
3516
|
+
channel={channel}
|
|
3517
|
+
/>
|
|
3518
|
+
)}
|
|
3519
|
+
</CapRow>
|
|
3520
|
+
</CapSpin>
|
|
3229
3521
|
)}
|
|
3230
3522
|
/>
|
|
3231
3523
|
);
|
|
@@ -3327,4 +3619,4 @@ CommonTestAndPreview.defaultProps = {
|
|
|
3327
3619
|
// Note: Redux connection is handled by the wrapper components (e.g., TestAndPreviewSlidebox)
|
|
3328
3620
|
// This component receives all Redux props (including intl) from its parent
|
|
3329
3621
|
|
|
3330
|
-
export default CommonTestAndPreview;
|
|
3622
|
+
export default CommonTestAndPreview;
|