@capillarytech/creatives-library 8.0.330-alpha.0 → 8.0.330

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 (122) hide show
  1. package/constants/unified.js +0 -18
  2. package/package.json +1 -1
  3. package/services/api.js +0 -17
  4. package/services/tests/api.test.js +0 -85
  5. package/utils/commonUtils.js +0 -28
  6. package/utils/tests/commonUtil.test.js +0 -169
  7. package/v2Components/CapTagList/index.js +0 -10
  8. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
  9. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  10. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
  11. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  15. package/v2Components/CommonTestAndPreview/SendTestMessage.js +53 -87
  16. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +1 -20
  17. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  18. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -145
  19. package/v2Components/CommonTestAndPreview/actions.js +0 -10
  20. package/v2Components/CommonTestAndPreview/constants.js +1 -53
  21. package/v2Components/CommonTestAndPreview/index.js +168 -998
  22. package/v2Components/CommonTestAndPreview/messages.js +3 -147
  23. package/v2Components/CommonTestAndPreview/reducer.js +0 -10
  24. package/v2Components/CommonTestAndPreview/sagas.js +6 -15
  25. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +286 -328
  26. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  27. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  29. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +24 -65
  30. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  31. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
  32. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -168
  33. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
  34. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  35. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
  36. package/v2Components/FormBuilder/index.js +1 -7
  37. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  38. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  39. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  40. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  41. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  42. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  43. package/v2Containers/CreativesContainer/constants.js +0 -9
  44. package/v2Containers/CreativesContainer/index.js +93 -292
  45. package/v2Containers/CreativesContainer/index.scss +1 -51
  46. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  47. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  48. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  49. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  50. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -20
  51. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  52. package/v2Containers/Rcs/constants.js +3 -40
  53. package/v2Containers/Rcs/index.js +895 -1145
  54. package/v2Containers/Rcs/index.scss +6 -85
  55. package/v2Containers/Rcs/messages.js +2 -12
  56. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2236 -41719
  57. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  58. package/v2Containers/Rcs/tests/index.test.js +38 -41
  59. package/v2Containers/Rcs/tests/mockData.js +0 -38
  60. package/v2Containers/Rcs/tests/utils.test.js +1 -435
  61. package/v2Containers/Rcs/utils.js +10 -405
  62. package/v2Containers/Sms/Create/index.js +38 -100
  63. package/v2Containers/SmsTrai/Create/index.js +4 -9
  64. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  65. package/v2Containers/SmsTrai/Edit/index.js +128 -636
  66. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  67. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2604 -4590
  68. package/v2Containers/SmsWrapper/index.js +8 -37
  69. package/v2Containers/TagList/index.js +0 -6
  70. package/v2Containers/Templates/_templates.scss +2 -63
  71. package/v2Containers/Templates/actions.js +0 -11
  72. package/v2Containers/Templates/constants.js +0 -2
  73. package/v2Containers/Templates/index.js +40 -90
  74. package/v2Containers/Templates/sagas.js +12 -57
  75. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
  76. package/v2Containers/Templates/tests/sagas.test.js +123 -193
  77. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  78. package/v2Containers/TemplatesV2/index.js +23 -86
  79. package/v2Containers/Whatsapp/index.js +20 -3
  80. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5790
  81. package/utils/templateVarUtils.js +0 -201
  82. package/utils/tests/templateVarUtils.test.js +0 -204
  83. package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
  84. package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -155
  85. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -93
  86. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  87. package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
  88. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -648
  89. package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -174
  90. package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
  91. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  92. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
  93. package/v2Components/SmsFallback/constants.js +0 -73
  94. package/v2Components/SmsFallback/index.js +0 -955
  95. package/v2Components/SmsFallback/index.scss +0 -265
  96. package/v2Components/SmsFallback/messages.js +0 -78
  97. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -118
  98. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  99. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  100. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  101. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
  102. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -277
  103. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  104. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  105. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  106. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  107. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  108. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  109. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
  110. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  111. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  112. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  113. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
  114. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  115. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  116. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  117. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  118. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  119. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  120. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  121. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  122. 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,380 +33,6 @@ export const getTemplateStatusType = (templateStatus) => {
50
33
  }
51
34
  };
52
35
 
