@capillarytech/creatives-library 9.0.14 → 9.0.15-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +32 -0
- package/global-styles.js +1 -1
- package/package.json +1 -1
- package/services/tests/api.test.js +35 -20
- package/utils/common.js +8 -0
- package/utils/commonUtils.js +19 -1
- package/utils/rcsPayloadUtils.js +92 -0
- package/utils/templateVarUtils.js +201 -0
- package/utils/tests/rcsPayloadUtils.test.js +226 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +7 -0
- package/v2Components/CapActionButton/index.js +168 -109
- package/v2Components/CapActionButton/index.scss +156 -5
- package/v2Components/CapActionButton/messages.js +19 -3
- package/v2Components/CapActionButton/tests/index.test.js +42 -18
- package/v2Components/CapImageUpload/index.js +2 -2
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +60 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +201 -23
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +355 -76
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +38 -2
- package/v2Components/CommonTestAndPreview/index.js +761 -222
- package/v2Components/CommonTestAndPreview/messages.js +45 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +25 -6
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -267
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
- package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
- package/v2Components/CommonTestAndPreview/utils.js +84 -0
- package/v2Components/FormBuilder/Classic.js +4487 -0
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +366 -0
- package/v2Components/FormBuilder/Functional/channels/registry.js +17 -0
- package/v2Components/FormBuilder/Functional/channels/sms/buildSubmitPayload.js +9 -0
- package/v2Components/FormBuilder/Functional/channels/sms/config.js +30 -0
- package/v2Components/FormBuilder/Functional/channels/sms/getEditorErrorDescriptor.js +46 -0
- package/v2Components/FormBuilder/Functional/channels/sms/getLiquidContent.js +13 -0
- package/v2Components/FormBuilder/Functional/channels/sms/index.js +22 -0
- package/v2Components/FormBuilder/Functional/channels/sms/tests/getEditorErrorDescriptor.test.js +52 -0
- package/v2Components/FormBuilder/Functional/channels/sms/tests/getLiquidContent.test.js +25 -0
- package/v2Components/FormBuilder/Functional/channels/sms/tests/validate.test.js +87 -0
- package/v2Components/FormBuilder/Functional/channels/sms/validate.js +89 -0
- package/v2Components/FormBuilder/Functional/constants.js +42 -0
- package/v2Components/FormBuilder/Functional/core/schema/fieldRegistry.js +38 -0
- package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +85 -0
- package/v2Components/FormBuilder/Functional/core/store/formReducer.js +81 -0
- package/v2Components/FormBuilder/Functional/core/store/selectors.js +30 -0
- package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +91 -0
- package/v2Components/FormBuilder/Functional/index.js +26 -0
- package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +59 -0
- package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +31 -0
- package/v2Components/FormBuilder/Functional/layout/Section.js +116 -0
- package/v2Components/FormBuilder/Functional/renderers/smsRenderers.js +258 -0
- package/v2Components/FormBuilder/Functional/tests/channelRegistry.test.js +21 -0
- package/v2Components/FormBuilder/Functional/tests/fieldRegistry.test.js +65 -0
- package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +97 -0
- package/v2Components/FormBuilder/Functional/tests/fixtures/smsParityCases.js +192 -0
- package/v2Components/FormBuilder/Functional/tests/formReducer.test.js +129 -0
- package/v2Components/FormBuilder/Functional/tests/initializeFormState.test.js +132 -0
- package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +40 -0
- package/v2Components/FormBuilder/Functional/tests/section.test.js +99 -0
- package/v2Components/FormBuilder/Functional/tests/selectors.test.js +67 -0
- package/v2Components/FormBuilder/Functional/tests/sms.crossFlowParity.test.js +155 -0
- package/v2Components/FormBuilder/Functional/tests/sms.liquid.test.js +172 -0
- package/v2Components/FormBuilder/Functional/tests/sms.rollout.test.js +122 -0
- package/v2Components/FormBuilder/Functional/tests/sms.shell.parity.test.js +329 -0
- package/v2Components/FormBuilder/Functional/tests/smsRenderers.test.js +160 -0
- package/v2Components/FormBuilder/Functional/tests/toLegacyFormData.test.js +95 -0
- package/v2Components/FormBuilder/_formBuilder.scss +5 -0
- package/v2Components/FormBuilder/index.js +41 -4479
- package/v2Components/FormBuilder/tests/__snapshots__/sms.characterization.test.js.snap +114 -0
- package/v2Components/FormBuilder/tests/entryGate.test.js +106 -0
- package/v2Components/FormBuilder/tests/sms.characterization.test.js +336 -0
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
- package/v2Components/SmsFallback/constants.js +94 -0
- package/v2Components/SmsFallback/index.js +958 -0
- package/v2Components/SmsFallback/index.scss +266 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +41 -22
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +143 -31
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
- package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
- package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +17 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
- package/v2Containers/CreativesContainer/constants.js +11 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
- package/v2Containers/CreativesContainer/index.js +323 -104
- package/v2Containers/CreativesContainer/index.scss +83 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +79 -34
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +333 -90
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
- package/v2Containers/Rcs/constants.js +131 -11
- package/v2Containers/Rcs/index.js +2602 -807
- package/v2Containers/Rcs/index.scss +289 -8
- package/v2Containers/Rcs/messages.js +34 -3
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +227 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +73535 -38537
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -8
- package/v2Containers/Rcs/tests/index.test.js +147 -36
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
- package/v2Containers/Rcs/tests/utils.test.js +646 -30
- package/v2Containers/Rcs/utils.js +478 -11
- package/v2Containers/Sms/Create/index.js +115 -48
- package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Create/index.js +9 -4
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/index.js +678 -169
- package/v2Containers/SmsTrai/Edit/index.scss +126 -0
- package/v2Containers/SmsTrai/Edit/messages.js +14 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +6 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +171 -12
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +125 -55
- package/v2Containers/Templates/sagas.js +57 -13
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
- package/v2Containers/Templates/tests/sagas.test.js +199 -16
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/v2Containers/TemplatesV2/index.js +86 -23
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js
CHANGED
|
@@ -9,29 +9,72 @@
|
|
|
9
9
|
|
|
10
10
|
import get from 'lodash/get';
|
|
11
11
|
import { CHANNELS } from '../../constants';
|
|
12
|
+
import { RCS_CONTACT_INFO_GSM_TYPE_KEYS } from '../constants';
|
|
13
|
+
import { typeMatches, unwrapEntity } from '../../utils';
|
|
12
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Include row unless explicitly invalid — many APIs omit `valid`, which would
|
|
17
|
+
* otherwise filter out every sender (RCS/SMS lists empty in UI).
|
|
18
|
+
*/
|
|
13
19
|
const getSenderOptions = (contactInfo, key) =>
|
|
14
20
|
(contactInfo || [])
|
|
15
|
-
.filter((contactInfoItem) => contactInfoItem.type
|
|
21
|
+
.filter((contactInfoItem) => typeMatches(contactInfoItem.type, key) && contactInfoItem.valid !== false)
|
|
16
22
|
.sort((contact) => (contact.default ? -1 : 0));
|
|
17
23
|
|
|
24
|
+
/** RCS APIs may label senders under several `contactInfo.type` keys — see `RCS_CONTACT_INFO_GSM_TYPE_KEYS`. */
|
|
25
|
+
const getRcsLikeGsmSenders = (contactInfo) => {
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
const mergedSenderRows = [];
|
|
28
|
+
RCS_CONTACT_INFO_GSM_TYPE_KEYS.forEach((key) => {
|
|
29
|
+
getSenderOptions(contactInfo, key).forEach((row) => {
|
|
30
|
+
const senderValue = row?.value;
|
|
31
|
+
// Skip empty/whitespace sender values to avoid polluting the RCS sender dropdown.
|
|
32
|
+
if (senderValue == null || String(senderValue).trim() === '' || seen.has(senderValue)) return;
|
|
33
|
+
seen.add(senderValue);
|
|
34
|
+
mergedSenderRows.push(row);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
return mergedSenderRows.sort((a, b) => (a.default ? -1 : 0) - (b.default ? -1 : 0));
|
|
38
|
+
};
|
|
39
|
+
|
|
18
40
|
/**
|
|
19
41
|
* Parse raw API response for one channel into { domains: [...] }
|
|
20
|
-
* @param {string} channel - SMS | EMAIL | WHATSAPP
|
|
42
|
+
* @param {string} channel - SMS | EMAIL | WHATSAPP | RCS
|
|
21
43
|
* @param {Object} response - API response (e.g. { entity: { SMS: [...] } } or { entity: [...] })
|
|
22
44
|
* @returns {{ domains: Array }} - domains array compatible with campaigns DeliverySettingsV2
|
|
23
45
|
*/
|
|
24
46
|
export function parseSenderDetailsResponse(channel, response) {
|
|
25
|
-
const entity =
|
|
26
|
-
if (
|
|
47
|
+
const entity = unwrapEntity(response);
|
|
48
|
+
if (entity == null || entity === '') {
|
|
27
49
|
return { domains: [] };
|
|
28
50
|
}
|
|
29
51
|
|
|
30
|
-
//
|
|
31
|
-
|
|
52
|
+
// Normalize once: entity keys may be any casing (SMS/sms/rcs) but downstream branches use CHANNELS.* (uppercase).
|
|
53
|
+
const normalizedChannel =
|
|
54
|
+
channel == null || String(channel).trim() === ''
|
|
55
|
+
? ''
|
|
56
|
+
: String(channel).trim().toUpperCase();
|
|
57
|
+
|
|
58
|
+
// Single-channel API: entity may be { [channel]: [...] } or direct array.
|
|
59
|
+
// Some responses use different casing (e.g. rcs vs RCS) or a single domain object instead of an array.
|
|
60
|
+
let channelSenderDetails =
|
|
61
|
+
get(entity, channel, null)
|
|
62
|
+
?? (normalizedChannel ? get(entity, normalizedChannel, null) : null)
|
|
63
|
+
?? (normalizedChannel ? get(entity, normalizedChannel.toLowerCase(), null) : null);
|
|
64
|
+
|
|
32
65
|
if (channelSenderDetails == null && Array.isArray(entity)) {
|
|
33
66
|
channelSenderDetails = entity;
|
|
34
67
|
}
|
|
68
|
+
|
|
69
|
+
if (channelSenderDetails != null && !Array.isArray(channelSenderDetails)) {
|
|
70
|
+
const single = channelSenderDetails;
|
|
71
|
+
if (single && typeof single === 'object' && single.domainProperties) {
|
|
72
|
+
channelSenderDetails = [single];
|
|
73
|
+
} else {
|
|
74
|
+
return { domains: [] };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
35
78
|
if (!Array.isArray(channelSenderDetails)) {
|
|
36
79
|
return { domains: [] };
|
|
37
80
|
}
|
|
@@ -42,6 +85,7 @@ export function parseSenderDetailsResponse(channel, response) {
|
|
|
42
85
|
const { id: dgmId, priority, domainProperties = {} } = element;
|
|
43
86
|
const {
|
|
44
87
|
domainName,
|
|
88
|
+
hostName,
|
|
45
89
|
id = -1,
|
|
46
90
|
contactInfo,
|
|
47
91
|
connectionProperties: {
|
|
@@ -51,21 +95,25 @@ export function parseSenderDetailsResponse(channel, response) {
|
|
|
51
95
|
} = {},
|
|
52
96
|
} = domainProperties;
|
|
53
97
|
|
|
98
|
+
const resolvedDomainName = domainName || hostName;
|
|
99
|
+
|
|
54
100
|
const domain = {
|
|
55
101
|
dgmId,
|
|
56
|
-
domainName,
|
|
102
|
+
domainName: resolvedDomainName,
|
|
57
103
|
domainId: id,
|
|
58
104
|
priority: priority != null ? priority : 0,
|
|
59
105
|
};
|
|
60
106
|
|
|
61
|
-
if ([CHANNELS.SMS, CHANNELS.WHATSAPP].includes(
|
|
107
|
+
if ([CHANNELS.SMS, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(normalizedChannel)) {
|
|
62
108
|
domain.cdmaSenders = getSenderOptions(contactInfo, 'cdma_sender_id');
|
|
63
|
-
domain.gsmSenders =
|
|
109
|
+
domain.gsmSenders = normalizedChannel === CHANNELS.RCS
|
|
110
|
+
? getRcsLikeGsmSenders(contactInfo)
|
|
111
|
+
: getSenderOptions(contactInfo, 'gsm_sender_id');
|
|
64
112
|
domain.sourceAccountIdentifier =
|
|
65
113
|
sourceAccountIdentifier || wabaId || userid || '';
|
|
66
114
|
}
|
|
67
115
|
|
|
68
|
-
if (
|
|
116
|
+
if (normalizedChannel === CHANNELS.EMAIL) {
|
|
69
117
|
domain.emailSenders = getSenderOptions(contactInfo, 'sender_id');
|
|
70
118
|
domain.emailRepliers = getSenderOptions(contactInfo, 'reply_to_id');
|
|
71
119
|
}
|
|
@@ -76,7 +124,8 @@ export function parseSenderDetailsResponse(channel, response) {
|
|
|
76
124
|
// Sort by priority and dedupe by domainName (match campaigns behaviour)
|
|
77
125
|
domains.sort((a, b) => (a.priority || 0) - (b.priority || 0));
|
|
78
126
|
const deduped = domains.reduce((acc, domain) => {
|
|
79
|
-
|
|
127
|
+
const key = domain.domainName ?? domain.domainId;
|
|
128
|
+
if (!acc.find((existing) => (existing.domainName ?? existing.domainId) === key)) {
|
|
80
129
|
acc.push(domain);
|
|
81
130
|
}
|
|
82
131
|
return acc;
|
|
@@ -6,12 +6,13 @@ import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
|
6
6
|
import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
|
|
7
7
|
import CapStepsAccordian from '@capillarytech/cap-ui-library/CapStepsAccordian';
|
|
8
8
|
import CapTreeSelect from '@capillarytech/cap-ui-library/CapTreeSelect';
|
|
9
|
+
import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
|
|
9
10
|
import isEmpty from 'lodash/isEmpty';
|
|
10
11
|
import messages from './messages';
|
|
11
12
|
import DeliverySettings from './DeliverySettings';
|
|
12
13
|
import { CHANNELS } from './constants';
|
|
13
14
|
|
|
14
|
-
const CHANNELS_WITH_DELIVERY_SETTINGS = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
|
|
15
|
+
const CHANNELS_WITH_DELIVERY_SETTINGS = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
|
|
15
16
|
|
|
16
17
|
const SendTestMessage = ({
|
|
17
18
|
isFetchingTestCustomers,
|
|
@@ -28,12 +29,13 @@ const SendTestMessage = ({
|
|
|
28
29
|
searchValue,
|
|
29
30
|
setSearchValue,
|
|
30
31
|
deliverySettings,
|
|
31
|
-
|
|
32
|
+
senderDetailsByChannel = {},
|
|
32
33
|
wecrmAccounts,
|
|
33
34
|
onSaveDeliverySettings,
|
|
34
35
|
isLoadingSenderDetails,
|
|
35
36
|
smsTraiDltEnabled,
|
|
36
37
|
registeredSenderIds,
|
|
38
|
+
isChannelSmsFallbackPreviewEnabled,
|
|
37
39
|
}) => {
|
|
38
40
|
const addCustomerContent = renderAddTestCustomerButton ? renderAddTestCustomerButton() : null;
|
|
39
41
|
return (
|
|
@@ -77,13 +79,14 @@ const SendTestMessage = ({
|
|
|
77
79
|
<DeliverySettings
|
|
78
80
|
channel={channel}
|
|
79
81
|
deliverySettings={deliverySettings || {}}
|
|
80
|
-
|
|
82
|
+
senderDetailsByChannel={senderDetailsByChannel}
|
|
81
83
|
wecrmAccounts={wecrmAccounts || []}
|
|
82
84
|
onSaveDeliverySettings={onSaveDeliverySettings}
|
|
83
85
|
isLoadingSenderDetails={isLoadingSenderDetails}
|
|
84
86
|
formatMessage={formatMessage}
|
|
85
87
|
smsTraiDltEnabled={smsTraiDltEnabled}
|
|
86
88
|
registeredSenderIds={registeredSenderIds}
|
|
89
|
+
isChannelSmsFallbackPreviewEnabled={isChannelSmsFallbackPreviewEnabled}
|
|
87
90
|
whatsappAccountFromForm={
|
|
88
91
|
channel === CHANNELS.WHATSAPP && formData?.accountName
|
|
89
92
|
? { accountName: formData.accountName }
|
|
@@ -121,12 +124,13 @@ SendTestMessage.propTypes = {
|
|
|
121
124
|
searchValue: PropTypes.string,
|
|
122
125
|
setSearchValue: PropTypes.func,
|
|
123
126
|
deliverySettings: PropTypes.object,
|
|
124
|
-
|
|
127
|
+
senderDetailsByChannel: PropTypes.object,
|
|
125
128
|
wecrmAccounts: PropTypes.array,
|
|
126
129
|
onSaveDeliverySettings: PropTypes.func,
|
|
127
130
|
isLoadingSenderDetails: PropTypes.bool,
|
|
128
131
|
smsTraiDltEnabled: PropTypes.bool,
|
|
129
132
|
registeredSenderIds: PropTypes.array,
|
|
133
|
+
isChannelSmsFallbackPreviewEnabled: PropTypes.bool,
|
|
130
134
|
};
|
|
131
135
|
|
|
132
136
|
SendTestMessage.defaultProps = {
|
|
@@ -136,12 +140,13 @@ SendTestMessage.defaultProps = {
|
|
|
136
140
|
searchValue: '',
|
|
137
141
|
setSearchValue: () => {}, // no-op when not provided; required by TreeSelect when showSearch is true
|
|
138
142
|
deliverySettings: {},
|
|
139
|
-
|
|
143
|
+
senderDetailsByChannel: {},
|
|
140
144
|
wecrmAccounts: [],
|
|
141
145
|
onSaveDeliverySettings: undefined,
|
|
142
146
|
isLoadingSenderDetails: false,
|
|
143
147
|
smsTraiDltEnabled: false,
|
|
144
148
|
registeredSenderIds: [],
|
|
149
|
+
isChannelSmsFallbackPreviewEnabled: false,
|
|
145
150
|
};
|
|
146
151
|
|
|
147
152
|
export default SendTestMessage;
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
TIME_FORMAT_MINUTE_PADDING_THRESHOLD,
|
|
26
26
|
TIME_FORMAT_AM,
|
|
27
27
|
TIME_FORMAT_PM,
|
|
28
|
+
MEDIA_TYPE_VIDEO,
|
|
28
29
|
} from '../constants';
|
|
29
30
|
import messages from '../messages';
|
|
30
31
|
import { RCS_BUTTON_TYPES } from '../../../v2Containers/Rcs/constants';
|
|
@@ -50,41 +51,57 @@ const RcsPreviewContent = ({
|
|
|
50
51
|
const deviceName = device === ANDROID ? ANDROID_DEVICE_NAME : IOS_DEVICE_NAME;
|
|
51
52
|
|
|
52
53
|
// Render RCS suggestions (Quick Reply, CTA, Phone Number)
|
|
53
|
-
|
|
54
|
+
// options.isCarousel: stacked bars below card body with gap (Figma), no dividers between CTAs
|
|
55
|
+
const renderRcsSuggestionsPreview = (suggestionsArg, options = {}) => {
|
|
56
|
+
const { isCarousel = false } = options;
|
|
54
57
|
const renderArray = [];
|
|
55
|
-
const
|
|
58
|
+
const suggestions = Array.isArray(suggestionsArg)
|
|
59
|
+
? suggestionsArg
|
|
60
|
+
: (content?.suggestions || []);
|
|
56
61
|
|
|
57
62
|
if (suggestions.length === 0) {
|
|
58
63
|
return null;
|
|
59
64
|
}
|
|
60
65
|
|
|
66
|
+
const ctaClass = isCarousel ? 'rcs-cta-preview rcs-cta-preview--carousel-bar' : 'rcs-cta-preview';
|
|
67
|
+
|
|
61
68
|
suggestions.forEach((suggestion) => {
|
|
62
69
|
const { type, text } = suggestion || {};
|
|
63
70
|
const suggestionKey = `${type}-${text || Math.random()}`;
|
|
64
71
|
|
|
65
|
-
if (renderArray.length > 0) {
|
|
72
|
+
if (renderArray.length > 0 && !isCarousel) {
|
|
66
73
|
renderArray.push(<CapDivider key={`divider-${suggestionKey}`} className="whatsapp-divider" />);
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
if (type === RCS_BUTTON_TYPES.QUICK_REPLY) {
|
|
70
77
|
renderArray.push(
|
|
71
|
-
<CapLabel key={suggestionKey} type="label21" className=
|
|
78
|
+
<CapLabel key={suggestionKey} type="label21" className={ctaClass}>
|
|
72
79
|
<CapIcon type="small-link" size="xs" />
|
|
73
80
|
{text}
|
|
74
81
|
</CapLabel>
|
|
75
82
|
);
|
|
76
83
|
} else if (type === RCS_BUTTON_TYPES.CTA) {
|
|
77
84
|
renderArray.push(
|
|
78
|
-
<CapLabel key={suggestionKey} type="label21" className=
|
|
85
|
+
<CapLabel key={suggestionKey} type="label21" className={ctaClass}>
|
|
79
86
|
<CapIcon type="launch" size="xs" />
|
|
80
87
|
{text}
|
|
81
88
|
</CapLabel>
|
|
82
89
|
);
|
|
83
90
|
} else if (type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
|
|
91
|
+
// Carousel: label text then phone icon (matches device-style RCS preview)
|
|
84
92
|
renderArray.push(
|
|
85
|
-
<CapLabel key={suggestionKey} type="label21" className=
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
<CapLabel key={suggestionKey} type="label21" className={ctaClass}>
|
|
94
|
+
{isCarousel ? (
|
|
95
|
+
<>
|
|
96
|
+
{text}
|
|
97
|
+
<CapIcon type="call" size="xs" className="rcs-cta-preview-phone-icon-end" />
|
|
98
|
+
</>
|
|
99
|
+
) : (
|
|
100
|
+
<>
|
|
101
|
+
<CapIcon type="call" size="xs" />
|
|
102
|
+
{text}
|
|
103
|
+
</>
|
|
104
|
+
)}
|
|
88
105
|
</CapLabel>
|
|
89
106
|
);
|
|
90
107
|
}
|
|
@@ -93,6 +110,108 @@ const RcsPreviewContent = ({
|
|
|
93
110
|
return renderArray?.length > 0 ? renderArray : null;
|
|
94
111
|
};
|
|
95
112
|
|
|
113
|
+
// Carousel media box: aspect matches RCS editor card size (SHORT/MEDIUM × SMALL/MEDIUM).
|
|
114
|
+
const getCarouselMediaAspectStyle = (isVideo) => {
|
|
115
|
+
const d = content?.carouselPreviewDimensions;
|
|
116
|
+
if (!d) return undefined;
|
|
117
|
+
if (isVideo) {
|
|
118
|
+
const { videoThumbWidth, videoThumbHeight } = d;
|
|
119
|
+
if (!videoThumbWidth || !videoThumbHeight) return undefined;
|
|
120
|
+
return { aspectRatio: `${videoThumbWidth} / ${videoThumbHeight}` };
|
|
121
|
+
}
|
|
122
|
+
const { imageWidth, imageHeight } = d;
|
|
123
|
+
if (!imageWidth || !imageHeight) return undefined;
|
|
124
|
+
return { aspectRatio: `${imageWidth} / ${imageHeight}` };
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Carousel preview (WhatsApp-style static horizontal scroll).
|
|
128
|
+
// Caller passes a non-empty array only (see hasCarousel); avoids duplicate guards that Sonar
|
|
129
|
+
// flagged as partially covered because inner checks were unreachable when hasCarousel was true.
|
|
130
|
+
const renderCarouselPreviewContent = (carouselCards) => (
|
|
131
|
+
<CapRow className="msg-container-carousel">
|
|
132
|
+
<CapRow className="scroll-container" type="flex" noWrap>
|
|
133
|
+
{carouselCards.map((card, idx) => {
|
|
134
|
+
const key = `rcs-carousel-${idx}-${card?.bodyText || card?.imageSrc || card?.videoPreviewImg || ''}`;
|
|
135
|
+
const isVideo =
|
|
136
|
+
(card?.mediaType || '').toLowerCase() === String(MEDIA_TYPE_VIDEO).toLowerCase();
|
|
137
|
+
const cardSuggestions = Array.isArray(card?.suggestions) ? card.suggestions : [];
|
|
138
|
+
const suggestionsNode = cardSuggestions.length > 0
|
|
139
|
+
? renderRcsSuggestionsPreview(cardSuggestions, { isCarousel: true })
|
|
140
|
+
: null;
|
|
141
|
+
const titleTrimmed = String(card?.title ?? '').trim();
|
|
142
|
+
const bodyTrimmed = String(card?.bodyText ?? '').trim();
|
|
143
|
+
const titleShown = titleTrimmed || formatMessage(messages.rcsCarouselPreviewTitlePlaceholder);
|
|
144
|
+
const bodyShown = bodyTrimmed || formatMessage(messages.rcsCarouselPreviewBodyPlaceholder);
|
|
145
|
+
return (
|
|
146
|
+
<CapRow
|
|
147
|
+
key={key}
|
|
148
|
+
className="message-pop align-left message-pop-carousel"
|
|
149
|
+
>
|
|
150
|
+
<CapRow className="whatsapp-content">
|
|
151
|
+
{!isVideo && (
|
|
152
|
+
<CapRow
|
|
153
|
+
className="whatsapp-image rcs-carousel-media-wrap"
|
|
154
|
+
style={getCarouselMediaAspectStyle(false)}
|
|
155
|
+
>
|
|
156
|
+
<CapImage
|
|
157
|
+
src={card?.imageSrc ? card.imageSrc : rcsImageEmptyPreview}
|
|
158
|
+
className="rcs-carousel-img"
|
|
159
|
+
alt={formatMessage(messages.previewGenerated)}
|
|
160
|
+
/>
|
|
161
|
+
</CapRow>
|
|
162
|
+
)}
|
|
163
|
+
{isVideo && (
|
|
164
|
+
<CapTooltip
|
|
165
|
+
title={formatMessage(messages.videoPreviewTooltip)}
|
|
166
|
+
wrapperClassName="rcs-carousel-video-tooltip-wrapper"
|
|
167
|
+
triggerClassName="rcs-carousel-video-tooltip-trigger"
|
|
168
|
+
>
|
|
169
|
+
<CapRow className="video-preview">
|
|
170
|
+
<CapRow
|
|
171
|
+
className="whatsapp-image rcs-carousel-media-wrap"
|
|
172
|
+
style={getCarouselMediaAspectStyle(true)}
|
|
173
|
+
>
|
|
174
|
+
<CapImage
|
|
175
|
+
src={card?.videoPreviewImg ? card.videoPreviewImg : rcsVideoEmptyPreview}
|
|
176
|
+
className="rcs-carousel-img"
|
|
177
|
+
alt={formatMessage(messages.previewGenerated)}
|
|
178
|
+
/>
|
|
179
|
+
</CapRow>
|
|
180
|
+
<CapRow className="icon-position">
|
|
181
|
+
<CapImage className="video-icon" src={videoPlay} alt="Play" />
|
|
182
|
+
</CapRow>
|
|
183
|
+
</CapRow>
|
|
184
|
+
</CapTooltip>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
<CapRow className="carousel-content">
|
|
188
|
+
<CapLabel
|
|
189
|
+
type={card.titleLabelType || 'label1'}
|
|
190
|
+
className={`carousel-title${titleTrimmed ? '' : ' rcs-carousel-field-placeholder'}`}
|
|
191
|
+
>
|
|
192
|
+
{titleShown}
|
|
193
|
+
</CapLabel>
|
|
194
|
+
<CapLabel
|
|
195
|
+
type={card.bodyLabelType || 'label2'}
|
|
196
|
+
className={`carousel-message${bodyTrimmed ? '' : ' rcs-carousel-field-placeholder'}`}
|
|
197
|
+
>
|
|
198
|
+
{bodyShown}
|
|
199
|
+
</CapLabel>
|
|
200
|
+
</CapRow>
|
|
201
|
+
|
|
202
|
+
{!!suggestionsNode && (
|
|
203
|
+
<CapRow className="rcs-carousel-cta-stack" gutter={0}>
|
|
204
|
+
{suggestionsNode}
|
|
205
|
+
</CapRow>
|
|
206
|
+
)}
|
|
207
|
+
</CapRow>
|
|
208
|
+
</CapRow>
|
|
209
|
+
);
|
|
210
|
+
})}
|
|
211
|
+
</CapRow>
|
|
212
|
+
</CapRow>
|
|
213
|
+
);
|
|
214
|
+
|
|
96
215
|
// Render text preview content
|
|
97
216
|
const renderTextPreviewContent = () => {
|
|
98
217
|
const {
|
|
@@ -226,11 +345,39 @@ const RcsPreviewContent = ({
|
|
|
226
345
|
const timestamp = `${displayHours}:${displayMinutes} ${ampm}`;
|
|
227
346
|
|
|
228
347
|
// Render normal RCS preview (same structure as SMS up to sms-message-container)
|
|
348
|
+
const carouselCardsNormalized = Array.isArray(content?.carouselData) ? content?.carouselData : [];
|
|
349
|
+
const hasCarousel = carouselCardsNormalized?.length > 0;
|
|
229
350
|
const hasMedia = !!(content?.rcsImageSrc || content?.rcsVideoSrc);
|
|
230
|
-
|
|
351
|
+
let contentToRender;
|
|
352
|
+
if (hasCarousel) {
|
|
353
|
+
contentToRender = renderCarouselPreviewContent(carouselCardsNormalized);
|
|
354
|
+
} else if (hasMedia) {
|
|
355
|
+
contentToRender = renderMediaPreviewContent();
|
|
356
|
+
} else {
|
|
357
|
+
contentToRender = renderTextPreviewContent();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const carouselPanelStyle = hasCarousel ? {
|
|
361
|
+
position: 'absolute',
|
|
362
|
+
left: '50%',
|
|
363
|
+
transform: 'translateX(-50%)',
|
|
364
|
+
width: '100%',
|
|
365
|
+
maxWidth: '26.786rem',
|
|
366
|
+
boxSizing: 'border-box',
|
|
367
|
+
top: device === ANDROID ? '3.1rem' : '4.143rem',
|
|
368
|
+
height: device === ANDROID ? 'calc(100% - 3.8rem)' : device === IOS ? 'calc(100% - 5rem)' : 'calc(100% - 4.5rem)',
|
|
369
|
+
display: 'flex',
|
|
370
|
+
flexDirection: 'column',
|
|
371
|
+
paddingTop: '5.857rem',
|
|
372
|
+
paddingBottom: '1.142rem',
|
|
373
|
+
} : undefined;
|
|
231
374
|
|
|
232
375
|
return (
|
|
233
|
-
<CapRow
|
|
376
|
+
<CapRow
|
|
377
|
+
type='flex' align='middle' justify='center'
|
|
378
|
+
className={`sms-device-container rcs-device-container ${hasCarousel ? 'rcs-device-container-carousel' : ''}`}
|
|
379
|
+
style={{ position: 'relative' }}
|
|
380
|
+
>
|
|
234
381
|
{/* Device Background Image */}
|
|
235
382
|
<CapImage
|
|
236
383
|
className="sms-device-image"
|
|
@@ -238,8 +385,10 @@ const RcsPreviewContent = ({
|
|
|
238
385
|
alt={`${deviceName} Device`}
|
|
239
386
|
/>
|
|
240
387
|
|
|
241
|
-
{/* Content Overlay */}
|
|
242
|
-
<CapRow
|
|
388
|
+
{/* Content Overlay - nav bar + date separator always; message content only for non-carousel */}
|
|
389
|
+
<CapRow
|
|
390
|
+
className={`sms-content-overlay sms-content-overlay-${device} sms-preview sms-${device} rcs-content-overlay`}
|
|
391
|
+
>
|
|
243
392
|
{/* Navigation Bar - Same as SMS */}
|
|
244
393
|
<CapRow type='flex' className={`sms-navigation-bar ${!showHeader ? 'sms-navigation-bar-no-header' : ''}`}>
|
|
245
394
|
<CapIcon type="chevron-left" className="sms-back-arrow" />
|
|
@@ -254,24 +403,41 @@ const RcsPreviewContent = ({
|
|
|
254
403
|
<FormattedMessage {...messages.timeAgo} defaultMessage="Today" />
|
|
255
404
|
</CapRow>
|
|
256
405
|
|
|
257
|
-
{/* Message Container -
|
|
258
|
-
|
|
259
|
-
<CapRow
|
|
260
|
-
<CapRow useLegacy className="sms-message-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
406
|
+
{/* Message Container - non-carousel only; carousel is rendered outside this overflow-x:hidden ancestor */}
|
|
407
|
+
{!hasCarousel && (
|
|
408
|
+
<CapRow className="sms-message-container">
|
|
409
|
+
<CapRow useLegacy className="sms-message-bubble">
|
|
410
|
+
<CapRow useLegacy className="sms-message-text">
|
|
411
|
+
<CapRow useLegacy className="message-pop sms">
|
|
412
|
+
{contentToRender}
|
|
413
|
+
</CapRow>
|
|
264
414
|
</CapRow>
|
|
265
415
|
</CapRow>
|
|
416
|
+
<CapRow useLegacy className="sms-timestamp">
|
|
417
|
+
{timestamp}
|
|
418
|
+
</CapRow>
|
|
266
419
|
</CapRow>
|
|
267
|
-
|
|
420
|
+
)}
|
|
421
|
+
</CapRow>
|
|
422
|
+
|
|
423
|
+
{/* Carousel panel: sibling of overlay, no overflow-x:hidden ancestor → scroll-container
|
|
424
|
+
can receive horizontal scroll events. Positioned absolutely over the device image. */}
|
|
425
|
+
{hasCarousel && (
|
|
426
|
+
<CapRow
|
|
427
|
+
className={`rcs-carousel-panel rcs-carousel-panel-${device}`}
|
|
428
|
+
style={carouselPanelStyle}
|
|
429
|
+
>
|
|
430
|
+
{renderCarouselPreviewContent(carouselCardsNormalized)}
|
|
431
|
+
<CapRow useLegacy className="sms-timestamp rcs-carousel-timestamp">
|
|
268
432
|
{timestamp}
|
|
269
433
|
</CapRow>
|
|
270
434
|
</CapRow>
|
|
271
|
-
|
|
435
|
+
)}
|
|
272
436
|
</CapRow>
|
|
273
|
-
)
|
|
274
|
-
}
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
|
|
275
441
|
|
|
276
442
|
RcsPreviewContent.propTypes = {
|
|
277
443
|
content: PropTypes.shape({
|
|
@@ -282,6 +448,18 @@ RcsPreviewContent.propTypes = {
|
|
|
282
448
|
rcsImageSrc: PropTypes.string,
|
|
283
449
|
rcsVideoSrc: PropTypes.string,
|
|
284
450
|
rcsThumbnailSrc: PropTypes.string,
|
|
451
|
+
carouselData: PropTypes.arrayOf(
|
|
452
|
+
PropTypes.shape({
|
|
453
|
+
titleLabelType: PropTypes.string,
|
|
454
|
+
bodyLabelType: PropTypes.string,
|
|
455
|
+
})
|
|
456
|
+
),
|
|
457
|
+
carouselPreviewDimensions: PropTypes.shape({
|
|
458
|
+
imageWidth: PropTypes.number,
|
|
459
|
+
imageHeight: PropTypes.number,
|
|
460
|
+
videoThumbWidth: PropTypes.number,
|
|
461
|
+
videoThumbHeight: PropTypes.number,
|
|
462
|
+
}),
|
|
285
463
|
suggestions: PropTypes.arrayOf(
|
|
286
464
|
PropTypes.shape({
|
|
287
465
|
type: PropTypes.string,
|