@capillarytech/creatives-library 9.0.29 → 9.0.30
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 +29 -0
- package/global-styles.js +1 -1
- package/package.json +1 -1
- package/services/tests/api.test.js +35 -20
- package/utils/commonUtils.js +22 -1
- package/utils/rcsPayloadUtils.js +102 -0
- package/utils/templateVarUtils.js +198 -0
- package/utils/tests/rcsPayloadUtils.test.js +295 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +21 -1
- package/v2Components/CapActionButton/index.js +261 -144
- package/v2Components/CapActionButton/index.scss +245 -14
- package/v2Components/CapActionButton/messages.js +32 -3
- package/v2Components/CapActionButton/tests/index.test.js +74 -19
- package/v2Components/CapImageUpload/index.js +2 -2
- package/v2Components/CapTagList/index.js +9 -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 +58 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +186 -22
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +381 -80
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +35 -2
- package/v2Components/CommonTestAndPreview/index.js +770 -231
- 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 +412 -257
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +337 -63
- 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 +108 -15
- 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/tests/utils.test.js +151 -0
- package/v2Components/CommonTestAndPreview/utils.js +84 -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 +471 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +45 -25
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +147 -32
- 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 +27 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +37 -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 +77 -0
- package/v2Containers/CreativesContainer/index.js +322 -106
- package/v2Containers/CreativesContainer/index.scss +102 -1
- package/v2Containers/CreativesContainer/tests/CreativesSlideBoxWrapper.test.js +58 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.smsDltPreview.test.js +73 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +37 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +103 -34
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +193 -15
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +88 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +549 -57
- 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/carouselUtils.js +224 -0
- package/v2Containers/Rcs/components/CarouselCard.js +317 -0
- package/v2Containers/Rcs/components/CarouselCardButtons.js +113 -0
- package/v2Containers/Rcs/components/CarouselCardMedia.js +136 -0
- package/v2Containers/Rcs/components/CarouselCharacterCount.js +31 -0
- package/v2Containers/Rcs/components/CarouselDimensionSelection.js +80 -0
- package/v2Containers/Rcs/constants.js +132 -16
- package/v2Containers/Rcs/index.js +1822 -946
- package/v2Containers/Rcs/index.scss +443 -8
- package/v2Containers/Rcs/messages.js +45 -22
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +494 -0
- package/v2Containers/Rcs/tests/CarouselCard.test.js +464 -0
- package/v2Containers/Rcs/tests/CarouselCardButtons.test.js +211 -0
- package/v2Containers/Rcs/tests/CarouselCardMedia.test.js +160 -0
- package/v2Containers/Rcs/tests/CarouselCharacterCount.test.js +50 -0
- package/v2Containers/Rcs/tests/CarouselDimensionSelection.test.js +119 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +74440 -39387
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +10 -74
- package/v2Containers/Rcs/tests/carouselUtils.test.js +916 -0
- package/v2Containers/Rcs/tests/index.test.js +275 -40
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +912 -0
- package/v2Containers/Rcs/tests/utils.test.js +682 -30
- package/v2Containers/Rcs/utils.js +514 -12
- 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 +5 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +196 -12
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +131 -59
- package/v2Containers/Templates/sagas.js +57 -13
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +129 -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 +88 -25
- 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
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import isEmpty from 'lodash/isEmpty';
|
|
2
|
+
import get from 'lodash/get';
|
|
3
|
+
import { extractRegisteredSenderIdsFromSmsFallbackRecord } from '../../utils/commonUtils';
|
|
4
|
+
import { isTraiDLTEnable } from '../../utils/common';
|
|
5
|
+
import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
6
|
+
import {
|
|
7
|
+
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
8
|
+
RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX,
|
|
9
|
+
REGEX_SPECIAL_CHARS_ESCAPE_PATTERN,
|
|
10
|
+
RCS_MUSTACHE_WRAPPED_REGEX,
|
|
11
|
+
} from './constants';
|
|
12
|
+
|
|
13
|
+
/** RCS card `cardVarMapped` / `rcsCardVarMapped` only (SMS fallback slot keys stay on `smsFallBackContent`). */
|
|
14
|
+
export function pickRcsCardVarMappedEntries(record) {
|
|
15
|
+
if (record == null || typeof record !== 'object' || Array.isArray(record)) return {};
|
|
16
|
+
return Object.fromEntries(
|
|
17
|
+
Object.entries(record).filter(([entryKey]) => {
|
|
18
|
+
const key = String(entryKey);
|
|
19
|
+
return (
|
|
20
|
+
RCS_NUMERIC_VAR_NAME_REGEX.test(key)
|
|
21
|
+
|| RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX.test(key)
|
|
22
|
+
);
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Nested `versions…smsFallBackContent` and root `smsFallBackContent` (CreativesContainer mirror)
|
|
29
|
+
* can diverge. Merge explicitly (avoid `get(..., {}) || root` — `{}` is truthy and blocks fallback).
|
|
30
|
+
*
|
|
31
|
+
* When both exist, **nested wins on overlap**: the canonical RCS path is what `createPayload` / submit
|
|
32
|
+
* update; the root mirror can lag behind campaign parent state and would otherwise keep old
|
|
33
|
+
* `message` / `rcsSmsFallbackVarMapped` in hydration and approval payloads.
|
|
34
|
+
*/
|
|
35
|
+
export function mergeRcsSmsFallBackContentFromDetails(details) {
|
|
36
|
+
if (!details || typeof details !== 'object') return {};
|
|
37
|
+
const nestedSmsFallback = details?.versions?.base?.content?.RCS?.smsFallBackContent;
|
|
38
|
+
const rootSmsFallback = details?.smsFallBackContent;
|
|
39
|
+
const nestedRecord =
|
|
40
|
+
nestedSmsFallback != null && typeof nestedSmsFallback === 'object' ? nestedSmsFallback : {};
|
|
41
|
+
const rootRecord =
|
|
42
|
+
rootSmsFallback != null && typeof rootSmsFallback === 'object' ? rootSmsFallback : {};
|
|
43
|
+
return { ...rootRecord, ...nestedRecord };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Merge SMS fallback slot maps from API/templateData (`apiShape`) and editor state (`localShape`).
|
|
48
|
+
* Spreading `{ ...api, ...local }` lets `rcsSmsFallbackVarMapped: {}` on local wipe a populated API map,
|
|
49
|
+
* which keeps Done disabled in DLT campaigns until fixed.
|
|
50
|
+
*/
|
|
51
|
+
export function mergeRcsSmsFallbackVarMapLayers(apiShape = {}, localShape = {}) {
|
|
52
|
+
const readMap = (shape) => {
|
|
53
|
+
if (!shape || typeof shape !== 'object') return {};
|
|
54
|
+
const camel = shape.rcsSmsFallbackVarMapped;
|
|
55
|
+
const kebab = shape['rcs-sms-fallback-var-mapped'];
|
|
56
|
+
const src =
|
|
57
|
+
camel && typeof camel === 'object'
|
|
58
|
+
? camel
|
|
59
|
+
: kebab && typeof kebab === 'object'
|
|
60
|
+
? kebab
|
|
61
|
+
: {};
|
|
62
|
+
return { ...src };
|
|
63
|
+
};
|
|
64
|
+
return { ...readMap(apiShape), ...readMap(localShape) };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* First non-empty trimmed string for SMS fallback body used in DLT slot checks / payload `templateContent`.
|
|
69
|
+
* Prefer **raw** template fields before resolved `message` so `{#…#}` / `{{…}}` tokens are not lost
|
|
70
|
+
* when `message` is consumer-resolved text (campaigns).
|
|
71
|
+
*/
|
|
72
|
+
/**
|
|
73
|
+
* Whether an SMS fallback object carries template body, name, or var-map data (same rule as Rcs `createPayload`).
|
|
74
|
+
*/
|
|
75
|
+
export function hasMeaningfulSmsFallbackShape(smsFallback) {
|
|
76
|
+
return !!(
|
|
77
|
+
smsFallback
|
|
78
|
+
&& (
|
|
79
|
+
String(
|
|
80
|
+
smsFallback.content
|
|
81
|
+
|| smsFallback.templateContent
|
|
82
|
+
|| smsFallback.message
|
|
83
|
+
|| smsFallback.smsContent
|
|
84
|
+
|| smsFallback.smsTemplateContent
|
|
85
|
+
|| '',
|
|
86
|
+
).trim() !== ''
|
|
87
|
+
|| String(smsFallback.templateName || smsFallback.smsTemplateName || '').trim() !== ''
|
|
88
|
+
|| (smsFallback.rcsSmsFallbackVarMapped && Object.keys(smsFallback.rcsSmsFallbackVarMapped).length > 0)
|
|
89
|
+
|| (smsFallback[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
90
|
+
&& Object.keys(smsFallback[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0)
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Library `templateData`: merged root + nested `smsFallBackContent` (nested wins), matching `createPayload`.
|
|
97
|
+
*/
|
|
98
|
+
export function getLibrarySmsFallbackApiBaselineFromTemplateData(templateData) {
|
|
99
|
+
const smsFromTemplateRoot = get(
|
|
100
|
+
templateData,
|
|
101
|
+
'versions.base.content.RCS.smsFallBackContent',
|
|
102
|
+
);
|
|
103
|
+
return {
|
|
104
|
+
...(templateData?.smsFallBackContent && typeof templateData.smsFallBackContent === 'object'
|
|
105
|
+
? templateData.smsFallBackContent
|
|
106
|
+
: {}),
|
|
107
|
+
...(smsFromTemplateRoot && typeof smsFromTemplateRoot === 'object'
|
|
108
|
+
? smsFromTemplateRoot
|
|
109
|
+
: {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { extractRegisteredSenderIdsFromSmsFallbackRecord } from '../../utils/commonUtils';
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Library/campaign: merge API baseline (`templateData`) with local editor state (`smsFallbackData`).
|
|
117
|
+
* Full mode has no API baseline to merge against — Send-for-approval reads editor state as-is.
|
|
118
|
+
* Shared by `createPayload`'s payload merge and Done/slot validation so both read the same merge
|
|
119
|
+
* and can't drift apart.
|
|
120
|
+
*/
|
|
121
|
+
export function mergeSmsFallbackForLibrary(templateData, smsFallbackData, isFullMode) {
|
|
122
|
+
if (isFullMode) {
|
|
123
|
+
return smsFallbackData || {};
|
|
124
|
+
}
|
|
125
|
+
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
126
|
+
const local =
|
|
127
|
+
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
128
|
+
return {
|
|
129
|
+
...smsFromApiShape,
|
|
130
|
+
...local,
|
|
131
|
+
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Build the `smsFallBackContent` object for the RCS template save/edit payload, or `undefined`
|
|
137
|
+
* when there is no meaningful SMS fallback to persist. Mirrors `createPayload`'s two shapes:
|
|
138
|
+
* full mode (Send for approval) only carries the fields cap-campaigns-v2's
|
|
139
|
+
* `normalizeRcsMessageContentForApi` reads (`message` + `templateConfigs`); library/campaign mode
|
|
140
|
+
* also stores template name/content/var-map/sender-ids so reopening the editor can rehydrate.
|
|
141
|
+
*/
|
|
142
|
+
export function buildSmsFallBackContentForPayload({
|
|
143
|
+
smsFallbackData,
|
|
144
|
+
templateData,
|
|
145
|
+
isFullMode,
|
|
146
|
+
smsRegister,
|
|
147
|
+
}) {
|
|
148
|
+
const smsFallbackMerged = mergeSmsFallbackForLibrary(templateData, smsFallbackData, isFullMode);
|
|
149
|
+
const smsFallbackForPayload = isFullMode
|
|
150
|
+
? (hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null)
|
|
151
|
+
: (() => {
|
|
152
|
+
const mapped = {
|
|
153
|
+
templateName:
|
|
154
|
+
smsFallbackMerged.templateName
|
|
155
|
+
|| smsFallbackMerged.smsTemplateName
|
|
156
|
+
|| '',
|
|
157
|
+
// Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
|
|
158
|
+
content:
|
|
159
|
+
smsFallbackMerged.content
|
|
160
|
+
|| smsFallbackMerged.smsContent
|
|
161
|
+
|| smsFallbackMerged.smsTemplateContent
|
|
162
|
+
|| smsFallbackMerged.message
|
|
163
|
+
|| '',
|
|
164
|
+
templateContent:
|
|
165
|
+
pickFirstSmsFallbackTemplateString(smsFallbackMerged)
|
|
166
|
+
|| '',
|
|
167
|
+
...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
|
|
168
|
+
&& { unicodeValidity: smsFallbackMerged.unicodeValidity }),
|
|
169
|
+
...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
170
|
+
&& Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
171
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
172
|
+
smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
173
|
+
}),
|
|
174
|
+
};
|
|
175
|
+
return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
|
|
176
|
+
})();
|
|
177
|
+
|
|
178
|
+
if (!smsFallbackForPayload) return undefined;
|
|
179
|
+
|
|
180
|
+
const smsBodyText =
|
|
181
|
+
smsFallbackForPayload.content
|
|
182
|
+
|| smsFallbackForPayload.templateContent
|
|
183
|
+
|| smsFallbackForPayload.message
|
|
184
|
+
|| smsFallbackForPayload.smsContent
|
|
185
|
+
|| '';
|
|
186
|
+
/**
|
|
187
|
+
* Campaigns `getTraiSenderIds` / Iris read `smsFallBackContent.templateConfigs.registeredSenderIds`.
|
|
188
|
+
* Library `smsFallbackForPayload` omits ids — use merged state (`smsFallbackMerged`) like test preview.
|
|
189
|
+
*/
|
|
190
|
+
const mergedFallback = smsFallbackMerged || {};
|
|
191
|
+
const tcSibling = mergedFallback.templateConfigs && typeof mergedFallback.templateConfigs === 'object'
|
|
192
|
+
? mergedFallback.templateConfigs
|
|
193
|
+
: {};
|
|
194
|
+
const smsFallbackTemplateId =
|
|
195
|
+
(mergedFallback.smsTemplateId != null && String(mergedFallback.smsTemplateId).trim() !== ''
|
|
196
|
+
? String(mergedFallback.smsTemplateId)
|
|
197
|
+
: '')
|
|
198
|
+
|| (tcSibling.templateId != null && String(tcSibling.templateId).trim() !== ''
|
|
199
|
+
? String(tcSibling.templateId)
|
|
200
|
+
: '');
|
|
201
|
+
const smsFallbackTemplateStr =
|
|
202
|
+
pickFirstSmsFallbackTemplateString(mergedFallback)
|
|
203
|
+
|| (typeof mergedFallback.templateContent === 'string' ? mergedFallback.templateContent : '')
|
|
204
|
+
|| (typeof tcSibling.template === 'string' ? tcSibling.template : '')
|
|
205
|
+
|| '';
|
|
206
|
+
const smsFallbackTemplateName =
|
|
207
|
+
mergedFallback.templateName
|
|
208
|
+
|| mergedFallback.smsTemplateName
|
|
209
|
+
|| tcSibling.templateName
|
|
210
|
+
|| tcSibling.name
|
|
211
|
+
|| '';
|
|
212
|
+
const registeredSenderIdsForPayload = Array.isArray(mergedFallback.registeredSenderIds)
|
|
213
|
+
? mergedFallback.registeredSenderIds
|
|
214
|
+
: Array.isArray(tcSibling.registeredSenderIds)
|
|
215
|
+
? tcSibling.registeredSenderIds
|
|
216
|
+
: Array.isArray(tcSibling.header)
|
|
217
|
+
? tcSibling.header
|
|
218
|
+
: null;
|
|
219
|
+
const hasRegisteredSenderIds = Array.isArray(registeredSenderIdsForPayload);
|
|
220
|
+
const smsFallbackTemplateConfigs =
|
|
221
|
+
smsFallbackTemplateId || hasRegisteredSenderIds
|
|
222
|
+
? {
|
|
223
|
+
...(smsFallbackTemplateId && { templateId: smsFallbackTemplateId }),
|
|
224
|
+
...(smsFallbackTemplateStr && { template: smsFallbackTemplateStr }),
|
|
225
|
+
...(smsFallbackTemplateName && {
|
|
226
|
+
templateName: smsFallbackTemplateName,
|
|
227
|
+
}),
|
|
228
|
+
...(hasRegisteredSenderIds && {
|
|
229
|
+
registeredSenderIds: registeredSenderIdsForPayload,
|
|
230
|
+
}),
|
|
231
|
+
}
|
|
232
|
+
: null;
|
|
233
|
+
const isDltCampaign = !isFullMode && isTraiDLTEnable(isFullMode, smsRegister);
|
|
234
|
+
|
|
235
|
+
const smsFallBackContent = isFullMode
|
|
236
|
+
? {
|
|
237
|
+
smsTemplateName: smsFallbackForPayload.templateName || '',
|
|
238
|
+
smsContent: smsBodyText,
|
|
239
|
+
// cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
|
|
240
|
+
message: smsBodyText,
|
|
241
|
+
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
242
|
+
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
243
|
+
}),
|
|
244
|
+
...(smsFallbackForPayload.rcsSmsFallbackVarMapped
|
|
245
|
+
&& Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
|
|
246
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
|
|
247
|
+
}),
|
|
248
|
+
...(smsFallbackTemplateConfigs && {
|
|
249
|
+
templateConfigs: smsFallbackTemplateConfigs,
|
|
250
|
+
}),
|
|
251
|
+
}
|
|
252
|
+
: {
|
|
253
|
+
// Round-trip storage: full shape so reopening the editor restores template
|
|
254
|
+
// name, sender IDs, and var mappings. normalizeRcsMessageContentForApi
|
|
255
|
+
// (called in CreativesContainer getCreativesData) strips this to
|
|
256
|
+
// { message, templateConfigs } before the API call — the extra fields here
|
|
257
|
+
// are only used for re-hydration, never sent to the API.
|
|
258
|
+
message: smsBodyText,
|
|
259
|
+
...(smsFallbackForPayload.templateName && {
|
|
260
|
+
smsTemplateName: smsFallbackForPayload.templateName,
|
|
261
|
+
}),
|
|
262
|
+
...(smsFallbackForPayload.templateContent && {
|
|
263
|
+
templateContent: smsFallbackForPayload.templateContent,
|
|
264
|
+
}),
|
|
265
|
+
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
266
|
+
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
267
|
+
}),
|
|
268
|
+
...(Array.isArray(registeredSenderIdsForPayload) && {
|
|
269
|
+
registeredSenderIds: registeredSenderIdsForPayload,
|
|
270
|
+
}),
|
|
271
|
+
...(smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
272
|
+
&& Object.keys(smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
273
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
274
|
+
smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
275
|
+
}),
|
|
276
|
+
...(isDltCampaign && smsFallbackTemplateConfigs && {
|
|
277
|
+
templateConfigs: smsFallbackTemplateConfigs,
|
|
278
|
+
}),
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
return { smsFallBackContent };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Resolve SMS-fallback hydration state from RCS template `details` — shared by the carousel and
|
|
286
|
+
* non-carousel hydration branches in Rcs/index.js so template shape doesn't fork the fallback-message
|
|
287
|
+
* / unicode / var-map / sender-id resolution logic. `hydrationKey` is a stable fingerprint used to
|
|
288
|
+
* skip a redundant `setSmsFallbackData` when re-hydrating with unchanged data.
|
|
289
|
+
*/
|
|
290
|
+
export function resolveSmsFallbackHydrationFromDetails(details) {
|
|
291
|
+
const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
|
|
292
|
+
const base = get(smsFallbackContent, 'versions.base', {});
|
|
293
|
+
const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
|
|
294
|
+
const smsEditor = base['sms-editor'];
|
|
295
|
+
const fromNested = Array.isArray(updatedEditor)
|
|
296
|
+
? updatedEditor.join('')
|
|
297
|
+
: (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
|
|
298
|
+
const fallbackMessage = smsFallbackContent.smsContent
|
|
299
|
+
|| smsFallbackContent.smsTemplateContent
|
|
300
|
+
|| smsFallbackContent.message
|
|
301
|
+
|| fromNested
|
|
302
|
+
|| '';
|
|
303
|
+
const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
|
|
304
|
+
const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
|
|
305
|
+
const templateConfigsRecord =
|
|
306
|
+
smsFallbackContent.templateConfigs && typeof smsFallbackContent.templateConfigs === 'object'
|
|
307
|
+
? smsFallbackContent.templateConfigs
|
|
308
|
+
: {};
|
|
309
|
+
const smsFallbackTemplateName =
|
|
310
|
+
smsFallbackContent.templateName
|
|
311
|
+
|| smsFallbackContent.smsTemplateName
|
|
312
|
+
|| templateConfigsRecord.templateName
|
|
313
|
+
|| templateConfigsRecord.name
|
|
314
|
+
|| '';
|
|
315
|
+
const hasFallbackPayload =
|
|
316
|
+
smsFallbackContent
|
|
317
|
+
&& Object.keys(smsFallbackContent).length > 0
|
|
318
|
+
&& (
|
|
319
|
+
!!smsFallbackTemplateName
|
|
320
|
+
|| !!fallbackMessage
|
|
321
|
+
|| hasVarMapped
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
if (!hasFallbackPayload) {
|
|
325
|
+
return { hasFallbackPayload: false, nextSmsState: null, hydrationKey: '__EMPTY__' };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
|
|
329
|
+
// eslint-disable-next-line no-console
|
|
330
|
+
console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
|
|
331
|
+
}
|
|
332
|
+
const unicodeFromApi =
|
|
333
|
+
typeof smsFallbackContent.unicodeValidity === 'boolean'
|
|
334
|
+
? smsFallbackContent.unicodeValidity
|
|
335
|
+
: (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
|
|
336
|
+
const registeredSenderIds = extractRegisteredSenderIdsFromSmsFallbackRecord(smsFallbackContent);
|
|
337
|
+
const nextSmsState = {
|
|
338
|
+
templateName: smsFallbackTemplateName,
|
|
339
|
+
content: fallbackMessage,
|
|
340
|
+
templateContent: fallbackMessage,
|
|
341
|
+
unicodeValidity: unicodeFromApi,
|
|
342
|
+
...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
|
|
343
|
+
...(Array.isArray(registeredSenderIds) && registeredSenderIds.length > 0
|
|
344
|
+
? { registeredSenderIds }
|
|
345
|
+
: {}),
|
|
346
|
+
};
|
|
347
|
+
const hydrationKey = JSON.stringify({
|
|
348
|
+
creativeKey: details._id || details.name || details.creativeName || '',
|
|
349
|
+
templateName: nextSmsState.templateName,
|
|
350
|
+
content: nextSmsState.content,
|
|
351
|
+
unicodeValidity: nextSmsState.unicodeValidity,
|
|
352
|
+
varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
|
|
353
|
+
senderIds: Array.isArray(registeredSenderIds) ? registeredSenderIds.join('') : '',
|
|
354
|
+
});
|
|
355
|
+
return { hasFallbackPayload: true, nextSmsState, hydrationKey };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Apply SMS-fallback hydration to component state — shared by the carousel and non-carousel
|
|
360
|
+
* hydration branches in Rcs/index.js so both stay in sync on the ref-guard / setSmsFallbackData logic.
|
|
361
|
+
*/
|
|
362
|
+
export function applySmsFallbackHydration({
|
|
363
|
+
details,
|
|
364
|
+
isFullMode,
|
|
365
|
+
lastSmsFallbackHydrationKeyRef,
|
|
366
|
+
setSmsFallbackData,
|
|
367
|
+
}) {
|
|
368
|
+
const smsFallback = resolveSmsFallbackHydrationFromDetails(details);
|
|
369
|
+
if (smsFallback.hasFallbackPayload) {
|
|
370
|
+
if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== smsFallback.hydrationKey) {
|
|
371
|
+
lastSmsFallbackHydrationKeyRef.current = smsFallback.hydrationKey;
|
|
372
|
+
setSmsFallbackData(smsFallback.nextSmsState);
|
|
373
|
+
}
|
|
374
|
+
} else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
|
|
375
|
+
lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
|
|
376
|
+
setSmsFallbackData(null);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function pickFirstSmsFallbackTemplateString(sms = {}) {
|
|
381
|
+
if (!sms || typeof sms !== 'object') return '';
|
|
382
|
+
const keys = [
|
|
383
|
+
'templateContent',
|
|
384
|
+
'smsTemplateContent',
|
|
385
|
+
'content',
|
|
386
|
+
'smsContent',
|
|
387
|
+
'message',
|
|
388
|
+
];
|
|
389
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
390
|
+
const fieldValue = sms[keys[i]];
|
|
391
|
+
if (fieldValue == null) continue;
|
|
392
|
+
const trimmedValue = String(fieldValue).trim();
|
|
393
|
+
if (trimmedValue) return trimmedValue;
|
|
394
|
+
}
|
|
395
|
+
return '';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Campaign reopen: payloads often keep tag/label values on numeric keys (`"1"`, `"2"`, …) while
|
|
400
|
+
* semantic keys (`user_name`, …) stay `""`. Copy non-empty slot values onto empty semantic keys so
|
|
401
|
+
* VarSegment editors prepopulate after Done → Edit without changing global resolve behavior.
|
|
402
|
+
*/
|
|
403
|
+
export function syncCardVarMappedSemanticsFromSlots(
|
|
404
|
+
cardVarMappedInput,
|
|
405
|
+
templateTitle,
|
|
406
|
+
templateDesc,
|
|
407
|
+
rcsVarRegex,
|
|
408
|
+
) {
|
|
409
|
+
const cardVarMappedSynced =
|
|
410
|
+
cardVarMappedInput != null && typeof cardVarMappedInput === 'object'
|
|
411
|
+
? { ...cardVarMappedInput }
|
|
412
|
+
: {};
|
|
413
|
+
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '').trim();
|
|
414
|
+
const templateVarTokens = [
|
|
415
|
+
...(templateTitle?.match(rcsVarRegex) ?? []),
|
|
416
|
+
...(templateDesc?.match(rcsVarRegex) ?? []),
|
|
417
|
+
];
|
|
418
|
+
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
419
|
+
const semanticVarName = getVarNameFromToken(token);
|
|
420
|
+
if (!semanticVarName) return;
|
|
421
|
+
const numericSlotKey = String(slotIndexZeroBased + 1);
|
|
422
|
+
const semanticValueTrimmed = String(cardVarMappedSynced[semanticVarName] ?? '').trim();
|
|
423
|
+
const numericSlotValueTrimmed = String(cardVarMappedSynced[numericSlotKey] ?? '').trim();
|
|
424
|
+
if (!semanticValueTrimmed && numericSlotValueTrimmed) {
|
|
425
|
+
cardVarMappedSynced[semanticVarName] = cardVarMappedSynced[numericSlotKey];
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
return cardVarMappedSynced;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Maps resolved campaign values back to `{{semanticKey}}` using `cardVarMapped` keys.
|
|
433
|
+
* Used when hydrating library/journey RCS edit from payloads without existing `{{…}}` tokens.
|
|
434
|
+
*/
|
|
435
|
+
export function getUnmappedDesc(str, mapping) {
|
|
436
|
+
if (!str) return '';
|
|
437
|
+
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
438
|
+
let result = str;
|
|
439
|
+
const replacements = [];
|
|
440
|
+
Object.entries(mapping).forEach(([key, value]) => {
|
|
441
|
+
const raw = (value ?? '').toString();
|
|
442
|
+
if (!raw || raw?.trim?.() === '') return;
|
|
443
|
+
const braced = RCS_MUSTACHE_WRAPPED_REGEX.test(raw) ? raw : `{{${raw}}}`;
|
|
444
|
+
replacements.push({ key, needle: raw });
|
|
445
|
+
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
446
|
+
});
|
|
447
|
+
const seen = new Set();
|
|
448
|
+
const uniq = replacements
|
|
449
|
+
.filter(({ key, needle }) => {
|
|
450
|
+
const id = `${key}::${needle}`;
|
|
451
|
+
if (seen.has(id)) return false;
|
|
452
|
+
seen.add(id);
|
|
453
|
+
return true;
|
|
454
|
+
})
|
|
455
|
+
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
456
|
+
|
|
457
|
+
uniq.forEach(({ key, needle }) => {
|
|
458
|
+
if (!needle) return;
|
|
459
|
+
const escaped = needle.replace(REGEX_SPECIAL_CHARS_ESCAPE_PATTERN, '\\$&');
|
|
460
|
+
const regex = new RegExp(escaped, 'g');
|
|
461
|
+
result = result.replace(regex, `{{${key}}}`);
|
|
462
|
+
});
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function hasRcsVarTokens(str, rcsVarRegex) {
|
|
467
|
+
return (str?.match(rcsVarRegex) ?? []).length > 0;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Library / journey: only run `getUnmappedDesc` when the loaded copy has no `{{…}}` tokens
|
|
472
|
+
* (e.g. fully resolved campaign text). If the API already has numeric or named slots, keep as-is.
|
|
473
|
+
*/
|
|
474
|
+
export function normalizeLibraryLoadedTitleDesc({
|
|
475
|
+
loadedTitle,
|
|
476
|
+
loadedDesc,
|
|
477
|
+
isFullMode,
|
|
478
|
+
cardVarMappedAfterHydration,
|
|
479
|
+
rcsVarRegex,
|
|
480
|
+
}) {
|
|
481
|
+
const normalizedTitle =
|
|
482
|
+
!isFullMode
|
|
483
|
+
&& !isEmpty(cardVarMappedAfterHydration)
|
|
484
|
+
&& !hasRcsVarTokens(loadedTitle, rcsVarRegex)
|
|
485
|
+
? getUnmappedDesc(loadedTitle, cardVarMappedAfterHydration)
|
|
486
|
+
: loadedTitle;
|
|
487
|
+
const normalizedDesc =
|
|
488
|
+
!isFullMode
|
|
489
|
+
&& !isEmpty(cardVarMappedAfterHydration)
|
|
490
|
+
&& !hasRcsVarTokens(loadedDesc, rcsVarRegex)
|
|
491
|
+
? getUnmappedDesc(loadedDesc, cardVarMappedAfterHydration)
|
|
492
|
+
: loadedDesc;
|
|
493
|
+
return { normalizedTitle, normalizedDesc };
|
|
494
|
+
}
|