53
- /**
54
- * Global RegExp matching `{{numericVarName}}` in RCS template strings.
55
- * `numericVarName` is escaped for regex metacharacters.
56
- */
57
- export function buildRcsNumericMustachePlaceholderRegex(numericVarName) {
58
- const escaped = String(numericVarName).replace(RCS_REGEX_META_CHARS_PATTERN, '\\$&');
59
- return new RegExp(`\\{\\{${escaped}\\}\\}`, 'g');
60
- }
61
-
62
- export function normalizeCardVarMapped(rawCardVarMapped, orderedTagNames) {
63
- if (!rawCardVarMapped || typeof rawCardVarMapped !== 'object') return {};
64
- const normalizedMap = {};
65
- const templateVarNamesInOrder = Array.isArray(orderedTagNames) ? orderedTagNames : null;
66
- const hasOrderedSlots =
67
- Boolean(templateVarNamesInOrder?.length);
68
- Object.entries(rawCardVarMapped).forEach(([entryKey, entryValue]) => {
69
- const trimmedValue = entryValue == null ? '' : String(entryValue).trim();
70
- const entryKeyIsNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(entryKey));
71
- const mustacheInnerMatch = trimmedValue.match(/^\{\{([^}]+)\}\}$/);
72
- const innerFromMustache =
73
- mustacheInnerMatch?.[1] != null ? String(mustacheInnerMatch[1]).trim() : null;
74
-
75
- if (innerFromMustache !== null && entryKeyIsNumericSlot) {
76
- const slotIndexZeroBased = parseInt(String(entryKey), 10) - 1;
77
- const expectedVarForSlot =
78
- hasOrderedSlots
79
- && slotIndexZeroBased >= 0
80
- && slotIndexZeroBased < templateVarNamesInOrder.length
81
- ? templateVarNamesInOrder[slotIndexZeroBased]
82
- : null;
83
- const innerMatchesSlotToken =
84
- expectedVarForSlot != null && innerFromMustache === expectedVarForSlot;
85
- const legacyUnorderedPlaceholder = !hasOrderedSlots;
86
- /* Library: slot "1" + {{user_id_b64}} when token is user_id_b64 → empty semantic. With ordered
87
- * slots, only clear when inner matches that slot's template token; else keep (e.g. {{1}}+{{FirstName}}). */
88
- const clearNumericSlotMustacheAsUnfilled =
89
- !RCS_NUMERIC_VAR_NAME_REGEX.test(innerFromMustache)
90
- && (legacyUnorderedPlaceholder || innerMatchesSlotToken);
91
- if (clearNumericSlotMustacheAsUnfilled) {
92
- const outputKey = innerFromMustache;
93
- const existingValue = normalizedMap[outputKey];
94
- if (existingValue != null && String(existingValue).trim() !== '') {
95
- return;
96
- }
97
- normalizedMap[outputKey] = '';
98
- return;
99
- }
100
- if (RCS_NUMERIC_VAR_NAME_REGEX.test(innerFromMustache)) {
101
- const outputKey = innerFromMustache;
102
- const existingValue = normalizedMap[outputKey];
103
- if (existingValue != null && String(existingValue).trim() !== '') {
104
- return;
105
- }
106
- normalizedMap[outputKey] = '';
107
- return;
108
- }
109
- }
110
-
111
- if (innerFromMustache !== null && !entryKeyIsNumericSlot) {
112
- if (innerFromMustache === String(entryKey)) {
113
- const existingValue = normalizedMap[entryKey];
114
- if (existingValue != null && String(existingValue).trim() !== '') {
115
- return;
116
- }
117
- normalizedMap[entryKey] = '';
118
- return;
119
- }
120
- }
121
-
122
- if (entryKeyIsNumericSlot && templateVarNamesInOrder?.length) {
123
- const slotIndexZeroBased = parseInt(String(entryKey), 10) - 1;
124
- if (slotIndexZeroBased >= 0 && slotIndexZeroBased < templateVarNamesInOrder.length) {
125
- normalizedMap[templateVarNamesInOrder[slotIndexZeroBased]] = trimmedValue;
126
- return;
127
- }
128
- }
129
- normalizedMap[entryKey] = trimmedValue;
130
- });
131
- return normalizedMap;
132
- }
133
-
134
- /**
135
- * Semantic names that appear in both title and description (e.g. `{{adv}}` in header and body).
136
- * Those slots must not share one semantic `cardVarMapped` key — otherwise VarSegment inputs mirror.
137
- */
138
- export function getRcsSemanticVarNamesSpanningTitleAndDesc(
139
- templateTitle,
140
- templateDesc,
141
- rcsVarRegex,
142
- ) {
143
- const getVarNameFromToken = (token = '') => token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
144
- const titleTokens = templateTitle?.match(rcsVarRegex) ?? [];
145
- const descTokens = templateDesc?.match(rcsVarRegex) ?? [];
146
- const titleNames = new Set(titleTokens.map(getVarNameFromToken).filter(Boolean));
147
- const descNames = new Set(descTokens.map(getVarNameFromToken).filter(Boolean));
148
- const spanning = new Set();
149
- titleNames.forEach((n) => {
150
- if (descNames.has(n)) spanning.add(n);
151
- });
152
- return spanning;
153
- }
154
-
155
- /**
156
- * Rebuild `cardVarMapped` so keys match the current title/description tokens (title tokens first,
157
- * then description), in order. Values are taken from the matching key, else from legacy slot
158
- * `1`, `2`, … by index. If there are no `{{...}}` tokens, returns a shallow clone of `raw`.
159
- */
160
- export function coalesceCardVarMappedToTemplate(
161
- sourceCardVarMap,
162
- templateTitle,
163
- templateDesc,
164
- rcsVarRegex,
165
- ) {
166
- const getVarNameFromToken = (token = '') => token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
167
- const templateVarTokens = [
168
- ...(templateTitle?.match(rcsVarRegex) ?? []),
169
- ...(templateDesc?.match(rcsVarRegex) ?? []),
170
- ];
171
- const lookupSourceMap =
172
- sourceCardVarMap != null && typeof sourceCardVarMap === 'object' ? sourceCardVarMap : {};
173
- if (!templateVarTokens.length) {
174
- return { ...lookupSourceMap };
175
- }
176
- const semanticNamesSpanningTitleAndDesc = getRcsSemanticVarNamesSpanningTitleAndDesc(
177
- templateTitle,
178
- templateDesc,
179
- rcsVarRegex,
180
- );
181
- const coalescedMap = { ...lookupSourceMap };
182
- const seenSemanticVarNames = new Set();
183
- templateVarTokens.forEach((token, slotIndexZeroBased) => {
184
- const semanticVarName = getVarNameFromToken(token);
185
- if (!semanticVarName) return;
186
- const numericSlotKey = String(slotIndexZeroBased + 1);
187
- const isRepeatOfSemanticName = seenSemanticVarNames.has(semanticVarName);
188
- const skipSharedSemanticLookup =
189
- isRepeatOfSemanticName && semanticNamesSpanningTitleAndDesc.has(semanticVarName);
190
- let valueFromSource = lookupSourceMap[numericSlotKey];
191
- if (valueFromSource === undefined || valueFromSource === null) {
192
- if (!skipSharedSemanticLookup) {
193
- valueFromSource = lookupSourceMap[semanticVarName];
194
- }
195
- }
196
- if (valueFromSource === undefined || valueFromSource === null) {
197
- valueFromSource = lookupSourceMap[String(slotIndexZeroBased + 1)];
198
- }
199
- if (valueFromSource === undefined || valueFromSource === null) {
200
- valueFromSource = lookupSourceMap[slotIndexZeroBased + 1];
201
- }
202
- const trimmedSlotValue = valueFromSource == null ? '' : String(valueFromSource).trim();
203
- coalescedMap[numericSlotKey] = trimmedSlotValue;
204
- if (!seenSemanticVarNames.has(semanticVarName)) {
205
- seenSemanticVarNames.add(semanticVarName);
206
- coalescedMap[semanticVarName] = trimmedSlotValue;
207
- }
208
- });
209
- return coalescedMap;
210
- }
211
-
212
- /**
213
- * Resolve the personalization value for a variable slot — aligned with createPayload:
214
- * per-slot numeric keys `1`, `2`, … win over legacy semantic keys when both exist (duplicate
215
- * `{{name}}` in title+desc). If semantic is explicitly cleared to '', that still wins over a
216
- * stale numeric value (see tests) — except in embedded library / journey mode (`isLibraryMode`).
217
- *
218
- * In library mode, campaign payloads often set semantic keys to '' while numeric slot `1`,`2`,…
219
- * still holds the value selected in the library; prefer that so VarSegment prepopulates.
220
- *
221
- * When a numeric slot is present but only whitespace / empty (common after hydration), do not
222
- * treat it as authoritative — fall through to the semantic key so preview and payload match the
223
- * tag the user selected (e.g. `1: ''` but `promotion_points: '{{newTag}}'`).
224
- *
225
- * @param {boolean} [omitSemanticFallback=false] When true, do not read `varName` on the map (and do
226
- * not apply the early `semanticEmpty` short-circuit). Use when the same semantic name appears in
227
- * both title and description so each global slot stays independent.
228
- */
229
- export function resolveCardVarMappedSlotValue(
230
- cardVarMapped,
231
- varName,
232
- globalSlotIndexZeroBased,
233
- isLibraryMode = false,
234
- omitSemanticFallback = false,
235
- ) {
236
- const varMap = cardVarMapped ?? {};
237
- const slotKey = String(globalSlotIndexZeroBased + 1);
238
- const semanticEmpty =
239
- Object.prototype.hasOwnProperty.call(varMap, varName)
240
- && String(varMap[varName] ?? '') === '';
241
- const slotNonEmpty =
242
- Object.prototype.hasOwnProperty.call(varMap, slotKey)
243
- && String(varMap[slotKey] ?? '').trim() !== '';
244
-
245
- if (semanticEmpty && !(isLibraryMode && slotNonEmpty) && !omitSemanticFallback) {
246
- return '';
247
- }
248
- let numericSlotValue = '';
249
- if (Object.prototype.hasOwnProperty.call(varMap, slotKey)) {
250
- numericSlotValue = String(varMap[slotKey] ?? '');
251
- } else if (Object.prototype.hasOwnProperty.call(varMap, globalSlotIndexZeroBased + 1)) {
252
- numericSlotValue = String(varMap[globalSlotIndexZeroBased + 1] ?? '');
253
- }
254
- if (numericSlotValue.trim() !== '') {
255
- return numericSlotValue;
256
- }
257
- if (!omitSemanticFallback && Object.prototype.hasOwnProperty.call(varMap, varName)) {
258
- return String(varMap[varName] ?? '');
259
- }
260
- return '';
261
- }
262
-
263
- /** Text-only RCS card: editor shows a single “Text message” field (description); title row is hidden. */
264
- export function isRcsTextOnlyCardMediaType(mediaType) {
265
- return (
266
- mediaType === RCS_MEDIA_TYPES.NONE
267
- || String(mediaType || '').toUpperCase() === 'TEXT'
268
- );
269
- }
270
-
271
- /**
272
- * Resolve RCS card title/description for TemplatePreview (e.g. campaign slidebox preview).
273
- * Mirrors `resolveTemplateWithMap` in the Rcs editor: title vars use global slots 0..n-1, then description.
274
- * For text-only cards (`textOnlyCard`), ignore persisted `title` and resolve description from slot 0 — matches
275
- * the editor where only the message body is shown.
276
- */
277
- export function resolveRcsCardPreviewStrings(
278
- title,
279
- description,
280
- cardVarMapped,
281
- isLibraryMode = false,
282
- textOnlyCard = false,
283
- ) {
284
- const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
285
- const getVarNameFromToken = (token = '') =>
286
- token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
287
- const semanticNamesSpanningTitleAndDesc = textOnlyCard
288
- ? new Set()
289
- : getRcsSemanticVarNamesSpanningTitleAndDesc(title, description, rcsVarRegex);
290
-
291
- const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
292
- if (!str) return '';
293
- const arr = splitTemplateVarStringRcs(str);
294
- let varOrdinal = 0;
295
- return arr
296
- .map((elem) => {
297
- if (rcsVarTestRegex.test(elem)) {
298
- const key = getVarNameFromToken(elem);
299
- const globalSlot = slotOffset + varOrdinal;
300
- varOrdinal += 1;
301
- const omitSemantic = semanticNamesSpanningTitleAndDesc.has(key);
302
- const v = resolveCardVarMappedSlotValue(
303
- cardVarMapped,
304
- key,
305
- globalSlot,
306
- isLibraryMode,
307
- omitSemantic,
308
- );
309
- if (v == null || String(v).trim() === '') return elem;
310
- return String(v);
311
- }
312
- return elem;
313
- })
314
- .join('');
315
- };
316
-
317
- const effectiveTitle = textOnlyCard ? '' : String(title || '');
318
- const titleVarCount = textOnlyCard
319
- ? 0
320
- : (effectiveTitle.match(rcsVarRegex) || []).length;
321
- return {
322
- rcsTitle: textOnlyCard ? '' : resolveTemplateWithMap(effectiveTitle, 0),
323
- rcsDesc: resolveTemplateWithMap(String(description || ''), titleVarCount),
324
- };
325
- }
326
-
327
- /**
328
- * Campaign consumer payload: replace each card's `title` / `description` with VarSegment-resolved
329
- * tag strings (same rules as {@link resolveRcsCardPreviewStrings}). Root `rcsCardVarMapped` merges
330
- * with per-card `cardVarMapped` for resolution; emitted `cardVarMapped` omits SMS-fallback slot keys
331
- * (root/nested merged with {@link pickRcsCardVarMappedEntries} per side).
332
- */
333
- export function mapRcsCardContentForConsumerWithResolvedTags(
334
- cardContentArray,
335
- rootRcsCardVarMapped,
336
- isFullMode,
337
- ) {
338
- const rootRecord =
339
- rootRcsCardVarMapped != null && typeof rootRcsCardVarMapped === 'object'
340
- ? rootRcsCardVarMapped
341
- : {};
342
- const list = Array.isArray(cardContentArray) ? cardContentArray : [];
343
- const isLibraryMode = isFullMode !== true;
344
- return list.map((card) => {
345
- if (!card || typeof card !== 'object') return card;
346
- const nested =
347
- card.cardVarMapped != null && typeof card.cardVarMapped === 'object'
348
- ? card.cardVarMapped
349
- : {};
350
- const rootClean = pickRcsCardVarMappedEntries(rootRecord);
351
- const nestedClean = pickRcsCardVarMappedEntries(nested);
352
- const merged = { ...rootClean, ...nestedClean };
353
- const textOnly = isRcsTextOnlyCardMediaType(card.mediaType);
354
- const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
355
- card.title ?? '',
356
- card.description ?? '',
357
- merged,
358
- isLibraryMode,
359
- textOnly,
360
- );
361
- const { cardVarMapped: _drop, ...cardRest } = card;
362
- return {
363
- ...cardRest,
364
- title: rcsTitle,
365
- description: rcsDesc,
366
- ...(Object.keys(nestedClean).length > 0 ? { cardVarMapped: nestedClean } : {}),
367
- };
368
- });
369
- }
370
-
371
- /**
372
- * Before save: strip only legacy numeric self-placeholders (`{{1}}`, `{{2}}`, …) mistakenly stored as
373
- * slot values. Preserve semantic tokens like `{{FirstName}}` from TagList — those are valid mappings.
374
- */
375
- export function sanitizeCardVarMappedValue(val) {
376
- if (val == null) return '';
377
- const trimmedDisplayString = String(val).trim();
378
- if (/^\{\{\d+\}\}$/.test(trimmedDisplayString)) return '';
379
- return String(val);
380
- }
381
-
382
- /**
383
- * Same completion rule as SmsTraiEdit RCS fallback — used by `isDisableDone` from
384
- * `smsFallbackData.rcsSmsFallbackVarMapped` + template string.
385
- * Every variable token (DLT {#…#} or mustache {{…}}) must have a non-empty trimmed value in the map.
386
- *
387
- * Slot keys are usually `${token}_${segmentIndex}` (same as VarSegmentMessageEditor). Persisted / API
388
- * payloads may use `${token}_${varOrdinal}` with a 1-based occurrence index (see SmsTraiEdit init).
389
- * We try segment index first, then ordinal — so e.g. template `{#var#}` (segment index 0) still matches
390
- * map `{#var#}_1`.
391
- *
392
- * @param {string} templateText
393
- * @param {Record<string, string>} [varSlotValueMap={}]
394
- * @returns {boolean}
395
- */
396
- export function areAllRcsSmsFallbackVarSlotsFilled(templateText, varSlotValueMap = {}) {
397
- if (!templateText || typeof templateText !== 'string') return true;
398
- const segments = splitTemplateVarString(templateText, COMBINED_SMS_TEMPLATE_VAR_REGEX);
399
- const hasVarToken = segments.some(
400
- (segment) =>
401
- typeof segment === 'string'
402
- && isAnyTemplateVarToken(segment),
403
- );
404
- if (!hasVarToken) return true;
405
- let varOrdinal = 0;
406
- return segments.every((segment, segmentIndex) => {
407
- if (
408
- typeof segment !== 'string'
409
- || !isAnyTemplateVarToken(segment)
410
- ) return true;
411
- varOrdinal += 1;
412
- const indexKey = `${segment}_${segmentIndex}`;
413
- const ordinalKey = `${segment}_${varOrdinal}`;
414
- let mappedSlotValue;
415
- if (Object.prototype.hasOwnProperty.call(varSlotValueMap, indexKey)) {
416
- mappedSlotValue = varSlotValueMap[indexKey];
417
- } else if (Object.prototype.hasOwnProperty.call(varSlotValueMap, ordinalKey)) {
418
- mappedSlotValue = varSlotValueMap[ordinalKey];
419
- } else {
420
- mappedSlotValue = undefined;
421
- }
422
- if (mappedSlotValue == null) return false;
423
- return String(mappedSlotValue).trim() !== '';
424
- });
425
- }
426
-
427
36
  export const getRCSContent = (template) => {
428
37
  const renderRcsSuggestionsPreview = (rcsSuggestions) => {
429
38
  const renderArray = [];
@@ -475,10 +84,8 @@ export const getRCSContent = (template) => {
475
84
  media = {},
476
85
  description,
477
86
  title,
478
- mediaType,
479
87
  suggestions = [],
480
88
  } = cardContent[0];
481
- const isTextOnlyCard = isRcsTextOnlyCardMediaType(mediaType);
482
89
  const mediaPreview = media?.thumbnailUrl ? media.thumbnailUrl : media.mediaUrl;
483
90
  return (
484
91
  <div className="cap-rcs-creatives">
@@ -488,15 +95,13 @@ export const getRCSContent = (template) => {
488
95
  className="rcs-listing-image"
489
96
  />
490
97
  )}
491
- {!isTextOnlyCard && (
492
- <CapLabel
493
- type="label19"
494
- className="rcs-listing-content title"
495
- fontWeight="bold"
496
- >
497
- {title}
498
- </CapLabel>
499
- )}
98
+ <CapLabel
99
+ type="label19"
100
+ className="rcs-listing-content title"
101
+ fontWeight="bold"
102
+ >
103
+ {title}
104
+ </CapLabel>
500
105
  <CapLabel type="label19" className="rcs-listing-content desc">
501
106
  {description}
502
107
  </CapLabel>
@@ -33,10 +33,6 @@ import injectReducer from '../../../utils/injectReducer';
33
33
  import v2SmsCreateReducer from './reducer';
34
34
  import * as globalActions from '../../Cap/actions';
35
35
  import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
36
- import {
37
- getSmsEmbeddedFooterValidity,
38
- getSmsMessageFromFormData,
39
- } from '../smsFormDataHelpers';
40
36
 
41
37
  export class Create extends React.Component { // eslint-disable-line react/prefer-stateless-function
42
38
  constructor(props) {
@@ -57,11 +53,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
57
53
  isTestAndPreviewMode: false,
58
54
  pendingGetFormData: false,
59
55
  };
60
- // Tracks the last validity value reported to SmsFallback so componentDidUpdate
61
- // does not dispatch on every render and create an infinite update loop.
62
- // Intentionally undefined (not true) so the first render always reports the
63
- // real validity rather than assuming the form starts invalid.
64
- this._lastReportedSmsFooterInvalid = undefined;
65
56
  this.saveFormData = this.saveFormData.bind(this);
66
57
  this.onFormDataChange = this.onFormDataChange.bind(this);
67
58
  this.onTemplateNameChange = this.onTemplateNameChange.bind(this);
@@ -168,9 +159,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
168
159
  layout: 'SMS',
169
160
  type: 'TAG',
170
161
  context: this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default',
171
- embedded: this.props.forceFullTagContext
172
- ? 'full'
173
- : (this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full'),
162
+ embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
174
163
  };
175
164
  if (this.props.getDefaultTags) {
176
165
  query.context = this.props.getDefaultTags;
@@ -183,22 +172,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
183
172
  }
184
173
  }
185
174
 
186
- componentDidUpdate() {
187
- if (!this.props.embeddedSmsFallback || typeof this.props.onEmbeddedSmsFooterValidity !== 'function') {
188
- return;
189
- }
190
- const validity = getSmsEmbeddedFooterValidity(this.state.formData, this.state.tabCount);
191
- const isInvalid = !!validity.isTemplateNameEmpty || !!validity.isMessageEmpty;
192
- // Only dispatch when the validity value changes. Dispatching unconditionally caused
193
- // an infinite loop: componentDidUpdate → dispatch → SmsFallback re-render →
194
- // SmsCreate re-render → componentDidUpdate → ... even when state was unchanged.
195
- // The instance variable handles both reference-based and mutation-based FormBuilder
196
- // updates: validity is recomputed from current formData content, not by reference.
197
- if (this._lastReportedSmsFooterInvalid === isInvalid) return;
198
- this._lastReportedSmsFooterInvalid = isInvalid;
199
- this.props.onEmbeddedSmsFooterValidity(validity);
200
- }
201
-
202
175
  componentWillUnmount() {
203
176
  if (this.pendingGetFormDataTimeout) {
204
177
  clearTimeout(this.pendingGetFormDataTimeout);
@@ -302,10 +275,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
302
275
  }
303
276
  const result = {};
304
277
  result.base = baseData;
305
- /* Root field used by FormBuilder; embedded getFormSubscriptionData reads value.base */
306
- if (formData['template-name'] !== undefined) {
307
- result.base['template-name'] = formData['template-name'];
308
- }
309
278
  if (this.state.isValid) {
310
279
  const msgObj = charCount.updateCharCount(baseData['sms-editor']);
311
280
  result.base.msg_count = msgObj.msgCount;
@@ -912,9 +881,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
912
881
  layout: 'SMS',
913
882
  type: 'TAG',
914
883
  context: (data && data.toLowerCase() === 'all') ? 'default' : (data && data.toLowerCase()),
915
- embedded: this.props.forceFullTagContext
916
- ? 'full'
917
- : (this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full'),
884
+ embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
918
885
  };
919
886
  this.props.globalActions.fetchSchemaForEntity(query);
920
887
  }
@@ -922,29 +889,11 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
922
889
  removeStandAlone() {
923
890
  const schema = _.cloneDeep(this.state.schema);
924
891
  const childSections = _.get(schema, 'standalone.sections[0].childSections');
925
- if (childSections) {
926
- /* In-form Save / Test row (index 2) only exists when the schema has > 2 sections. */
927
- if (childSections.length > 2) {
928
- childSections.splice(2, 1);
929
- }
930
- /*
931
- * Creatives library drops the standalone template-name block because `SlideBoxHeader`
932
- * shows the name. This is independent of the section count — guard it separately so
933
- * it still runs even when childSections.length <= 2 (e.g. schema arrives pre-trimmed).
934
- * RCS SMS fallback uses the same slidebox footer but keeps the name in the form.
935
- */
936
- if (!this.props.embeddedSmsFallback) {
937
- const fields = _.get(childSections, '[1].childSections[0].childSections');
938
- if (fields && fields.length > 0) {
939
- fields.splice(0, 1);
940
- _.set(childSections, '[1].childSections[0].childSections', fields);
941
- }
942
- }
943
- _.set(schema, 'standalone.sections[0].childSections', childSections);
944
- }
945
- // Always increment loadingStatus — isSmsLoading() requires >= 2 in library mode
946
- // (isFullMode=false). The early return previously skipped this, leaving the
947
- // spinner stuck forever when the schema had <= 2 childSections.
892
+ childSections.splice(2, 1);
893
+ const fields = _.get(childSections, '[1].childSections[0].childSections');//removing template name section
894
+ fields.splice(0, 1);
895
+ _.set(childSections, '[1].childSections[0].childSections', fields);
896
+ _.set(schema, 'standalone.sections[0].childSections', childSections);
948
897
  this.setState({ schema, loadingStatus: this.state.loadingStatus + 1 });
949
898
  }
950
899
 
@@ -989,8 +938,37 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
989
938
  this.setState({startValidation: false});
990
939
  }
991
940
 
992
- getTemplateContent = () =>
993
- getSmsMessageFromFormData(this.state.formData, this.state.currentTab);
941
+ getTemplateContent = () => {
942
+ // Get SMS content from formData
943
+ if (!this.state.formData || !Array.isArray(this.state.formData) || this.state.formData.length === 0) {
944
+ return '';
945
+ }
946
+ const currentTabData = this.state.formData[this.state.currentTab - 1];
947
+ if (!currentTabData) {
948
+ return '';
949
+ }
950
+
951
+ // PRIORITY 1: Check direct path first (most common for SMS)
952
+ // This handles: formData[0]['sms-editor']
953
+ if (currentTabData['sms-editor']) {
954
+ return currentTabData['sms-editor'];
955
+ }
956
+
957
+ // PRIORITY 2: Check activeTab structure (for versioned templates)
958
+ // This handles: formData[0][activeTab]['sms-editor']
959
+ const activeTab = currentTabData?.activeTab || 'base';
960
+ if (currentTabData[activeTab]?.['sms-editor']) {
961
+ return currentTabData[activeTab]['sms-editor'];
962
+ }
963
+
964
+ // PRIORITY 3: Check base explicitly (fallback)
965
+ // This handles: formData[0]['base']['sms-editor']
966
+ if (currentTabData['base']?.['sms-editor']) {
967
+ return currentTabData['base']['sms-editor'];
968
+ }
969
+
970
+ return '';
971
+ }
994
972
 
995
973
  handleTestAndPreview = () => {
996
974
  // If parent is managing state (props exist), call parent handler
@@ -1019,35 +997,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
1019
997
  }
1020
998
 
1021
999
  saveFormData() {
1022
- /*
1023
- * RCS SMS fallback slidebox (embeddedSmsFallback): parent passes isFullMode from RCS, but we must not
1024
- * call createTemplate — that spins CapSpin on createTemplateInProgress and is not the embedded contract.
1025
- * Same as library: hand off form payload via getFormSubscriptionData.
1026
- */
1027
- if (this.props.embeddedSmsFallback && this.props.getFormSubscriptionData) {
1028
- const { isTemplateNameEmpty, isMessageEmpty } = getSmsEmbeddedFooterValidity(
1029
- this.state.formData,
1030
- this.state.tabCount,
1031
- );
1032
- if (isTemplateNameEmpty || isMessageEmpty) {
1033
- this.setState({ startValidation: true, pendingGetFormData: false });
1034
- if (this.props.onValidationFail) {
1035
- this.props.onValidationFail();
1036
- }
1037
- return;
1038
- }
1039
- const payload = this.getFormData();
1040
- if (!payload.validity) {
1041
- if (this.props.onValidationFail) {
1042
- this.props.onValidationFail();
1043
- }
1044
- this.setState({ pendingGetFormData: false, startValidation: false });
1045
- return;
1046
- }
1047
- this.props.getFormSubscriptionData(payload);
1048
- this.setState({ pendingGetFormData: false, startValidation: false });
1049
- return;
1050
- }
1051
1000
  // In library mode: FormBuilder calls onSubmit only after liquid validation succeeds.
1052
1001
  // Submit to parent here so the slidebox can close with valid data.
1053
1002
  if (!this.props.isFullMode) {
@@ -1147,9 +1096,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
1147
1096
  onTestContentClicked={this.props.onTestContentClicked}
1148
1097
  onPreviewContentClicked={this.props.onPreviewContentClicked}
1149
1098
  eventContextTags={this.props?.eventContextTags}
1150
- tagListGetPopupContainer={this.props.tagListGetPopupContainer}
1151
- tagListPopoverOverlayStyle={this.props.tagListPopoverOverlayStyle}
1152
- tagListPopoverOverlayClassName={this.props.tagListPopoverOverlayClassName}
1153
1099
  />
1154
1100
  </CapColumn>
1155
1101
  </CapRow>
@@ -1162,7 +1108,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
1162
1108
  formData={this.state.formData}
1163
1109
  content={this.getTemplateContent()}
1164
1110
  currentChannel={SMS}
1165
- smsRegister={this.props.smsRegister}
1166
1111
  />
1167
1112
  </div>
1168
1113
  );
@@ -1191,13 +1136,6 @@ Create.propTypes = {
1191
1136
  handleTestAndPreview: PropTypes.func,
1192
1137
  handleCloseTestAndPreview: PropTypes.func,
1193
1138
  isTestAndPreviewMode: PropTypes.bool,
1194
- smsRegister: PropTypes.any,
1195
- forceFullTagContext: PropTypes.bool,
1196
- embeddedSmsFallback: PropTypes.bool,
1197
- onEmbeddedSmsFooterValidity: PropTypes.func,
1198
- tagListGetPopupContainer: PropTypes.func,
1199
- tagListPopoverOverlayStyle: PropTypes.object,
1200
- tagListPopoverOverlayClassName: PropTypes.string,
1201
1139
  };
1202
1140
 
1203
1141
  const mapStateToProps = createStructuredSelector({