@capillarytech/creatives-library 9.0.15-alpha.3 → 9.0.16-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/AppRoot.js +80 -0
  2. package/app.js +5 -3
  3. package/constants/unified.js +0 -29
  4. package/entry.js +67 -1
  5. package/global-styles.js +1 -1
  6. package/mfe-exposed-components.js +2 -4
  7. package/package.json +2 -2
  8. package/services/api.js +1 -1
  9. package/services/tests/api.test.js +20 -35
  10. package/styles/containers/layout/_layoutPage.scss +8 -6
  11. package/utils/commonUtils.js +1 -19
  12. package/utils/getDataLayer.js +15 -0
  13. package/utils/gtmTrackers/gtmEvents/creativeDetails.js +2 -1
  14. package/utils/mfeDetect.js +1 -0
  15. package/v2Components/CapActionButton/constants.js +1 -8
  16. package/v2Components/CapActionButton/index.js +144 -202
  17. package/v2Components/CapActionButton/index.scss +14 -232
  18. package/v2Components/CapActionButton/messages.js +3 -19
  19. package/v2Components/CapActionButton/tests/index.test.js +18 -42
  20. package/v2Components/CapImageUpload/index.js +2 -2
  21. package/v2Components/CapTagList/index.js +0 -10
  22. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +48 -70
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  24. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -214
  25. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  26. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +9 -83
  27. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  28. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -60
  29. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  30. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +22 -202
  31. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +80 -381
  32. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +8 -155
  33. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  34. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  35. package/v2Components/CommonTestAndPreview/index.js +222 -761
  36. package/v2Components/CommonTestAndPreview/messages.js +3 -45
  37. package/v2Components/CommonTestAndPreview/sagas.js +6 -25
  38. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -1
  39. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +267 -284
  40. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  41. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  42. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  43. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
  44. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  45. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
  46. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  47. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -133
  48. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +24 -31
  49. package/v2Components/NavigationBar/index.js +9 -7
  50. package/v2Components/NavigationBar/mfeModuleHeader.config.js +16 -0
  51. package/v2Components/Pagination/_pagination.scss +2 -0
  52. package/v2Components/TemplatePreview/_templatePreview.scss +25 -45
  53. package/v2Components/TemplatePreview/index.js +32 -147
  54. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  55. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  56. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  57. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  58. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  59. package/v2Containers/Cap/constants.js +1 -0
  60. package/v2Containers/Cap/index.js +47 -21
  61. package/v2Containers/Cap/sagas.js +19 -0
  62. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +1 -4
  63. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +0 -4
  64. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  65. package/v2Containers/CreativesContainer/SlideBoxContent.js +7 -37
  66. package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -14
  67. package/v2Containers/CreativesContainer/SlideBoxHeader.js +5 -36
  68. package/v2Containers/CreativesContainer/constants.js +0 -11
  69. package/v2Containers/CreativesContainer/index.js +104 -323
  70. package/v2Containers/CreativesContainer/index.scss +1 -83
  71. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -79
  72. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  73. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  74. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +90 -333
  75. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  76. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  77. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  78. package/v2Containers/Rcs/constants.js +11 -131
  79. package/v2Containers/Rcs/index.js +829 -2682
  80. package/v2Containers/Rcs/index.scss +8 -443
  81. package/v2Containers/Rcs/messages.js +6 -45
  82. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +39409 -74176
  83. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +74 -10
  84. package/v2Containers/Rcs/tests/index.test.js +40 -151
  85. package/v2Containers/Rcs/tests/mockData.js +0 -38
  86. package/v2Containers/Rcs/tests/utils.test.js +30 -643
  87. package/v2Containers/Rcs/utils.js +12 -466
  88. package/v2Containers/Sms/Create/index.js +48 -115
  89. package/v2Containers/SmsTrai/Create/index.js +4 -9
  90. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  91. package/v2Containers/SmsTrai/Edit/index.js +169 -678
  92. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  93. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2854 -5455
  94. package/v2Containers/SmsWrapper/index.js +8 -37
  95. package/v2Containers/TagList/index.js +0 -6
  96. package/v2Containers/Templates/_templates.scss +11 -196
  97. package/v2Containers/Templates/actions.js +0 -11
  98. package/v2Containers/Templates/constants.js +0 -2
  99. package/v2Containers/Templates/index.js +60 -131
  100. package/v2Containers/Templates/sagas.js +13 -57
  101. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1015 -1060
  102. package/v2Containers/Templates/tests/sagas.test.js +16 -199
  103. package/v2Containers/TemplatesV2/TemplatesV2.style.js +10 -75
  104. package/v2Containers/TemplatesV2/index.js +43 -86
  105. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  106. package/v2Containers/Whatsapp/index.js +25 -4
  107. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  108. package/utils/rcsPayloadUtils.js +0 -92
  109. package/utils/templateVarUtils.js +0 -201
  110. package/utils/tests/rcsPayloadUtils.test.js +0 -226
  111. package/utils/tests/templateVarUtils.test.js +0 -204
  112. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  113. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  114. package/v2Components/CommonTestAndPreview/utils.js +0 -84
  115. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -91
  116. package/v2Components/SmsFallback/constants.js +0 -94
  117. package/v2Components/SmsFallback/index.js +0 -958
  118. package/v2Components/SmsFallback/index.scss +0 -266
  119. package/v2Components/SmsFallback/messages.js +0 -78
  120. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -120
  121. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  122. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  123. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  124. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -208
  125. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -309
  126. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  127. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  128. package/v2Components/TemplatePreview/constants.js +0 -2
  129. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  130. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  131. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  132. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -17
  133. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -79
  134. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  135. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  136. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  137. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -227
  138. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  139. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  140. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  141. package/v2Containers/SmsTrai/Edit/index.scss +0 -126
  142. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  143. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  144. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  145. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  146. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -1,28 +1,11 @@
