@capillarytech/creatives-library 8.0.298 → 8.0.299-alpha.4
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/package.json +1 -1
- package/services/api.js +17 -0
- package/services/tests/api.test.js +85 -0
- package/utils/commonUtils.js +10 -0
- package/utils/tests/commonUtil.test.js +169 -0
- package/v2Components/CommonTestAndPreview/AddTestCustomer.js +42 -0
- package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +284 -0
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +72 -0
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +78 -49
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +200 -4
- package/v2Components/CommonTestAndPreview/actions.js +10 -0
- package/v2Components/CommonTestAndPreview/constants.js +18 -1
- package/v2Components/CommonTestAndPreview/index.js +274 -14
- package/v2Components/CommonTestAndPreview/messages.js +94 -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 +653 -0
- package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +316 -0
- package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +114 -0
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +53 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +25 -2
- package/v2Components/CommonTestAndPreview/tests/index.test.js +7 -0
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +71 -0
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +17 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1588 -1336
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +369 -306
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +9 -3
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +5794 -5080
|
@@ -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';
|
|
@@ -27,10 +30,16 @@ import LeftPanelContent from './LeftPanelContent';
|
|
|
27
30
|
import CustomValuesEditor from './CustomValuesEditor';
|
|
28
31
|
import SendTestMessage from './SendTestMessage';
|
|
29
32
|
import PreviewSection from './PreviewSection';
|
|
30
|
-
|
|
33
|
+
import AddTestCustomerButton from './AddTestCustomer';
|
|
34
|
+
import ExistingCustomerModal from './ExistingCustomerModal';
|
|
31
35
|
// Import constants
|
|
32
36
|
import {
|
|
33
37
|
CHANNELS,
|
|
38
|
+
CUSTOMER_MODAL_NEW,
|
|
39
|
+
CUSTOMER_MODAL_EXISTING,
|
|
40
|
+
IDENTIFIER_TYPE_EMAIL,
|
|
41
|
+
IDENTIFIER_TYPE_MOBILE,
|
|
42
|
+
IDENTIFIER_TYPE_PHONE,
|
|
34
43
|
TEST,
|
|
35
44
|
DESKTOP,
|
|
36
45
|
ANDROID,
|
|
@@ -74,6 +83,8 @@ import {
|
|
|
74
83
|
|
|
75
84
|
// Import utilities
|
|
76
85
|
import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
86
|
+
import { isValidEmail, isValidMobile } from '../../utils/commonUtils';
|
|
87
|
+
import { getMembersLookup } from '../../services/api';
|
|
77
88
|
|
|
78
89
|
/**
|
|
79
90
|
* Preview Component Factory - REMOVED IN PHASE 5
|
|
@@ -130,6 +141,11 @@ const CommonTestAndPreview = (props) => {
|
|
|
130
141
|
const [customValues, setCustomValues] = useState({});
|
|
131
142
|
const [showJSON, setShowJSON] = useState(false);
|
|
132
143
|
const [tagsExtracted, setTagsExtracted] = useState(false);
|
|
144
|
+
const [searchValue, setSearchValue] = useState("");
|
|
145
|
+
const [customerModal, setCustomerModal] = useState([false, ""]);
|
|
146
|
+
const [isCustomerDataLoading, setIsCustomerDataLoading] = useState(false);
|
|
147
|
+
const [customerData, setCustomerData] = useState({ name: '', email: '', mobile: '', customerId: '' });
|
|
148
|
+
|
|
133
149
|
// Initialize device based on channel: SMS uses Android/iOS, others use Desktop/Mobile
|
|
134
150
|
// Initialize device based on channel: SMS, WhatsApp, RCS, InApp, MobilePush, and Viber use Android/iOS, others use Desktop/Mobile
|
|
135
151
|
const initialDevice = (channel === CHANNELS.SMS || channel === CHANNELS.WHATSAPP || channel === CHANNELS.RCS || channel === CHANNELS.INAPP || channel === CHANNELS.MOBILEPUSH || channel === CHANNELS.VIBER) ? ANDROID : DESKTOP;
|
|
@@ -155,6 +171,9 @@ const CommonTestAndPreview = (props) => {
|
|
|
155
171
|
const [selectedTestEntities, setSelectedTestEntities] = useState([]);
|
|
156
172
|
const [beeContent, setBeeContent] = useState(''); // Track BEE editor content separately (EMAIL only)
|
|
157
173
|
const previousBeeContentRef = useRef(''); // Track previous BEE content (EMAIL only)
|
|
174
|
+
// Container for notifications so they render inside the slidebox (visible in campaigns/library mode)
|
|
175
|
+
const notificationContainerRef = useRef(null);
|
|
176
|
+
const getNotificationContainer = () => notificationContainerRef.current || document.body;
|
|
158
177
|
// Delivery settings for Test and Preview (SMS, Email, WhatsApp) — user selection only
|
|
159
178
|
const [testPreviewDeliverySettings, setTestPreviewDeliverySettings] = useState({
|
|
160
179
|
[CHANNELS.SMS]: {
|
|
@@ -348,7 +367,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
348
367
|
return content || '';
|
|
349
368
|
}, [channel, formData, currentTab, beeContent, content, beeInstance]);
|
|
350
369
|
|
|
351
|
-
// Build test entities tree data
|
|
370
|
+
// Build test entities tree data from testCustomers prop (includes customers added via addTestCustomer action)
|
|
352
371
|
const testEntitiesTreeData = useMemo(() => {
|
|
353
372
|
const groupsNode = {
|
|
354
373
|
title: 'Groups',
|
|
@@ -361,7 +380,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
361
380
|
title: 'Individuals',
|
|
362
381
|
value: 'customers-node',
|
|
363
382
|
selectable: false,
|
|
364
|
-
children: testCustomers?.map((customer) => ({
|
|
383
|
+
children: testCustomers?.map((customer) => ({
|
|
384
|
+
title: customer?.name?.trim() || customer?.email?.trim() || customer?.mobile?.trim() || customer?.userId || customer?.customerId,
|
|
385
|
+
value: customer?.userId ?? customer?.customerId,
|
|
386
|
+
})) || [],
|
|
365
387
|
};
|
|
366
388
|
|
|
367
389
|
return [groupsNode, customersNode];
|
|
@@ -388,6 +410,94 @@ const CommonTestAndPreview = (props) => {
|
|
|
388
410
|
return resolvedText;
|
|
389
411
|
};
|
|
390
412
|
|
|
413
|
+
/**
|
|
414
|
+
* Common handler for saving test customers (both new and existing)
|
|
415
|
+
*/
|
|
416
|
+
const handleSaveTestCustomer = async (validationErrors = {},setIsLoading = false) => {
|
|
417
|
+
// Check for validation errors before saving (for new customers)
|
|
418
|
+
if (customerModal[1] === CUSTOMER_MODAL_NEW && (validationErrors.email || validationErrors.mobile)) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
setIsLoading(true);
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
let payload;
|
|
426
|
+
|
|
427
|
+
if (customerModal[1] === CUSTOMER_MODAL_EXISTING) {
|
|
428
|
+
// For existing customers, use customerId
|
|
429
|
+
payload = {
|
|
430
|
+
customerId: customerData.customerId
|
|
431
|
+
};
|
|
432
|
+
} else {
|
|
433
|
+
// For new customers, use customer object
|
|
434
|
+
payload = {
|
|
435
|
+
customer: {
|
|
436
|
+
firstName: customerData.name || "",
|
|
437
|
+
mobile: customerData.mobile || "",
|
|
438
|
+
email: customerData.email || ""
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const response = await createTestCustomer(payload);
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
// Handle success: add to test customers list and selection (existing and new)
|
|
447
|
+
if (response && response.success) {
|
|
448
|
+
CapNotification.success({
|
|
449
|
+
message: formatMessage(messages.newTestCustomerAddedSuccess),
|
|
450
|
+
getContainer: getNotificationContainer,
|
|
451
|
+
});
|
|
452
|
+
// API may return customerId in response.response (e.g. { response: { customerId: 438845651 } })
|
|
453
|
+
const res = response?.response || response;
|
|
454
|
+
const addedId = customerModal[1] === CUSTOMER_MODAL_EXISTING
|
|
455
|
+
? customerData?.customerId
|
|
456
|
+
: res?.customerId;
|
|
457
|
+
if (addedId) {
|
|
458
|
+
actions.addTestCustomer({
|
|
459
|
+
userId: addedId,
|
|
460
|
+
customerId: addedId,
|
|
461
|
+
name: customerData?.name?.trim() || '',
|
|
462
|
+
email: customerData?.email || '',
|
|
463
|
+
mobile: customerData?.mobile || '',
|
|
464
|
+
});
|
|
465
|
+
setSelectedTestEntities((prev) => [...prev, addedId]);
|
|
466
|
+
}
|
|
467
|
+
handleCloseCustomerModal();
|
|
468
|
+
} else {
|
|
469
|
+
// Show error notification for unsuccessful response
|
|
470
|
+
CapNotification.error({
|
|
471
|
+
message: formatMessage(messages.errorTitle),
|
|
472
|
+
description: response?.message || formatMessage(messages.failedToAddTestCustomer),
|
|
473
|
+
getContainer: getNotificationContainer,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (customerModal[1] === CUSTOMER_MODAL_EXISTING) {
|
|
478
|
+
// Show error notification for caught exceptions (existing customers only)
|
|
479
|
+
CapNotification.error({
|
|
480
|
+
message: formatMessage(messages.errorTitle),
|
|
481
|
+
description: error?.message || formatMessage(messages.errorAddingTestCustomer),
|
|
482
|
+
getContainer: getNotificationContainer,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
} finally {
|
|
486
|
+
setIsLoading(false);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const handleCloseCustomerModal = () => {
|
|
491
|
+
setCustomerModal([false, ""]);
|
|
492
|
+
setSearchValue('');
|
|
493
|
+
setCustomerData({
|
|
494
|
+
name: '',
|
|
495
|
+
email: '',
|
|
496
|
+
mobile: '',
|
|
497
|
+
customerId: '',
|
|
498
|
+
});
|
|
499
|
+
};
|
|
500
|
+
|
|
391
501
|
/**
|
|
392
502
|
* Prepare payload for preview API based on channel
|
|
393
503
|
*/
|
|
@@ -2418,6 +2528,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2418
2528
|
} catch (error) {
|
|
2419
2529
|
CapNotification.error({
|
|
2420
2530
|
message: formatMessage(messages.invalidJSON),
|
|
2531
|
+
getContainer: getNotificationContainer,
|
|
2421
2532
|
});
|
|
2422
2533
|
}
|
|
2423
2534
|
};
|
|
@@ -2464,6 +2575,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2464
2575
|
} catch (error) {
|
|
2465
2576
|
CapNotification.error({
|
|
2466
2577
|
message: formatMessage(messages.previewUpdateError),
|
|
2578
|
+
getContainer: getNotificationContainer,
|
|
2467
2579
|
});
|
|
2468
2580
|
}
|
|
2469
2581
|
};
|
|
@@ -2472,7 +2584,6 @@ const CommonTestAndPreview = (props) => {
|
|
|
2472
2584
|
* Handle extract tags
|
|
2473
2585
|
*/
|
|
2474
2586
|
const handleExtractTags = () => {
|
|
2475
|
-
// Get content based on channel
|
|
2476
2587
|
let contentToExtract = getCurrentContent;
|
|
2477
2588
|
|
|
2478
2589
|
if (channel === CHANNELS.EMAIL && formData) {
|
|
@@ -2512,6 +2623,110 @@ const CommonTestAndPreview = (props) => {
|
|
|
2512
2623
|
setSelectedTestEntities(value);
|
|
2513
2624
|
};
|
|
2514
2625
|
|
|
2626
|
+
/**
|
|
2627
|
+
* Map API customerDetails item to our customerData shape
|
|
2628
|
+
*/
|
|
2629
|
+
const mapCustomerDetailsToCustomerData = (detail, identifierValue) => {
|
|
2630
|
+
const firstName = detail.firstName || '';
|
|
2631
|
+
const lastName = detail.lastName || '';
|
|
2632
|
+
const name = [firstName, lastName].filter(Boolean).join(' ').trim() || '';
|
|
2633
|
+
const getIdentifierValue = (type) => {
|
|
2634
|
+
const fromIdentifiers = detail.identifiers?.find((i) => i.type === type)?.value;
|
|
2635
|
+
if (fromIdentifiers) return fromIdentifiers;
|
|
2636
|
+
const fromCommChannels = detail.commChannels?.find((c) => c.type === type)?.value;
|
|
2637
|
+
return fromCommChannels || (channel === CHANNELS.EMAIL && type === IDENTIFIER_TYPE_EMAIL ? identifierValue : channel === CHANNELS.SMS && type === IDENTIFIER_TYPE_MOBILE ? identifierValue : '');
|
|
2638
|
+
};
|
|
2639
|
+
return {
|
|
2640
|
+
name,
|
|
2641
|
+
email: channel === CHANNELS.EMAIL ? (getIdentifierValue(IDENTIFIER_TYPE_EMAIL) || identifierValue) : (getIdentifierValue(IDENTIFIER_TYPE_EMAIL) || ''),
|
|
2642
|
+
mobile: channel === CHANNELS.SMS ? (getIdentifierValue(IDENTIFIER_TYPE_MOBILE) || getIdentifierValue(IDENTIFIER_TYPE_PHONE) || identifierValue) : (getIdentifierValue(IDENTIFIER_TYPE_MOBILE) || getIdentifierValue(IDENTIFIER_TYPE_PHONE) || ''),
|
|
2643
|
+
customerId: detail.userId != null ? String(detail.userId) : '',
|
|
2644
|
+
};
|
|
2645
|
+
};
|
|
2646
|
+
|
|
2647
|
+
const handleAddTestCustomer = async () => {
|
|
2648
|
+
const identifierType = channel === CHANNELS.EMAIL ? IDENTIFIER_TYPE_EMAIL : IDENTIFIER_TYPE_MOBILE;
|
|
2649
|
+
const searchValueToCheck = searchValue || '';
|
|
2650
|
+
|
|
2651
|
+
// Check if this customer is already in the test customers list
|
|
2652
|
+
const existingTestCustomer = testCustomers?.find(customer => {
|
|
2653
|
+
if (channel === CHANNELS.EMAIL) {
|
|
2654
|
+
return customer.email === searchValueToCheck;
|
|
2655
|
+
} else if (channel === CHANNELS.SMS) {
|
|
2656
|
+
return customer.mobile === searchValueToCheck;
|
|
2657
|
+
}
|
|
2658
|
+
return false;
|
|
2659
|
+
});
|
|
2660
|
+
|
|
2661
|
+
if (existingTestCustomer) {
|
|
2662
|
+
const entityId = existingTestCustomer.userId ?? existingTestCustomer.customerId;
|
|
2663
|
+
if (entityId != null) {
|
|
2664
|
+
const id = String(entityId);
|
|
2665
|
+
setSelectedTestEntities((prev) =>
|
|
2666
|
+
prev.includes(id) ? prev : [...prev, id]
|
|
2667
|
+
);
|
|
2668
|
+
}
|
|
2669
|
+
setSearchValue('');
|
|
2670
|
+
CapNotification.success({
|
|
2671
|
+
message: formatMessage(messages.customerAlreadyInTestList),
|
|
2672
|
+
getContainer: getNotificationContainer,
|
|
2673
|
+
});
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
setIsCustomerDataLoading(true);
|
|
2678
|
+
|
|
2679
|
+
try {
|
|
2680
|
+
const response = await getMembersLookup(identifierType, searchValueToCheck);
|
|
2681
|
+
const success = response?.success && !response?.status?.isError;
|
|
2682
|
+
const res = response?.response || {};
|
|
2683
|
+
const exists = res.exists || false;
|
|
2684
|
+
const details = res.customerDetails || [];
|
|
2685
|
+
|
|
2686
|
+
if (!success) {
|
|
2687
|
+
const errorMessage = response?.message || response?.status?.message || formatMessage(messages.memberLookupError);
|
|
2688
|
+
CapNotification.error({ title: formatMessage(messages.errorTitle), message: errorMessage, getContainer: getNotificationContainer });
|
|
2689
|
+
return;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
if (exists && details.length > 0) {
|
|
2693
|
+
const mapped = mapCustomerDetailsToCustomerData(details[0], searchValueToCheck);
|
|
2694
|
+
const customerIdFromLookup = mapped.customerId;
|
|
2695
|
+
const alreadyInTestListByCustomerId = customerIdFromLookup && testCustomers?.some(
|
|
2696
|
+
(c) => String(c?.customerId) === customerIdFromLookup || String(c?.userId) === customerIdFromLookup
|
|
2697
|
+
);
|
|
2698
|
+
if (alreadyInTestListByCustomerId) {
|
|
2699
|
+
setSelectedTestEntities((prev) =>
|
|
2700
|
+
prev.includes(customerIdFromLookup) ? prev : [...prev, customerIdFromLookup]
|
|
2701
|
+
);
|
|
2702
|
+
setSearchValue('');
|
|
2703
|
+
CapNotification.success({
|
|
2704
|
+
message: formatMessage(messages.customerAlreadyInTestList),
|
|
2705
|
+
getContainer: getNotificationContainer,
|
|
2706
|
+
});
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
setCustomerData(mapped);
|
|
2710
|
+
setCustomerModal([true, CUSTOMER_MODAL_EXISTING]);
|
|
2711
|
+
} else {
|
|
2712
|
+
setCustomerData({
|
|
2713
|
+
name: '',
|
|
2714
|
+
email: channel === CHANNELS.EMAIL ? searchValueToCheck : '',
|
|
2715
|
+
mobile: channel === CHANNELS.SMS ? searchValueToCheck : '',
|
|
2716
|
+
customerId: '',
|
|
2717
|
+
});
|
|
2718
|
+
setCustomerModal([true, CUSTOMER_MODAL_NEW]);
|
|
2719
|
+
}
|
|
2720
|
+
} catch {
|
|
2721
|
+
CapNotification.error({
|
|
2722
|
+
message: formatMessage(messages.memberLookupError),
|
|
2723
|
+
getContainer: getNotificationContainer,
|
|
2724
|
+
});
|
|
2725
|
+
} finally {
|
|
2726
|
+
setIsCustomerDataLoading(false);
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
|
|
2515
2730
|
/**
|
|
2516
2731
|
* Handle send test message
|
|
2517
2732
|
*/
|
|
@@ -2558,10 +2773,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
2558
2773
|
if (result) {
|
|
2559
2774
|
CapNotification.success({
|
|
2560
2775
|
message: formatMessage(messages.testMessageSent),
|
|
2776
|
+
getContainer: getNotificationContainer,
|
|
2561
2777
|
});
|
|
2562
2778
|
} else {
|
|
2563
2779
|
CapNotification.error({
|
|
2564
2780
|
message: formatMessage(messages.testMessageFailed),
|
|
2781
|
+
getContainer: getNotificationContainer,
|
|
2565
2782
|
});
|
|
2566
2783
|
}
|
|
2567
2784
|
});
|
|
@@ -2630,6 +2847,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2630
2847
|
content={getCurrentContent}
|
|
2631
2848
|
channel={channel}
|
|
2632
2849
|
isSendingTestMessage={isSendingTestMessage}
|
|
2850
|
+
renderAddTestCustomerButton={renderAddTestCustomerButton}
|
|
2633
2851
|
formatMessage={formatMessage}
|
|
2634
2852
|
deliverySettings={testPreviewDeliverySettings[channel]}
|
|
2635
2853
|
senderDetailsOptions={senderDetailsByChannel[channel]}
|
|
@@ -2638,6 +2856,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
2638
2856
|
isLoadingSenderDetails={isLoadingSenderDetails}
|
|
2639
2857
|
smsTraiDltEnabled={smsTraiDltEnabled}
|
|
2640
2858
|
registeredSenderIds={registeredSenderIds}
|
|
2859
|
+
searchValue={searchValue}
|
|
2860
|
+
setSearchValue={setSearchValue}
|
|
2641
2861
|
/>
|
|
2642
2862
|
);
|
|
2643
2863
|
|
|
@@ -2647,6 +2867,20 @@ const CommonTestAndPreview = (props) => {
|
|
|
2647
2867
|
/>
|
|
2648
2868
|
);
|
|
2649
2869
|
|
|
2870
|
+
const renderAddTestCustomerButton = () => {
|
|
2871
|
+
const value = searchValue || "";
|
|
2872
|
+
const showAddButton =
|
|
2873
|
+
[CHANNELS.EMAIL, CHANNELS.SMS].includes(channel) &&
|
|
2874
|
+
(channel === CHANNELS.EMAIL ? isValidEmail(value) : isValidMobile(value));
|
|
2875
|
+
if (!showAddButton) return null;
|
|
2876
|
+
return (
|
|
2877
|
+
<AddTestCustomerButton
|
|
2878
|
+
searchValue={value}
|
|
2879
|
+
handleAddTestCustomer={handleAddTestCustomer}
|
|
2880
|
+
/>
|
|
2881
|
+
);
|
|
2882
|
+
};
|
|
2883
|
+
|
|
2650
2884
|
// Header content for the slidebox
|
|
2651
2885
|
const slideboxHeader = (
|
|
2652
2886
|
<CapRow className="test-preview-header">
|
|
@@ -2666,14 +2900,19 @@ const CommonTestAndPreview = (props) => {
|
|
|
2666
2900
|
show={show}
|
|
2667
2901
|
size="size-xl"
|
|
2668
2902
|
content={(
|
|
2669
|
-
<
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
{/*
|
|
2903
|
+
<div ref={notificationContainerRef} className="common-test-and-preview-notification-container" style={{ position: 'relative', height: '100%' }}>
|
|
2904
|
+
<CapSpin
|
|
2905
|
+
spinning={isCustomerDataLoading}
|
|
2906
|
+
className={`common-test-preview-lookup-spin ${isCustomerDataLoading ? 'common-test-preview-customer-loading' : ''}`}
|
|
2907
|
+
>
|
|
2908
|
+
<CapRow className="test-preview-container">
|
|
2909
|
+
<CapRow className="test-and-preview-panels">
|
|
2910
|
+
{/* Left Panel */}
|
|
2911
|
+
<CapRow className="left-panel">
|
|
2912
|
+
{channel === CHANNELS.ZALO ? null : renderLeftPanelContent()}
|
|
2913
|
+
<CapDivider className="panel-divider" />
|
|
2914
|
+
|
|
2915
|
+
{/* Send Test Message Section */}
|
|
2677
2916
|
{config.enableTestMessage !== false && (
|
|
2678
2917
|
<CapRow className="panel-section send-test-section">
|
|
2679
2918
|
{renderSendTestMessage()}
|
|
@@ -2687,7 +2926,28 @@ const CommonTestAndPreview = (props) => {
|
|
|
2687
2926
|
{renderPreview()}
|
|
2688
2927
|
</CapRow>
|
|
2689
2928
|
</CapRow>
|
|
2690
|
-
|
|
2929
|
+
{customerModal[0] && customerModal[1] === CUSTOMER_MODAL_EXISTING && (
|
|
2930
|
+
<ExistingCustomerModal
|
|
2931
|
+
customerData={customerData}
|
|
2932
|
+
setCustomerModal={setCustomerModal}
|
|
2933
|
+
customerModal={customerModal}
|
|
2934
|
+
channel={channel}
|
|
2935
|
+
onSave={handleSaveTestCustomer}
|
|
2936
|
+
/>
|
|
2937
|
+
)}
|
|
2938
|
+
{customerModal[0] && customerModal[1] === CUSTOMER_MODAL_NEW && (
|
|
2939
|
+
<CustomerCreationModal
|
|
2940
|
+
customerData={customerData}
|
|
2941
|
+
setCustomerData={setCustomerData}
|
|
2942
|
+
setCustomerModal={setCustomerModal}
|
|
2943
|
+
customerModal={customerModal}
|
|
2944
|
+
onSave={handleSaveTestCustomer}
|
|
2945
|
+
channel={channel}
|
|
2946
|
+
/>
|
|
2947
|
+
)}
|
|
2948
|
+
</CapRow>
|
|
2949
|
+
</CapSpin>
|
|
2950
|
+
</div>
|
|
2691
2951
|
)}
|
|
2692
2952
|
/>
|
|
2693
2953
|
);
|
|
@@ -2789,4 +3049,4 @@ CommonTestAndPreview.defaultProps = {
|
|
|
2789
3049
|
// Note: Redux connection is handled by the wrapper components (e.g., TestAndPreviewSlidebox)
|
|
2790
3050
|
// This component receives all Redux props (including intl) from its parent
|
|
2791
3051
|
|
|
2792
|
-
export default CommonTestAndPreview;
|
|
3052
|
+
export default CommonTestAndPreview;
|
|
@@ -8,10 +8,72 @@ import { defineMessages } from 'react-intl';
|
|
|
8
8
|
export const scope = 'app.v2Components.TestAndPreviewSlidebox';
|
|
9
9
|
|
|
10
10
|
export default defineMessages({
|
|
11
|
+
mobileAlreadyExists: {
|
|
12
|
+
id: `${scope}.mobileAlreadyExists`,
|
|
13
|
+
defaultMessage: 'This phone number matches with already existing user profile. Please remove or use a different number.',
|
|
14
|
+
},
|
|
15
|
+
customerNamePlaceholder: {
|
|
16
|
+
id: `${scope}.customerNamePlaceholder`,
|
|
17
|
+
defaultMessage: 'Enter the name',
|
|
18
|
+
},
|
|
19
|
+
customerEmailPlaceholder: {
|
|
20
|
+
id: `${scope}.customerEmailPlaceholder`,
|
|
21
|
+
defaultMessage: 'Enter the Email',
|
|
22
|
+
},
|
|
23
|
+
customerMobilePlaceholder: {
|
|
24
|
+
id: `${scope}.customerMobilePlaceholder`,
|
|
25
|
+
defaultMessage: 'Enter the Mobile Number',
|
|
26
|
+
},
|
|
27
|
+
customerAlreadyInTestList: {
|
|
28
|
+
id: `${scope}.customerAlreadyInTestList`,
|
|
29
|
+
defaultMessage: 'This customer is already in the test customers list.',
|
|
30
|
+
},
|
|
31
|
+
emailAlreadyExists: {
|
|
32
|
+
id: `${scope}.emailAlreadyExists`,
|
|
33
|
+
defaultMessage: 'This email matches with already existing user profile. Please remove or use a different email.',
|
|
34
|
+
},
|
|
35
|
+
customerName: {
|
|
36
|
+
id: `${scope}.customerName`,
|
|
37
|
+
defaultMessage: 'Name',
|
|
38
|
+
},
|
|
39
|
+
customerEmail: {
|
|
40
|
+
id: `${scope}.customerEmail`,
|
|
41
|
+
defaultMessage: 'Email',
|
|
42
|
+
},
|
|
43
|
+
customerMobile: {
|
|
44
|
+
id: `${scope}.customerMobileNumber`,
|
|
45
|
+
defaultMessage: 'Mobile Number',
|
|
46
|
+
},
|
|
47
|
+
saveButton: {
|
|
48
|
+
id: `${scope}.saveButton`,
|
|
49
|
+
defaultMessage: 'Save',
|
|
50
|
+
},
|
|
51
|
+
cancelButton: {
|
|
52
|
+
id: `${scope}.cancelButton`,
|
|
53
|
+
defaultMessage: 'Cancel',
|
|
54
|
+
},
|
|
55
|
+
customerID: {
|
|
56
|
+
id: `${scope}.customerID`,
|
|
57
|
+
defaultMessage: 'Customer ID',
|
|
58
|
+
},
|
|
59
|
+
existingCustomerModalDescription: {
|
|
60
|
+
id: `${scope}.existingCustomerModalDescription`,
|
|
61
|
+
defaultMessage: 'This user profile already exists in the system. Would you like to add them to Test Customer list?'
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
customerCreationModalTitle: {
|
|
65
|
+
id: `${scope}.customerCreationModalTitle`,
|
|
66
|
+
defaultMessage: 'Add new test customer',
|
|
67
|
+
},
|
|
68
|
+
customerCreationModalDescription: {
|
|
69
|
+
id: `${scope}.customerCreationModalDescription`,
|
|
70
|
+
defaultMessage: 'This customer profile will be available for testing across multiple communication channels.',
|
|
71
|
+
},
|
|
11
72
|
testAndPreviewHeader: {
|
|
12
73
|
id: `${scope}.testAndPreviewHeader`,
|
|
13
74
|
defaultMessage: 'Preview and Test',
|
|
14
75
|
},
|
|
76
|
+
|
|
15
77
|
customerSearchTitle: {
|
|
16
78
|
id: `${scope}.customerSearchTitle`,
|
|
17
79
|
defaultMessage: 'Customer',
|
|
@@ -32,6 +94,34 @@ export default defineMessages({
|
|
|
32
94
|
id: `${scope}.showJSON`,
|
|
33
95
|
defaultMessage: 'Show JSON',
|
|
34
96
|
},
|
|
97
|
+
addTestCustomer: {
|
|
98
|
+
id: `${scope}.addTestCustomer`,
|
|
99
|
+
defaultMessage: 'Add as Test Customer',
|
|
100
|
+
},
|
|
101
|
+
addTestCustomerWithValue: {
|
|
102
|
+
id: `${scope}.addTestCustomerWithValue`,
|
|
103
|
+
defaultMessage: 'Add {searchValue} as Test Customer',
|
|
104
|
+
},
|
|
105
|
+
memberLookupError: {
|
|
106
|
+
id: `${scope}.memberLookupError`,
|
|
107
|
+
defaultMessage: 'Unable to look up customer. Please try again.',
|
|
108
|
+
},
|
|
109
|
+
newTestCustomerAddedSuccess: {
|
|
110
|
+
id: `${scope}.newTestCustomerAddedSuccess`,
|
|
111
|
+
defaultMessage: 'New test customer added successfully!',
|
|
112
|
+
},
|
|
113
|
+
errorTitle: {
|
|
114
|
+
id: `${scope}.errorTitle`,
|
|
115
|
+
defaultMessage: 'Error',
|
|
116
|
+
},
|
|
117
|
+
failedToAddTestCustomer: {
|
|
118
|
+
id: `${scope}.failedToAddTestCustomer`,
|
|
119
|
+
defaultMessage: 'Failed to add test customer',
|
|
120
|
+
},
|
|
121
|
+
errorAddingTestCustomer: {
|
|
122
|
+
id: `${scope}.errorAddingTestCustomer`,
|
|
123
|
+
defaultMessage: 'An error occurred while adding test customer',
|
|
124
|
+
},
|
|
35
125
|
discardCustomValues: {
|
|
36
126
|
id: `${scope}.discardCustomValues`,
|
|
37
127
|
defaultMessage: 'Discard custom values',
|
|
@@ -120,6 +210,10 @@ export default defineMessages({
|
|
|
120
210
|
id: `${scope}.testCustomersPlaceholder`,
|
|
121
211
|
defaultMessage: 'Search and select a group or individual test customers',
|
|
122
212
|
},
|
|
213
|
+
noMatchingOptions: {
|
|
214
|
+
id: `${scope}.noMatchingOptions`,
|
|
215
|
+
defaultMessage: 'No matching options',
|
|
216
|
+
},
|
|
123
217
|
updatingPreview: {
|
|
124
218
|
id: `${scope}.updatingPreview`,
|
|
125
219
|
defaultMessage: 'Updating preview with the latest changes',
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
GET_TEST_CUSTOMERS_REQUESTED,
|
|
21
21
|
GET_TEST_CUSTOMERS_SUCCESS,
|
|
22
22
|
GET_TEST_CUSTOMERS_FAILURE,
|
|
23
|
+
ADD_TEST_CUSTOMER,
|
|
23
24
|
GET_TEST_GROUPS_REQUESTED,
|
|
24
25
|
GET_TEST_GROUPS_SUCCESS,
|
|
25
26
|
GET_TEST_GROUPS_FAILURE,
|
|
@@ -216,6 +217,15 @@ const previewAndTestReducer = (state = initialState, action) => {
|
|
|
216
217
|
return state.set('isFetchingTestCustomers', false)
|
|
217
218
|
.set('fetchTestCustomersError', action.payload.error);
|
|
218
219
|
|
|
220
|
+
case ADD_TEST_CUSTOMER: {
|
|
221
|
+
const raw = state.get('testCustomers');
|
|
222
|
+
const list = Array.isArray(raw) ? raw : (raw && raw.toArray ? raw.toArray().map((i) => (i && i.toJS ? i.toJS() : i)) : []);
|
|
223
|
+
const customer = action.payload.customer;
|
|
224
|
+
const newId = customer.userId || customer.customerId;
|
|
225
|
+
if (list.some((c) => (c.userId || c.customerId) === newId)) return state;
|
|
226
|
+
return state.set('testCustomers', list.concat([customer]));
|
|
227
|
+
}
|
|
228
|
+
|
|
219
229
|
// Test Groups
|
|
220
230
|
case GET_TEST_GROUPS_REQUESTED:
|
|
221
231
|
return state.set('isFetchingTestGroups', true)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AddTestCustomerButton Component
|
|
3
|
+
*
|
|
4
|
+
* The parent (index.js) only renders this button when channel is EMAIL/SMS and value is valid.
|
|
5
|
+
* This component always renders the button when mounted; visibility is the parent's responsibility.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
10
|
+
import { IntlProvider } from 'react-intl';
|
|
11
|
+
import AddTestCustomerButton from '../AddTestCustomer';
|
|
12
|
+
|
|
13
|
+
const mockMessages = {
|
|
14
|
+
'app.v2Components.TestAndPreviewSlidebox.addTestCustomerWithValue': 'Add {searchValue} as Test Customer',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const TestWrapper = ({ children }) => (
|
|
18
|
+
<IntlProvider locale="en" messages={mockMessages}>
|
|
19
|
+
{children}
|
|
20
|
+
</IntlProvider>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
describe('AddTestCustomerButton', () => {
|
|
24
|
+
const defaultProps = {
|
|
25
|
+
searchValue: 'user@example.com',
|
|
26
|
+
handleAddTestCustomer: jest.fn(),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should render button with searchValue in message', () => {
|
|
34
|
+
render(
|
|
35
|
+
<TestWrapper>
|
|
36
|
+
<AddTestCustomerButton {...defaultProps} />
|
|
37
|
+
</TestWrapper>
|
|
38
|
+
);
|
|
39
|
+
const button = screen.getByRole('button', { name: /add.*test customer/i });
|
|
40
|
+
expect(button).toBeTruthy();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should show searchValue in button text', () => {
|
|
44
|
+
render(
|
|
45
|
+
<TestWrapper>
|
|
46
|
+
<AddTestCustomerButton {...defaultProps} searchValue="user@example.com" />
|
|
47
|
+
</TestWrapper>
|
|
48
|
+
);
|
|
49
|
+
expect(screen.getByRole('button', { name: /user@example.com/i })).toBeTruthy();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should call handleAddTestCustomer when button is clicked', () => {
|
|
53
|
+
const handleAddTestCustomer = jest.fn();
|
|
54
|
+
render(
|
|
55
|
+
<TestWrapper>
|
|
56
|
+
<AddTestCustomerButton
|
|
57
|
+
searchValue="user@example.com"
|
|
58
|
+
handleAddTestCustomer={handleAddTestCustomer}
|
|
59
|
+
/>
|
|
60
|
+
</TestWrapper>
|
|
61
|
+
);
|
|
62
|
+
const button = screen.getByRole('button', { name: /add.*test customer/i });
|
|
63
|
+
fireEvent.click(button);
|
|
64
|
+
expect(handleAddTestCustomer).toHaveBeenCalledTimes(1);
|
|
65
|
+
});
|
|
66
|
+
});
|