1
1
  import React from 'react';
2
2
  import {CapIcon, CapImage, CapLabel, CapDivider } from '@capillarytech/cap-ui-library';
3
- import {
4
- RCS,
5
- RCS_MEDIA_TYPES,
6
- RCS_NUMERIC_VAR_NAME_REGEX,
7
- RCS_REGEX_META_CHARS_PATTERN,
8
- RCS_STRIP_MUSTACHE_DELIMITERS_REGEX,
9
- } from './constants';
3
+ import { RCS } from './constants';
10
4
  import './index.scss';
11
5
  // import { formatMessage } from '../../../utils/intl';
12
6
  import messages from './messages';
13
- import {
14
- STATUS_OPTIONS,
15
- RCS_BUTTON_TYPES,
16
- RCS_STATUSES,
17
- rcsVarRegex,
18
- rcsVarTestRegex,
19
- } from './constants';
20
- import {
21
- splitTemplateVarString,
22
- COMBINED_SMS_TEMPLATE_VAR_REGEX,
23
- isAnyTemplateVarToken,
24
- } from '../../utils/templateVarUtils';
25
- import { pickRcsCardVarMappedEntries } from './rcsLibraryHydrationUtils';
7
+ import { STATUS_OPTIONS, RCS_BUTTON_TYPES, RCS_STATUSES, RCS_MEDIA_TYPES } from './constants';
8
+
26
9
 
27
10
  export const getRcsStatusType = (status) => {
28
11
  switch (status) {
@@ -50,388 +33,6 @@ export const getTemplateStatusType = (templateStatus) => {
50
33
  }
51
34
  };
52
35
 
53
- /** Localized label for a carousel video thumbnail size (width × height in px). */
54
- export const formatRcsCarouselVideoThumbnailLabel = (formatMessage, dimensionEntry) => {
55
- if (!dimensionEntry) return '';
56
- const { width, height } = dimensionEntry;
57
- return formatMessage(messages.rcsCarouselVideoThumbnailLabel, { width, height });
58
- };
59
-
60
- /**
61
- * Global RegExp matching `{{numericVarName}}` in RCS template strings.
62
- * `numericVarName` is escaped for regex metacharacters.
63
- */
64
- export function buildRcsNumericMustachePlaceholderRegex(numericVarName) {
65
- const escaped = String(numericVarName).replace(RCS_REGEX_META_CHARS_PATTERN, '\\$&');
66
- return new RegExp(`\\{\\{${escaped}\\}\\}`, 'g');
67
- }
68
-
69
- export function normalizeCardVarMapped(rawCardVarMapped, orderedTagNames) {
70
- if (!rawCardVarMapped || typeof rawCardVarMapped !== 'object') return {};
71
- const normalizedMap = {};
72
- const templateVarNamesInOrder = Array.isArray(orderedTagNames) ? orderedTagNames : null;
73
- const hasOrderedSlots =
74
- Boolean(templateVarNamesInOrder?.length);
75
- Object.entries(rawCardVarMapped).forEach(([entryKey, entryValue]) => {
76
- const trimmedValue = entryValue == null ? '' : String(entryValue).trim();
77
- const entryKeyIsNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(entryKey));
78
- const mustacheInnerMatch = trimmedValue.match(/^\{\{([^}]+)\}\}$/);
79
- const innerFromMustache =
80
- mustacheInnerMatch?.[1] != null ? String(mustacheInnerMatch[1]).trim() : null;
81
-
82
- if (innerFromMustache !== null && entryKeyIsNumericSlot) {
83
- const slotIndexZeroBased = parseInt(String(entryKey), 10) - 1;
84
- const expectedVarForSlot =
85
- hasOrderedSlots
86
- && slotIndexZeroBased >= 0
87
- && slotIndexZeroBased < templateVarNamesInOrder.length
88
- ? templateVarNamesInOrder[slotIndexZeroBased]
89
- : null;
90
- const innerMatchesSlotToken =
91
- expectedVarForSlot != null && innerFromMustache === expectedVarForSlot;
92
- const legacyUnorderedPlaceholder = !hasOrderedSlots;
93
- /* Library: slot "1" + {{user_id_b64}} when token is user_id_b64 → empty semantic. With ordered
94
- * slots, only clear when inner matches that slot's template token; else keep (e.g. {{1}}+{{FirstName}}). */
95
- const clearNumericSlotMustacheAsUnfilled =
96
- !RCS_NUMERIC_VAR_NAME_REGEX.test(innerFromMustache)
97
- && (legacyUnorderedPlaceholder || innerMatchesSlotToken);
98
- if (clearNumericSlotMustacheAsUnfilled) {
99
- const outputKey = innerFromMustache;
100
- const existingValue = normalizedMap[outputKey];
101
- if (existingValue != null && String(existingValue).trim() !== '') {
102
- return;
103
- }
104
- normalizedMap[outputKey] = '';
105
- return;
106
- }
107
- if (RCS_NUMERIC_VAR_NAME_REGEX.test(innerFromMustache)) {
108
- const outputKey = innerFromMustache;
109
- const existingValue = normalizedMap[outputKey];
110
- if (existingValue != null && String(existingValue).trim() !== '') {
111
- return;
112
- }
113
- normalizedMap[outputKey] = '';
114
- return;
115
- }
116
- }
117
-
118
- if (innerFromMustache !== null && !entryKeyIsNumericSlot) {
119
- if (innerFromMustache === String(entryKey)) {
120
- const existingValue = normalizedMap[entryKey];
121
- if (existingValue != null && String(existingValue).trim() !== '') {
122
- return;
123
- }
124
- normalizedMap[entryKey] = '';
125
- return;
126
- }
127
- }
128
-
129
- if (entryKeyIsNumericSlot && templateVarNamesInOrder?.length) {
130
- const slotIndexZeroBased = parseInt(String(entryKey), 10) - 1;
131
- if (slotIndexZeroBased >= 0 && slotIndexZeroBased < templateVarNamesInOrder.length) {
132
- normalizedMap[templateVarNamesInOrder[slotIndexZeroBased]] = trimmedValue;
133
- return;
134
- }
135
- }
136
- normalizedMap[entryKey] = trimmedValue;
137
- });
138
- return normalizedMap;
139
- }
140
-
141
- /**
142
- * Semantic names that appear in both title and description (e.g. `{{adv}}` in header and body).
143
- * Those slots must not share one semantic `cardVarMapped` key — otherwise VarSegment inputs mirror.
144
- */
145
- export function getRcsSemanticVarNamesSpanningTitleAndDesc(
146
- templateTitle,
147
- templateDesc,
148
- rcsVarRegex,
149
- ) {
150
- const getVarNameFromToken = (token = '') => token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
151
- const titleTokens = templateTitle?.match(rcsVarRegex) ?? [];
152
- const descTokens = templateDesc?.match(rcsVarRegex) ?? [];
153
- const titleNames = new Set(titleTokens.map(getVarNameFromToken).filter(Boolean));
154
- const descNames = new Set(descTokens.map(getVarNameFromToken).filter(Boolean));
155
- const spanning = new Set();
156
- titleNames.forEach((n) => {
157
- if (descNames.has(n)) spanning.add(n);
158
- });
159
- return spanning;
160
- }
161
-
162
- /**
163
- * Rebuild `cardVarMapped` so keys match the current title/description tokens (title tokens first,
164
- * then description), in order. Values are taken from the matching key, else from legacy slot
165
- * `1`, `2`, … by index. If there are no `{{...}}` tokens, returns a shallow clone of `raw`.
166
- */
167
- export function coalesceCardVarMappedToTemplate(
168
- sourceCardVarMap,
169
- templateTitle,
170
- templateDesc,
171
- rcsVarRegex,
172
- ) {
173
- const getVarNameFromToken = (token = '') => token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
174
- const templateVarTokens = [
175
- ...(templateTitle?.match(rcsVarRegex) ?? []),
176
- ...(templateDesc?.match(rcsVarRegex) ?? []),
177
- ];
178
- const lookupSourceMap =
179
- sourceCardVarMap != null && typeof sourceCardVarMap === 'object' ? sourceCardVarMap : {};
180
- if (!templateVarTokens.length) {
181
- return { ...lookupSourceMap };
182
- }
183
- const semanticNamesSpanningTitleAndDesc = getRcsSemanticVarNamesSpanningTitleAndDesc(
184
- templateTitle,
185
- templateDesc,
186
- rcsVarRegex,
187
- );
188
- const coalescedMap = { ...lookupSourceMap };
189
- const seenSemanticVarNames = new Set();
190
- templateVarTokens.forEach((token, slotIndexZeroBased) => {
191
- const semanticVarName = getVarNameFromToken(token);
192
- if (!semanticVarName) return;
193
- const numericSlotKey = String(slotIndexZeroBased + 1);
194
- const isRepeatOfSemanticName = seenSemanticVarNames.has(semanticVarName);
195
- const skipSharedSemanticLookup =
196
- isRepeatOfSemanticName && semanticNamesSpanningTitleAndDesc.has(semanticVarName);
197
- let valueFromSource = lookupSourceMap[numericSlotKey];
198
- if (valueFromSource === undefined || valueFromSource === null) {
199
- if (!skipSharedSemanticLookup) {
200
- valueFromSource = lookupSourceMap[semanticVarName];
201
- }
202
- }
203
- if (valueFromSource === undefined || valueFromSource === null) {
204
- valueFromSource = lookupSourceMap[String(slotIndexZeroBased + 1)];
205
- }
206
- if (valueFromSource === undefined || valueFromSource === null) {
207
- valueFromSource = lookupSourceMap[slotIndexZeroBased + 1];
208
- }
209
- const trimmedSlotValue = valueFromSource == null ? '' : String(valueFromSource).trim();
210
- coalescedMap[numericSlotKey] = trimmedSlotValue;
211
- if (!seenSemanticVarNames.has(semanticVarName)) {
212
- seenSemanticVarNames.add(semanticVarName);
213
- coalescedMap[semanticVarName] = trimmedSlotValue;
214
- }
215
- });
216
- return coalescedMap;
217
- }
218
-
219
- /**
220
- * Resolve the personalization value for a variable slot — aligned with createPayload:
221
- * per-slot numeric keys `1`, `2`, … win over legacy semantic keys when both exist (duplicate
222
- * `{{name}}` in title+desc). If semantic is explicitly cleared to '', that still wins over a
223
- * stale numeric value (see tests) — except in embedded library / journey mode (`isLibraryMode`).
224
- *
225
- * In library mode, campaign payloads often set semantic keys to '' while numeric slot `1`,`2`,…
226
- * still holds the value selected in the library; prefer that so VarSegment prepopulates.
227
- *
228
- * When a numeric slot is present but only whitespace / empty (common after hydration), do not
229
- * treat it as authoritative — fall through to the semantic key so preview and payload match the
230
- * tag the user selected (e.g. `1: ''` but `promotion_points: '{{newTag}}'`).
231
- *
232
- * @param {boolean} [omitSemanticFallback=false] When true, do not read `varName` on the map (and do
233
- * not apply the early `semanticEmpty` short-circuit). Use when the same semantic name appears in
234
- * both title and description so each global slot stays independent.
235
- */
236
- export function resolveCardVarMappedSlotValue(
237
- cardVarMapped,
238
- varName,
239
- globalSlotIndexZeroBased,
240
- isLibraryMode = false,
241
- omitSemanticFallback = false,
242
- ) {
243
- const varMap = cardVarMapped ?? {};
244
- const slotKey = String(globalSlotIndexZeroBased + 1);
245
- const semanticEmpty =
246
- Object.prototype.hasOwnProperty.call(varMap, varName)
247
- && String(varMap[varName] ?? '') === '';
248
- const slotNonEmpty =
249
- Object.prototype.hasOwnProperty.call(varMap, slotKey)
250
- && String(varMap[slotKey] ?? '').trim() !== '';
251
-
252
- if (semanticEmpty && !(isLibraryMode && slotNonEmpty) && !omitSemanticFallback) {
253
- return '';
254
- }
255
- let numericSlotValue = '';
256
- if (Object.prototype.hasOwnProperty.call(varMap, slotKey)) {
257
- /** Text-only RCS card: editor shows a single "Text message" field (description); title row is hidden. */
258
- numericSlotValue = String(varMap[slotKey] ?? '');
259
- } else if (Object.prototype.hasOwnProperty.call(varMap, globalSlotIndexZeroBased + 1)) {
260
- numericSlotValue = String(varMap[globalSlotIndexZeroBased + 1] ?? '');
261
- }
262
- if (numericSlotValue.trim() !== '') {
263
- return numericSlotValue;
264
- }
265
- if (!omitSemanticFallback && Object.prototype.hasOwnProperty.call(varMap, varName)) {
266
- return String(varMap[varName] ?? '');
267
- }
268
- return '';
269
- }
270
-
271
- /** Text-only RCS card: editor shows a single “Text message” field (description); title row is hidden. */
272
- export function isRcsTextOnlyCardMediaType(mediaType) {
273
- return (
274
- mediaType === RCS_MEDIA_TYPES.NONE
275
- || String(mediaType || '').toUpperCase() === RCS_MEDIA_TYPES.TEXT
276
- );
277
- }
278
-
279
- /**
280
- * Resolve RCS card title/description for TemplatePreview (e.g. campaign slidebox preview).
281
- * Mirrors `resolveTemplateWithMap` in the Rcs editor: title vars use global slots 0..n-1, then description.
282
- * For text-only cards (`textOnlyCard`), ignore persisted `title` and resolve description from slot 0 — matches
283
- * the editor where only the message body is shown.
284
- */
285
- export function resolveRcsCardPreviewStrings(
286
- title,
287
- description,
288
- cardVarMapped,
289
- isLibraryMode = false,
290
- textOnlyCard = false,
291
- ) {
292
- const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
293
- const getVarNameFromToken = (token = '') =>
294
- token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
295
- const semanticNamesSpanningTitleAndDesc = textOnlyCard
296
- ? new Set()
297
- : getRcsSemanticVarNamesSpanningTitleAndDesc(title, description, rcsVarRegex);
298
-
299
- const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
300
- if (!str) return '';
301
- const arr = splitTemplateVarStringRcs(str);
302
- let varOrdinal = 0;
303
- return arr
304
- .map((elem) => {
305
- if (rcsVarTestRegex.test(elem)) {
306
- const key = getVarNameFromToken(elem);
307
- const globalSlot = slotOffset + varOrdinal;
308
- varOrdinal += 1;
309
- const omitSemantic = semanticNamesSpanningTitleAndDesc.has(key);
310
- const v = resolveCardVarMappedSlotValue(
311
- cardVarMapped,
312
- key,
313
- globalSlot,
314
- isLibraryMode,
315
- omitSemantic,
316
- );
317
- if (v == null || String(v).trim() === '') return elem;
318
- return String(v);
319
- }
320
- return elem;
321
- })
322
- .join('');
323
- };
324
-
325
- const effectiveTitle = textOnlyCard ? '' : String(title || '');
326
- const titleVarCount = textOnlyCard
327
- ? 0
328
- : (effectiveTitle.match(rcsVarRegex) || []).length;
329
- return {
330
- rcsTitle: textOnlyCard ? '' : resolveTemplateWithMap(effectiveTitle, 0),
331
- rcsDesc: resolveTemplateWithMap(String(description || ''), titleVarCount),
332
- };
333
- }
334
-
335
- /**
336
- * Campaign consumer payload: replace each card's `title` / `description` with VarSegment-resolved
337
- * tag strings (same rules as {@link resolveRcsCardPreviewStrings}). Root `rcsCardVarMapped` merges
338
- * with per-card `cardVarMapped` for resolution; emitted `cardVarMapped` omits SMS-fallback slot keys
339
- * (root/nested merged with {@link pickRcsCardVarMappedEntries} per side).
340
- */
341
- export function mapRcsCardContentForConsumerWithResolvedTags(
342
- cardContentArray,
343
- rootRcsCardVarMapped,
344
- isFullMode,
345
- ) {
346
- const rootRecord =
347
- rootRcsCardVarMapped != null && typeof rootRcsCardVarMapped === 'object'
348
- ? rootRcsCardVarMapped
349
- : {};
350
- const list = Array.isArray(cardContentArray) ? cardContentArray : [];
351
- const isLibraryMode = isFullMode !== true;
352
- return list.map((card) => {
353
- if (!card || typeof card !== 'object') return card;
354
- const nested =
355
- card.cardVarMapped != null && typeof card.cardVarMapped === 'object'
356
- ? card.cardVarMapped
357
- : {};
358
- const rootClean = pickRcsCardVarMappedEntries(rootRecord);
359
- const nestedClean = pickRcsCardVarMappedEntries(nested);
360
- const merged = { ...rootClean, ...nestedClean };
361
- const textOnly = isRcsTextOnlyCardMediaType(card.mediaType);
362
- const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
363
- card.title ?? '',
364
- card.description ?? '',
365
- merged,
366
- isLibraryMode,
367
- textOnly,
368
- );
369
- const { cardVarMapped: _drop, ...cardRest } = card;
370
- return {
371
- ...cardRest,
372
- title: rcsTitle,
373
- description: rcsDesc,
374
- ...(Object.keys(nestedClean).length > 0 ? { cardVarMapped: nestedClean } : {}),
375
- };
376
- });
377
- }
378
-
379
- /**
380
- * Before save: strip only legacy numeric self-placeholders (`{{1}}`, `{{2}}`, …) mistakenly stored as
381
- * slot values. Preserve semantic tokens like `{{FirstName}}` from TagList — those are valid mappings.
382
- */
383
- export function sanitizeCardVarMappedValue(val) {
384
- if (val == null) return '';
385
- const trimmedDisplayString = String(val).trim();
386
- if (/^\{\{\d+\}\}$/.test(trimmedDisplayString)) return '';
387
- return String(val);
388
- }
389
-
390
- /**
391
- * Same completion rule as SmsTraiEdit RCS fallback — used by `isDisableDone` from
392
- * `smsFallbackData.rcsSmsFallbackVarMapped` + template string.
393
- * Every variable token (DLT {#…#} or mustache {{…}}) must have a non-empty trimmed value in the map.
394
- *
395
- * Slot keys are usually `${token}_${segmentIndex}` (same as VarSegmentMessageEditor). Persisted / API
396
- * payloads may use `${token}_${varOrdinal}` with a 1-based occurrence index (see SmsTraiEdit init).
397
- * We try segment index first, then ordinal — so e.g. template `{#var#}` (segment index 0) still matches
398
- * map `{#var#}_1`.
399
- *
400
- * @param {string} templateText
401
- * @param {Record<string, string>} [varSlotValueMap={}]
402
- * @returns {boolean}
403
- */
404
- export function areAllRcsSmsFallbackVarSlotsFilled(templateText, varSlotValueMap = {}) {
405
- if (!templateText || typeof templateText !== 'string') return true;
406
- const segments = splitTemplateVarString(templateText, COMBINED_SMS_TEMPLATE_VAR_REGEX);
407
- const hasVarToken = segments.some(
408
- (segment) =>
409
- typeof segment === 'string'
410
- && isAnyTemplateVarToken(segment),
411
- );
412
- if (!hasVarToken) return true;
413
- let varOrdinal = 0;
414
- return segments.every((segment, segmentIndex) => {
415
- if (
416
- typeof segment !== 'string'
417
- || !isAnyTemplateVarToken(segment)
418
- ) return true;
419
- varOrdinal += 1;
420
- const indexKey = `${segment}_${segmentIndex}`;
421
- const ordinalKey = `${segment}_${varOrdinal}`;
422
- let mappedSlotValue;
423
- if (Object.prototype.hasOwnProperty.call(varSlotValueMap, indexKey)) {
424
- mappedSlotValue = varSlotValueMap[indexKey];
425
- } else if (Object.prototype.hasOwnProperty.call(varSlotValueMap, ordinalKey)) {
426
- mappedSlotValue = varSlotValueMap[ordinalKey];
427
- } else {
428
- mappedSlotValue = undefined;
429
- }
430
- if (mappedSlotValue == null) return false;
431
- return String(mappedSlotValue).trim() !== '';
432
- });
433
- }
434
-
435
36
  export const getRCSContent = (template) => {
436
37
  const renderRcsSuggestionsPreview = (rcsSuggestions) => {
437
38
  const renderArray = [];
@@ -450,7 +51,7 @@ export const getRCSContent = (template) => {
450
51
  } else if (suggestion.type === RCS_BUTTON_TYPES.CTA) {
451
52
  renderArray.push(
452
53
  <CapLabel type="label21" className="rcs-cta-preview">
453
- <CapIcon type="open-in-new" size="xs" />
54
+ <CapIcon type="launch" size="xs" />
454
55
  {suggestion.text}
455
56
  </CapLabel>,
456
57
  );
@@ -471,7 +72,6 @@ export const getRCSContent = (template) => {
471
72
  content: {
472
73
  [RCS]: {
473
74
  rcsContent: {
474
- cardType = '',
475
75
  cardContent = [{}],
476
76
  cardSettings = {},
477
77
  } = {},
@@ -484,62 +84,9 @@ export const getRCSContent = (template) => {
484
84
  media = {},
485
85
  description,
486
86
  title,
487
- mediaType,
488
87
  suggestions = [],
489
88
  } = cardContent[0];
490
- const isCarousel =
491
- (cardType || '').toString().toLowerCase() === 'carousel' ||
492
- (cardContent || []).length > 1;
493
- const isTextOnlyCard = isRcsTextOnlyCardMediaType(mediaType);
494
89
  const mediaPreview = media?.thumbnailUrl ? media.thumbnailUrl : media.mediaUrl;
495
-
496
- // Listing preview shows only the FIRST card — no peek of other cards, no buttons (Figma).
497
- const renderCarouselListingPreview = () => {
498
- const cards = Array.isArray(cardContent) ? cardContent : [];
499
- if (!cards.length) return null;
500
- const c = cards[0];
501
- const m = c?.media || {};
502
- const isVideo = (c?.mediaType || m?.mediaType || '').toString().toUpperCase() === RCS_MEDIA_TYPES.VIDEO;
503
- const thumbUrl = m?.thumbnailUrl;
504
- const mediaUrl = m?.mediaUrl;
505
- // Avoid rendering an <img src="...mp4"> when a video doesn't have a thumbnail.
506
- const url = thumbUrl ? thumbUrl : (isVideo ? '' : mediaUrl);
507
- return (
508
- <div className="whatsapp-carousel-container" role="group">
509
- <div className="whatsapp-carousel-card">
510
- {/* Text and media render as two separate rounded cards, not one merged
511
- box (Figma listing reference) — description only, no card title. */}
512
- <div className="rcs-carousel-listing-text-box">
513
- <CapLabel type="label9" className="whatsapp-carousel-body">
514
- {c?.description || ''}
515
- </CapLabel>
516
- </div>
517
- <div className="rcs-carousel-listing-media-box">
518
- {url && (
519
- <CapImage
520
- src={url}
521
- className="whatsapp-image"
522
- />
523
- )}
524
- {!url && isVideo && (
525
- <div
526
- className="whatsapp-image video-preview rcs-video-preview-placeholder"
527
- >
528
- <CapLabel type="label9" className="rcs-video-preview-label">
529
- Video preview
530
- </CapLabel>
531
- </div>
532
- )}
533
- </div>
534
- </div>
535
- </div>
536
- );
537
- };
538
-
539
- if (isCarousel) {
540
- return renderCarouselListingPreview();
541
- }
542
-
543
90
  return (
544
91
  <div className="cap-rcs-creatives">
545
92
  {mediaPreview && (
@@ -548,15 +95,13 @@ export const getRCSContent = (template) => {
548
95
  className="rcs-listing-image"
549
96
  />
550
97
  )}
551
- {!isTextOnlyCard && (
552
- <CapLabel
553
- type="label19"
554
- className="rcs-listing-content title"
555
- fontWeight="bold"
556
- >
557
- {title}
558
- </CapLabel>
559
- )}
98
+ <CapLabel
99
+ type="label19"
100
+ className="rcs-listing-content title"
101
+ fontWeight="bold"
102
+ >
103
+ {title}
104
+ </CapLabel>
560
105
  <CapLabel type="label19" className="rcs-listing-content desc">
561
106
  {description}
562
107
  </CapLabel>
@@ -564,3 +109,4 @@ export const getRCSContent = (template) => {
564
109
  </div>
565
110
  );
566
111
  };
112
+