@capillarytech/creatives-library 9.0.35 → 9.0.36
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/app.js +175 -10
- package/entry.js +1 -67
- package/mfe-exposed-components.js +4 -2
- package/package.json +2 -2
- package/services/api.js +1 -9
- package/styles/containers/layout/_layoutPage.scss +6 -8
- package/utils/gtmTrackers/gtmEvents/creativeDetails.js +1 -2
- package/utils/rcsPayloadUtils.js +2 -5
- package/utils/tests/rcsPayloadUtils.test.js +2 -54
- package/v2Components/CapActionButton/constants.js +0 -1
- package/v2Components/CapActionButton/index.js +9 -77
- package/v2Components/CapActionButton/index.scss +0 -13
- package/v2Components/CapActionButton/messages.js +0 -13
- package/v2Components/CapActionButton/tests/index.test.js +1 -32
- package/v2Components/CommonTestAndPreview/index.js +15 -44
- package/v2Components/CommonTestAndPreview/tests/index.test.js +0 -78
- package/v2Components/CommonTestAndPreview/utils.js +0 -34
- package/v2Components/NavigationBar/index.js +7 -9
- package/v2Components/NavigationBar/tests/index.test.js +19 -39
- package/v2Components/SmsFallback/index.js +0 -6
- package/v2Containers/Cap/constants.js +0 -1
- package/v2Containers/Cap/index.js +27 -57
- package/v2Containers/CreativesContainer/index.js +8 -11
- package/v2Containers/CreativesContainer/tests/index.test.js +0 -53
- package/v2Containers/Rcs/carouselUtils.js +73 -46
- package/v2Containers/Rcs/components/CarouselCard.js +9 -11
- package/v2Containers/Rcs/components/CarouselCardButtons.js +0 -20
- package/v2Containers/Rcs/constants.js +7 -2
- package/v2Containers/Rcs/index.js +205 -226
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +47 -60
- package/v2Containers/Rcs/tests/CarouselCard.test.js +2 -1
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -286
- package/v2Containers/Rcs/tests/carouselUtils.test.js +32 -28
- package/v2Containers/Rcs/tests/index.test.js +238 -151
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -110
- package/v2Containers/Rcs/tests/utils.test.js +17 -36
- package/v2Containers/Rcs/utils.js +55 -28
- package/v2Containers/SmsTrai/Edit/index.js +24 -35
- package/v2Containers/Templates/_templates.scss +1 -1
- package/v2Containers/Templates/index.js +0 -1
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +0 -5
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +3 -8
- package/v2Containers/TemplatesV2/index.js +9 -22
- package/AppRoot.js +0 -124
- package/app-config.js +0 -61
- package/bootstrap.js +0 -185
- package/utils/getDataLayer.js +0 -15
- package/utils/mfeDetect.js +0 -1
- package/utils/mfeFirstPaintReady.js +0 -29
- package/utils/mfeHistory.js +0 -62
- package/v2Components/NavigationBar/mfeModuleHeader.config.js +0 -16
|
@@ -71,7 +71,6 @@ import {
|
|
|
71
71
|
mergeSmsFallbackForLibrary,
|
|
72
72
|
buildSmsFallBackContentForPayload,
|
|
73
73
|
pickRcsCardVarMappedEntries,
|
|
74
|
-
extractRegisteredSenderIdsFromSmsFallbackRecord,
|
|
75
74
|
} from './rcsLibraryHydrationUtils';
|
|
76
75
|
import {
|
|
77
76
|
RCS,
|
|
@@ -153,6 +152,7 @@ import { isTagIncluded } from '../../utils/commonUtils';
|
|
|
153
152
|
import injectReducer from '../../utils/injectReducer';
|
|
154
153
|
import v2RcsReducer from './reducer';
|
|
155
154
|
import {
|
|
155
|
+
buildRcsNumericMustachePlaceholderRegex,
|
|
156
156
|
getTemplateStatusType,
|
|
157
157
|
normalizeCardVarMapped,
|
|
158
158
|
coalesceCardVarMappedToTemplate,
|
|
@@ -175,7 +175,7 @@ import {
|
|
|
175
175
|
getCarouselDescriptionCharacterCount,
|
|
176
176
|
buildCarouselCardsForPreview as buildCarouselCardsForPreviewUtil,
|
|
177
177
|
buildCarouselCardContentForPayload,
|
|
178
|
-
|
|
178
|
+
computeCarouselVarSlotKeys,
|
|
179
179
|
} from './carouselUtils';
|
|
180
180
|
import CarouselDimensionSelection from './components/CarouselDimensionSelection';
|
|
181
181
|
import CarouselCard from './components/CarouselCard';
|
|
@@ -271,7 +271,12 @@ export const Rcs = (props) => {
|
|
|
271
271
|
const [carouselErrors, setCarouselErrors] = useState([]); // [{ title: string|false, description: string|false }]
|
|
272
272
|
const [activeCarouselIndex, setActiveCarouselIndex] = useState('0');
|
|
273
273
|
const [carouselResetNonce, setCarouselResetNonce] = useState(0);
|
|
274
|
-
|
|
274
|
+
// Title and description each track their own last-focused var slot (mirrors titleTextAreaId/
|
|
275
|
+
// descTextAreaId for the single-card flow) — a single shared id would let a click on the
|
|
276
|
+
// "Add Labels" picker next to one field silently apply to whichever field was focused last,
|
|
277
|
+
// if the user hadn't just clicked into that field's own var slot.
|
|
278
|
+
const [carouselTitleFocusedVarId, setCarouselTitleFocusedVarId] = useState('');
|
|
279
|
+
const [carouselDescFocusedVarId, setCarouselDescFocusedVarId] = useState('');
|
|
275
280
|
const [imageError, setImageError] = useState(null);
|
|
276
281
|
const [templateTitleError, setTemplateTitleError] = useState(false);
|
|
277
282
|
const [cardVarMapped, setCardVarMapped] = useState({});
|
|
@@ -597,7 +602,8 @@ export const Rcs = (props) => {
|
|
|
597
602
|
}}
|
|
598
603
|
onTitleAddVar={() => appendVarToCarouselField(index, 'title')}
|
|
599
604
|
onDescriptionAddVar={() => appendVarToCarouselField(index, 'description')}
|
|
600
|
-
|
|
605
|
+
onTitleTagSelect={onCarouselTitleTagSelect}
|
|
606
|
+
onDescTagSelect={onCarouselDescTagSelect}
|
|
601
607
|
onContextChange={handleOnTagsContextChange}
|
|
602
608
|
renderEditMessage={renderCarouselEditMessage}
|
|
603
609
|
onFieldChange={(fieldName, value) => handleCarouselValueChange(index, [{ fieldName, value }])}
|
|
@@ -656,7 +662,8 @@ export const Rcs = (props) => {
|
|
|
656
662
|
onChange={(key) => {
|
|
657
663
|
setActiveCarouselIndex(`${key}`);
|
|
658
664
|
// reset focused var when switching cards (as per requirement)
|
|
659
|
-
|
|
665
|
+
setCarouselTitleFocusedVarId('');
|
|
666
|
+
setCarouselDescFocusedVarId('');
|
|
660
667
|
}}
|
|
661
668
|
panes={getCarouselTabPanes()}
|
|
662
669
|
/>
|
|
@@ -776,7 +783,7 @@ export const Rcs = (props) => {
|
|
|
776
783
|
['title', 'description'].forEach((field) => {
|
|
777
784
|
const templateStr = card?.[field] || '';
|
|
778
785
|
if (!templateStr) return;
|
|
779
|
-
const resolved = resolveCarouselTemplateWithMap(templateStr, idx);
|
|
786
|
+
const resolved = resolveCarouselTemplateWithMap(templateStr, idx, field === 'description');
|
|
780
787
|
if (!resolved) {
|
|
781
788
|
updateCarouselErrors(idx, { [field]: false });
|
|
782
789
|
return;
|
|
@@ -869,17 +876,22 @@ export const Rcs = (props) => {
|
|
|
869
876
|
};
|
|
870
877
|
|
|
871
878
|
/**
|
|
872
|
-
* Carousel resolve: each
|
|
873
|
-
*
|
|
874
|
-
*
|
|
879
|
+
* Carousel resolve: each variable slot's key comes from `computeCarouselVarSlotKeys` (title,
|
|
880
|
+
* then description, per card; same name within a card shares one key; different cards never
|
|
881
|
+
* collide). A slot's key never depends on which tag currently fills it.
|
|
875
882
|
*/
|
|
876
|
-
const resolveCarouselTemplateWithMap = (str = '', cardIndex) => {
|
|
883
|
+
const resolveCarouselTemplateWithMap = (str = '', cardIndex, isDescField = false) => {
|
|
877
884
|
if (!str) return '';
|
|
878
885
|
const arr = splitTemplateVarStringRcs(str);
|
|
886
|
+
const fieldKeys = (isDescField
|
|
887
|
+
? computeCarouselVarSlotKeys(carouselData, rcsVarRegex)[cardIndex]?.description
|
|
888
|
+
: computeCarouselVarSlotKeys(carouselData, rcsVarRegex)[cardIndex]?.title) ?? [];
|
|
889
|
+
let varOrdinal = 0;
|
|
879
890
|
return arr.map((elem) => {
|
|
880
891
|
if (rcsVarTestRegex.test(elem)) {
|
|
881
|
-
const
|
|
882
|
-
|
|
892
|
+
const slotKey = fieldKeys[varOrdinal];
|
|
893
|
+
varOrdinal += 1;
|
|
894
|
+
const slotValue = cardVarMapped?.[slotKey];
|
|
883
895
|
if (isNil(slotValue) || String(slotValue)?.trim?.() === '') return elem;
|
|
884
896
|
return String(slotValue);
|
|
885
897
|
}
|
|
@@ -888,7 +900,7 @@ export const Rcs = (props) => {
|
|
|
888
900
|
};
|
|
889
901
|
|
|
890
902
|
const buildCarouselCardsForPreview = (cards = []) =>
|
|
891
|
-
buildCarouselCardsForPreviewUtil(cards, { isFullMode,
|
|
903
|
+
buildCarouselCardsForPreviewUtil(cards, { isFullMode, rcsVarRegex, resolveTemplateWithMap });
|
|
892
904
|
|
|
893
905
|
/**
|
|
894
906
|
* Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
|
|
@@ -1086,9 +1098,9 @@ export const Rcs = (props) => {
|
|
|
1086
1098
|
if (mediaType) {
|
|
1087
1099
|
setTemplateMediaType(mediaType);
|
|
1088
1100
|
}
|
|
1089
|
-
const tempOrientation = cardSettings
|
|
1090
|
-
const tempAlignment = cardSettings
|
|
1091
|
-
const tempHeight = mediaData
|
|
1101
|
+
const tempOrientation = cardSettings.cardOrientation;
|
|
1102
|
+
const tempAlignment = cardSettings.mediaAlignment;
|
|
1103
|
+
const tempHeight = mediaData.height;
|
|
1092
1104
|
|
|
1093
1105
|
// Map to actual RCS_IMAGE_DIMENSIONS keys
|
|
1094
1106
|
let tempDimension;
|
|
@@ -1216,56 +1228,34 @@ export const Rcs = (props) => {
|
|
|
1216
1228
|
|
|
1217
1229
|
setTemplateType(contentType.carousel);
|
|
1218
1230
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
1219
|
-
|
|
1231
|
+
// Library/campaign payloads can store cardSettings/cardContent at the flat top-level
|
|
1232
|
+
// `rcsContent` instead of nested under versions.base.content.RCS (same fallback already
|
|
1233
|
+
// used for the rich_card media lookup below, line ~1793). Without it, cardWidth/height
|
|
1234
|
+
// silently fall back to SMALL/MEDIUM, mismatching the dimensions the thumbnail was
|
|
1235
|
+
// actually uploaded at and causing object-fit:cover to crop the preview image.
|
|
1220
1236
|
const cardSettings = rcsContent?.cardSettings
|
|
1221
1237
|
|| get(details, 'rcsContent.cardSettings', {});
|
|
1222
1238
|
const cardWidth = cardSettings?.cardWidth || SMALL;
|
|
1223
1239
|
setSelectedCarouselWidth(cardWidth);
|
|
1224
1240
|
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
const cardCount = Math.max(nestedCards.length, topCards.length);
|
|
1229
|
-
const mergedCards = Array.from({ length: cardCount }, (_, idx) => ({
|
|
1230
|
-
...(topCards[idx] || {}),
|
|
1231
|
-
...(nestedCards[idx] || {}),
|
|
1232
|
-
}));
|
|
1233
|
-
const cards = cardCount > 0 ? mergedCards : topCards;
|
|
1241
|
+
const cards = Array.isArray(rcsContent?.cardContent)
|
|
1242
|
+
? rcsContent.cardContent
|
|
1243
|
+
: get(details, 'rcsContent.cardContent', []);
|
|
1234
1244
|
const firstHeight = cards?.[0]?.media?.height || MEDIUM;
|
|
1235
1245
|
setSelectedCarouselHeight(firstHeight);
|
|
1236
1246
|
setSelectedCarousel(`${firstHeight}_${cardWidth}`);
|
|
1237
1247
|
setActiveCarouselIndex('0');
|
|
1238
1248
|
|
|
1239
1249
|
if (cards.length > 1) {
|
|
1240
|
-
|
|
1241
|
-
|
|
1250
|
+
// Each card's own `cardVarMapped` is already keyed by global var position (see
|
|
1251
|
+
// computeCarouselVarSlotKeys) — a slot's key is assigned once, from where its token
|
|
1252
|
+
// sits in the template text, and never depends on which tag fills it. So merging every
|
|
1253
|
+
// card's own map directly is correct and collision-free: no per-card coalescing,
|
|
1254
|
+
// normalizing, or re-scoping needed (unlike the old name-based scoped-key scheme).
|
|
1255
|
+
setCardVarMapped((previousVarMap) => cards.reduce((merged, c = {}) => {
|
|
1242
1256
|
const rawCardMap = c?.cardVarMapped;
|
|
1243
1257
|
if (rawCardMap == null || typeof rawCardMap !== 'object') return merged;
|
|
1244
|
-
|
|
1245
|
-
const cardTitle = c?.title != null ? String(c.title) : '';
|
|
1246
|
-
const cardDesc = c?.description != null ? String(c.description) : '';
|
|
1247
|
-
const cardTokens = [
|
|
1248
|
-
...(cardTitle ? cardTitle.match(rcsVarRegex) ?? [] : []),
|
|
1249
|
-
...(cardDesc ? cardDesc.match(rcsVarRegex) ?? [] : []),
|
|
1250
|
-
];
|
|
1251
|
-
const cardOrderedTagNames = cardTokens.map((token) => getVarNameFromToken(token)).filter(Boolean);
|
|
1252
|
-
const cardMapBeforeCoalesce = isFullMode
|
|
1253
|
-
? normalizeCardVarMapped(rawCardMap, cardOrderedTagNames)
|
|
1254
|
-
: { ...rawCardMap };
|
|
1255
|
-
const cardMapAfterCoalesce = coalesceCardVarMappedToTemplate(
|
|
1256
|
-
cardMapBeforeCoalesce,
|
|
1257
|
-
cardTitle,
|
|
1258
|
-
cardDesc,
|
|
1259
|
-
rcsVarRegex,
|
|
1260
|
-
);
|
|
1261
|
-
const cardMapAfterSlotSync = !isFullMode
|
|
1262
|
-
? syncCardVarMappedSemanticsFromSlots(cardMapAfterCoalesce, cardTitle, cardDesc, rcsVarRegex)
|
|
1263
|
-
: cardMapAfterCoalesce;
|
|
1264
|
-
const namespacedCardMap = {};
|
|
1265
|
-
Object.entries(cardMapAfterSlotSync).forEach(([key, value]) => {
|
|
1266
|
-
namespacedCardMap[getCarouselVarMapKey(idx, key)] = value;
|
|
1267
|
-
});
|
|
1268
|
-
return { ...merged, ...namespacedCardMap };
|
|
1258
|
+
return { ...merged, ...rawCardMap };
|
|
1269
1259
|
}, { ...(previousVarMap || {}) }));
|
|
1270
1260
|
}
|
|
1271
1261
|
|
|
@@ -1474,29 +1464,10 @@ export const Rcs = (props) => {
|
|
|
1474
1464
|
globalActions.fetchSchemaForEntity(query);
|
|
1475
1465
|
};
|
|
1476
1466
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
*/
|
|
1482
|
-
const replaceTagAtFieldSlotOrdinal = (templateStr, slotOrdinalZeroBased, tagName) => {
|
|
1483
|
-
if (!templateStr || slotOrdinalZeroBased === null || slotOrdinalZeroBased === undefined || !tagName) {
|
|
1484
|
-
return templateStr;
|
|
1485
|
-
}
|
|
1486
|
-
let varOrdinal = 0;
|
|
1487
|
-
let replaced = false;
|
|
1488
|
-
const updatedSegments = splitTemplateVarStringRcs(templateStr).map((segment) => {
|
|
1489
|
-
if (rcsVarTestRegex.test(segment)) {
|
|
1490
|
-
const currentOrdinal = varOrdinal;
|
|
1491
|
-
varOrdinal += 1;
|
|
1492
|
-
if (currentOrdinal === slotOrdinalZeroBased) {
|
|
1493
|
-
replaced = true;
|
|
1494
|
-
return `{{${tagName}}}`;
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
return segment;
|
|
1498
|
-
});
|
|
1499
|
-
return replaced ? updatedSegments.join('') : templateStr;
|
|
1467
|
+
const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
|
|
1468
|
+
if (!templateStr || !numericVarName || !tagName) return templateStr;
|
|
1469
|
+
const placeholderRegex = buildRcsNumericMustachePlaceholderRegex(numericVarName);
|
|
1470
|
+
return templateStr.replace(placeholderRegex, `{{${tagName}}}`);
|
|
1500
1471
|
};
|
|
1501
1472
|
|
|
1502
1473
|
const onTagSelect = (selectedTagNameFromPicker, varSegmentCompositeDomId, tagAreaField) => {
|
|
@@ -1526,15 +1497,16 @@ export const Rcs = (props) => {
|
|
|
1526
1497
|
setCardVarMapped((previousCardVarMapped) => {
|
|
1527
1498
|
const updatedCardVarMapped = { ...(previousCardVarMapped || {}) };
|
|
1528
1499
|
if (isNumericPlaceholderSlot) {
|
|
1529
|
-
// Numeric slot ({{1}}, {{2}}, …) is the persisted API contract — keep the numeric key
|
|
1530
|
-
// itself (do not fork off a semantic-name key); the template text stays {{N}} below.
|
|
1531
1500
|
const existingValueBeforeAppend = (
|
|
1532
1501
|
previousCardVarMapped?.[semanticOrNumericVarName] ?? ''
|
|
1533
1502
|
).toString();
|
|
1534
1503
|
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
1535
|
-
updatedCardVarMapped[semanticOrNumericVarName]
|
|
1504
|
+
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
1505
|
+
updatedCardVarMapped[selectedTagNameFromPicker] = mappedValueAfterAppendingTag;
|
|
1536
1506
|
} else {
|
|
1537
|
-
|
|
1507
|
+
// Same semantic token (e.g. {{adv}}) in title and body must not share one map key for
|
|
1508
|
+
// "existing value" — that appends the new tag onto the other field. Match handleRcsVarChange:
|
|
1509
|
+
// read/write the global numeric slot only and drop the shared semantic key.
|
|
1538
1510
|
const existingValueBeforeAppend = cardVarMappedNumericSlotKey
|
|
1539
1511
|
? String(previousCardVarMapped?.[cardVarMappedNumericSlotKey] ?? '')
|
|
1540
1512
|
: String(previousCardVarMapped?.[semanticOrNumericVarName] ?? '');
|
|
@@ -1563,40 +1535,36 @@ export const Rcs = (props) => {
|
|
|
1563
1535
|
return updatedCardVarMapped;
|
|
1564
1536
|
});
|
|
1565
1537
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC) {
|
|
1572
|
-
// Legacy slot that already holds a named tag (e.g. {{first_name}}) is replaced by
|
|
1573
|
-
// field-local position, since a name-based replace would also overwrite other slots
|
|
1574
|
-
// sharing that same tag name.
|
|
1575
|
-
const fieldOffsetForSlot = tagAreaField === RCS_TAG_AREA_FIELD_TITLE
|
|
1576
|
-
? 0
|
|
1577
|
-
: (templateTitle?.match(rcsVarRegex) ?? []).length;
|
|
1578
|
-
const localSlotOrdinal = (globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined)
|
|
1579
|
-
? globalVarSlotIndexZeroBased - fieldOffsetForSlot
|
|
1580
|
-
: null;
|
|
1581
|
-
|
|
1538
|
+
if (
|
|
1539
|
+
isNumericPlaceholderSlot
|
|
1540
|
+
&& (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC)
|
|
1541
|
+
) {
|
|
1582
1542
|
if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE) {
|
|
1583
1543
|
setTemplateTitle((previousTitle) => {
|
|
1584
|
-
const
|
|
1585
|
-
|
|
1586
|
-
|
|
1544
|
+
const titleAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
1545
|
+
previousTitle || '',
|
|
1546
|
+
semanticOrNumericVarName,
|
|
1547
|
+
selectedTagNameFromPicker,
|
|
1548
|
+
);
|
|
1549
|
+
if (titleAfterReplacingNumericPlaceholder === previousTitle) return previousTitle;
|
|
1550
|
+
setTemplateTitleError(variableErrorHandling(titleAfterReplacingNumericPlaceholder));
|
|
1587
1551
|
// Remount segment editor: tag insert replaces {{n}} with e.g. {{tag.FORMAT_1}} — slot ids change; avoids stale UI vs manual typing in full-mode TextArea
|
|
1588
1552
|
setRcsVarSegmentEditorRemountKey((remountKey) => remountKey + 1);
|
|
1589
|
-
return
|
|
1553
|
+
return titleAfterReplacingNumericPlaceholder;
|
|
1590
1554
|
});
|
|
1591
1555
|
} else {
|
|
1592
1556
|
setTemplateDesc((previousDescription) => {
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1557
|
+
const descriptionAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
1558
|
+
previousDescription || '',
|
|
1559
|
+
semanticOrNumericVarName,
|
|
1560
|
+
selectedTagNameFromPicker,
|
|
1561
|
+
);
|
|
1562
|
+
if (descriptionAfterReplacingNumericPlaceholder === previousDescription) {
|
|
1595
1563
|
return previousDescription;
|
|
1596
1564
|
}
|
|
1597
|
-
setTemplateDescError(
|
|
1565
|
+
setTemplateDescError(variableErrorHandling(descriptionAfterReplacingNumericPlaceholder));
|
|
1598
1566
|
setRcsVarSegmentEditorRemountKey((remountKey) => remountKey + 1);
|
|
1599
|
-
return
|
|
1567
|
+
return descriptionAfterReplacingNumericPlaceholder;
|
|
1600
1568
|
});
|
|
1601
1569
|
}
|
|
1602
1570
|
}
|
|
@@ -1606,25 +1574,51 @@ export const Rcs = (props) => {
|
|
|
1606
1574
|
|
|
1607
1575
|
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
1608
1576
|
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1577
|
+
/** Field-local var ordinal (0-based, vars only) for a `{{tok}}_segIdx` composite id — scoped to
|
|
1578
|
+
* a single field's own template string (title and description are tracked independently). */
|
|
1579
|
+
const getCarouselFieldLocalVarOrdinal = (compositeId, fieldTemplateStr) => {
|
|
1580
|
+
const segments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
|
|
1581
|
+
let varOrdinal = 0;
|
|
1582
|
+
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex += 1) {
|
|
1583
|
+
if (rcsVarTestRegex.test(segments[segmentIndex])) {
|
|
1584
|
+
if (`${segments[segmentIndex]}_${segmentIndex}` === compositeId) return varOrdinal;
|
|
1585
|
+
varOrdinal += 1;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
return null;
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
// The card's placeholder (numeric {{N}} or a bare semantic token like {{first_name}}) is locked
|
|
1592
|
+
// here — picking a tag only supplies/replaces the *value* behind that slot in cardVarMapped, it
|
|
1593
|
+
// never rewrites the card's title/description text. `focusedVarId` is passed in by the caller
|
|
1594
|
+
// (title vs description each track their own last-focused slot, like titleTextAreaId/
|
|
1595
|
+
// descTextAreaId) so a click on one field's "Add Labels" can never apply to the other field's
|
|
1596
|
+
// last-focused slot. The slot's key is its global position across the whole carousel — assigned
|
|
1597
|
+
// once from where its token sits in the template text, never from whatever tag currently fills
|
|
1598
|
+
// it — so swapping tags never changes the key and never collides with another card's slot.
|
|
1599
|
+
const onCarouselTagSelect = (data, focusedVarId, isDescField) => {
|
|
1600
|
+
if (!focusedVarId) return;
|
|
1616
1601
|
const activeIdx = parseInt(activeCarouselIndex, 10);
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1602
|
+
const cardIndex = isNaN(activeIdx) ? 0 : activeIdx;
|
|
1603
|
+
const fieldTemplateStr = (isDescField
|
|
1604
|
+
? carouselData?.[cardIndex]?.description
|
|
1605
|
+
: carouselData?.[cardIndex]?.title) || '';
|
|
1606
|
+
const localVarOrdinal = getCarouselFieldLocalVarOrdinal(focusedVarId, fieldTemplateStr);
|
|
1607
|
+
if (localVarOrdinal == null) return;
|
|
1608
|
+
const slotKeys = computeCarouselVarSlotKeys(carouselData, rcsVarRegex)[cardIndex];
|
|
1609
|
+
const slotKey = (isDescField ? slotKeys?.description : slotKeys?.title)?.[localVarOrdinal];
|
|
1610
|
+
if (!slotKey) return;
|
|
1611
|
+
|
|
1612
|
+
setCardVarMapped((prev) => ({
|
|
1613
|
+
...(prev || {}),
|
|
1614
|
+
[slotKey]: `{{${data}}}`,
|
|
1615
|
+
}));
|
|
1626
1616
|
};
|
|
1627
1617
|
|
|
1618
|
+
const onCarouselTitleTagSelect = (data) => onCarouselTagSelect(data, carouselTitleFocusedVarId, false);
|
|
1619
|
+
|
|
1620
|
+
const onCarouselDescTagSelect = (data) => onCarouselTagSelect(data, carouselDescFocusedVarId, true);
|
|
1621
|
+
|
|
1628
1622
|
//removing optout tag for rcs
|
|
1629
1623
|
const getRcsTags = () => {
|
|
1630
1624
|
const tempTags = cloneDeep(tags);
|
|
@@ -1710,7 +1704,7 @@ export const Rcs = (props) => {
|
|
|
1710
1704
|
key={`${elem}_${index}`}
|
|
1711
1705
|
placeholder={`enter the value for ${elem}`}
|
|
1712
1706
|
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1713
|
-
onChange={e => textAreaValueChange(e
|
|
1707
|
+
onChange={e => textAreaValueChange(e)}
|
|
1714
1708
|
value={textAreaValue(index, type)}
|
|
1715
1709
|
onFocus={(e) => setTextAreaId(e, type)}
|
|
1716
1710
|
/>
|
|
@@ -1732,13 +1726,31 @@ export const Rcs = (props) => {
|
|
|
1732
1726
|
return renderArray;
|
|
1733
1727
|
};
|
|
1734
1728
|
const onTemplateTitleChange = ({ target: { value } }) => {
|
|
1729
|
+
let errorMessage = false;
|
|
1730
|
+
if (templateType === contentType.rich_card && !value.trim()) {
|
|
1731
|
+
errorMessage = formatMessage(messages.emptyTemplateTitleErrorMessage);
|
|
1732
|
+
} else if (value.length > TEMPLATE_TITLE_MAX_LENGTH) {
|
|
1733
|
+
errorMessage = formatMessage(messages.templateHeaderLengthError);
|
|
1734
|
+
} else {
|
|
1735
|
+
errorMessage = variableErrorHandling(value);
|
|
1736
|
+
}
|
|
1735
1737
|
setTemplateTitle(value);
|
|
1736
|
-
setTemplateTitleError(
|
|
1738
|
+
setTemplateTitleError(errorMessage);
|
|
1737
1739
|
};
|
|
1738
1740
|
|
|
1739
1741
|
const onTemplateDescChange = ({ target: { value } }) => {
|
|
1742
|
+
let errorMessage = false;
|
|
1743
|
+
if(templateType === contentType.text_message && value?.length > (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH)){
|
|
1744
|
+
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
1745
|
+
} else if(templateType === contentType.rich_card && value?.length > RCS_RICH_CARD_MAX_LENGTH){
|
|
1746
|
+
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
1747
|
+
} else {
|
|
1748
|
+
errorMessage = false;
|
|
1749
|
+
}
|
|
1750
|
+
const varError = variableErrorHandling(value);
|
|
1751
|
+
const error = errorMessage || varError;
|
|
1740
1752
|
setTemplateDesc(value);
|
|
1741
|
-
setTemplateDescError(
|
|
1753
|
+
setTemplateDescError(error);
|
|
1742
1754
|
};
|
|
1743
1755
|
|
|
1744
1756
|
|
|
@@ -1821,29 +1833,6 @@ export const Rcs = (props) => {
|
|
|
1821
1833
|
return false;
|
|
1822
1834
|
};
|
|
1823
1835
|
|
|
1824
|
-
// Shared with onTemplateTitleChange so tag-picker inserts enforce the same length limit as manual typing.
|
|
1825
|
-
const computeTemplateTitleError = (value) => {
|
|
1826
|
-
if (templateType === contentType.rich_card && !value.trim()) {
|
|
1827
|
-
return formatMessage(messages.emptyTemplateTitleErrorMessage);
|
|
1828
|
-
}
|
|
1829
|
-
if (value.length > TEMPLATE_TITLE_MAX_LENGTH) {
|
|
1830
|
-
return formatMessage(messages.templateHeaderLengthError);
|
|
1831
|
-
}
|
|
1832
|
-
return variableErrorHandling(value);
|
|
1833
|
-
};
|
|
1834
|
-
|
|
1835
|
-
// Shared with onTemplateDescChange so tag-picker inserts enforce the same length limit as manual typing.
|
|
1836
|
-
const computeTemplateDescError = (value) => {
|
|
1837
|
-
let errorMessage = false;
|
|
1838
|
-
if (templateType === contentType.text_message && value?.length > (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH)) {
|
|
1839
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
1840
|
-
} else if (templateType === contentType.rich_card && value?.length > RCS_RICH_CARD_MAX_LENGTH) {
|
|
1841
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
1842
|
-
}
|
|
1843
|
-
const varError = variableErrorHandling(value);
|
|
1844
|
-
return errorMessage || varError;
|
|
1845
|
-
};
|
|
1846
|
-
|
|
1847
1836
|
const onMessageAddVar = () => {
|
|
1848
1837
|
onAddVar(templateDesc);
|
|
1849
1838
|
};
|
|
@@ -1930,31 +1919,37 @@ const onTitleAddVar = () => {
|
|
|
1930
1919
|
|
|
1931
1920
|
// Carousel: render variable-value editor for a given template string (title/description) belonging
|
|
1932
1921
|
// to `cardIndex`. This matches rich-card/text edit behavior: static pieces are read-only, variable
|
|
1933
|
-
// tokens are editable. Each
|
|
1934
|
-
//
|
|
1935
|
-
const renderCarouselEditMessage = (templateStr, cardIndex) => {
|
|
1922
|
+
// tokens are editable. Each slot's key comes from `computeCarouselVarSlotKeys` — stable regardless
|
|
1923
|
+
// of which tag fills it, and independent even from another occurrence of the same tag name.
|
|
1924
|
+
const renderCarouselEditMessage = (templateStr, cardIndex, fieldType) => {
|
|
1936
1925
|
const renderArray = [];
|
|
1937
1926
|
const templateArr = splitTemplateVarString(templateStr);
|
|
1927
|
+
const isDescField = fieldType === RCS_TAG_AREA_FIELD_DESC;
|
|
1928
|
+
const slotKeysForCard = computeCarouselVarSlotKeys(carouselData, rcsVarRegex)[cardIndex];
|
|
1929
|
+
const fieldKeys = (isDescField ? slotKeysForCard?.description : slotKeysForCard?.title) ?? [];
|
|
1930
|
+
let varOrdinal = 0;
|
|
1938
1931
|
if (templateArr?.length) {
|
|
1939
1932
|
templateArr.forEach((elem, index) => {
|
|
1940
1933
|
if (rcsVarTestRegex.test(elem)) {
|
|
1941
1934
|
const varName = getVarNameFromToken(elem);
|
|
1942
|
-
const mapKey =
|
|
1935
|
+
const mapKey = fieldKeys[varOrdinal];
|
|
1936
|
+
varOrdinal += 1;
|
|
1943
1937
|
const computedValue = varName ? ((cardVarMapped?.[mapKey] ?? '').toString()) : '';
|
|
1944
1938
|
renderArray.push(
|
|
1945
|
-
<div
|
|
1946
|
-
key={`${elem}_${index}_${rcsVarSegmentEditorRemountKey}`}
|
|
1947
|
-
className="var-segment-message-editor__var-slot"
|
|
1948
|
-
>
|
|
1939
|
+
<div key={`${elem}_${index}`} className="var-segment-message-editor__var-slot">
|
|
1949
1940
|
<TextArea
|
|
1950
1941
|
id={`${elem}_${index}`}
|
|
1951
1942
|
placeholder={`enter the value for ${elem}`}
|
|
1952
1943
|
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1953
|
-
onChange={(e) =>
|
|
1944
|
+
onChange={(e) => carouselTextAreaValueChange(e, cardIndex, fieldType)}
|
|
1954
1945
|
value={computedValue}
|
|
1955
1946
|
onFocus={(e) => {
|
|
1956
1947
|
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1957
|
-
|
|
1948
|
+
if (fieldType === RCS_TAG_AREA_FIELD_DESC) {
|
|
1949
|
+
setCarouselDescFocusedVarId(id);
|
|
1950
|
+
} else {
|
|
1951
|
+
setCarouselTitleFocusedVarId(id);
|
|
1952
|
+
}
|
|
1958
1953
|
}}
|
|
1959
1954
|
/>
|
|
1960
1955
|
</div>
|
|
@@ -1975,7 +1970,7 @@ const onTitleAddVar = () => {
|
|
|
1975
1970
|
return <CapRow className="rcs-edit-template-message-input">{renderArray}</CapRow>;
|
|
1976
1971
|
};
|
|
1977
1972
|
|
|
1978
|
-
const textAreaValueChange = (e
|
|
1973
|
+
const textAreaValueChange = (e) => {
|
|
1979
1974
|
const value = e?.target?.value ?? '';
|
|
1980
1975
|
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1981
1976
|
if (!id) return;
|
|
@@ -1986,14 +1981,38 @@ const onTitleAddVar = () => {
|
|
|
1986
1981
|
const variableName = getVarNameFromToken(token);
|
|
1987
1982
|
|
|
1988
1983
|
if (variableName) {
|
|
1989
|
-
const
|
|
1984
|
+
const nextVal = isInvalidValue ? "" : value;
|
|
1990
1985
|
setCardVarMapped((prev) => ({
|
|
1991
1986
|
...prev,
|
|
1992
|
-
[
|
|
1987
|
+
[variableName]: nextVal,
|
|
1993
1988
|
}));
|
|
1994
1989
|
}
|
|
1995
1990
|
};
|
|
1996
1991
|
|
|
1992
|
+
// Carousel: typing directly into a slot's value box (as opposed to picking a tag) — same global
|
|
1993
|
+
// positional key as onCarouselTagSelect/renderCarouselEditMessage, so both ways of filling a
|
|
1994
|
+
// slot land on the identical key.
|
|
1995
|
+
const carouselTextAreaValueChange = (e, cardIndex, fieldType) => {
|
|
1996
|
+
const value = e?.target?.value ?? '';
|
|
1997
|
+
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1998
|
+
if (!id) return;
|
|
1999
|
+
const isDescField = fieldType === RCS_TAG_AREA_FIELD_DESC;
|
|
2000
|
+
const fieldTemplateStr = (isDescField
|
|
2001
|
+
? carouselData?.[cardIndex]?.description
|
|
2002
|
+
: carouselData?.[cardIndex]?.title) || '';
|
|
2003
|
+
const localVarOrdinal = getCarouselFieldLocalVarOrdinal(id, fieldTemplateStr);
|
|
2004
|
+
if (localVarOrdinal == null) return;
|
|
2005
|
+
const slotKeys = computeCarouselVarSlotKeys(carouselData, rcsVarRegex)[cardIndex];
|
|
2006
|
+
const mapKey = (isDescField ? slotKeys?.description : slotKeys?.title)?.[localVarOrdinal];
|
|
2007
|
+
if (!mapKey) return;
|
|
2008
|
+
const isInvalidValue = value?.trim() === "";
|
|
2009
|
+
const nextVal = isInvalidValue ? "" : value;
|
|
2010
|
+
setCardVarMapped((prev) => ({
|
|
2011
|
+
...prev,
|
|
2012
|
+
[mapKey]: nextVal,
|
|
2013
|
+
}));
|
|
2014
|
+
};
|
|
2015
|
+
|
|
1997
2016
|
const setTextAreaId = (e, type) => {
|
|
1998
2017
|
// VarSegmentMessageEditor calls onFocus(id) with a plain string; DOM events
|
|
1999
2018
|
// have an `.target.id` shape. Support both.
|
|
@@ -2031,11 +2050,6 @@ const onTitleAddVar = () => {
|
|
|
2031
2050
|
isFullMode={isFullMode}
|
|
2032
2051
|
maxButtons={MAX_BUTTONS}
|
|
2033
2052
|
host={hostName}
|
|
2034
|
-
location={location}
|
|
2035
|
-
tags={getRcsTags()}
|
|
2036
|
-
injectedTags={injectedTags || {}}
|
|
2037
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
2038
|
-
onContextChange={handleOnTagsContextChange}
|
|
2039
2053
|
/>
|
|
2040
2054
|
</>
|
|
2041
2055
|
);
|
|
@@ -2657,6 +2671,8 @@ const onTitleAddVar = () => {
|
|
|
2657
2671
|
const carouselVidDims =
|
|
2658
2672
|
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
|
|
2659
2673
|
|| RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
|
|
2674
|
+
// Debug log for embedded/library mode preview payload (carousel)
|
|
2675
|
+
// eslint-disable-next-line no-console
|
|
2660
2676
|
return (
|
|
2661
2677
|
<UnifiedPreview
|
|
2662
2678
|
channel={RCS}
|
|
@@ -2840,55 +2856,40 @@ const onTitleAddVar = () => {
|
|
|
2840
2856
|
const cardContent = rcsForTest.rcsContent?.cardContent;
|
|
2841
2857
|
if (Array.isArray(cardContent) && cardContent[0]) {
|
|
2842
2858
|
if (isCarouselType) {
|
|
2843
|
-
// Carousel: resolve {{N}}
|
|
2844
|
-
//
|
|
2859
|
+
// Carousel: resolve {{N}} slot tokens to the actual tag expressions / static values the
|
|
2860
|
+
// user mapped via cardVarMapped. Run this for ALL modes (create, edit, consumer) so that:
|
|
2845
2861
|
// - buildRcsTestMessagePayload sends real Capillary tag names to the test API, and
|
|
2846
2862
|
// - prepareTagExtractionPayload can extract tag metadata from the card content.
|
|
2847
|
-
// Each
|
|
2848
|
-
//
|
|
2849
|
-
// a key, so there is nothing to accidentally collide).
|
|
2863
|
+
// Each variable slot's key comes from computeCarouselVarSlotKeys, so two cards' variables
|
|
2864
|
+
// never share a key even when they pick the same tag name for their own slot.
|
|
2850
2865
|
rcsForTest = {
|
|
2851
2866
|
...rcsForTest,
|
|
2852
2867
|
rcsContent: {
|
|
2853
2868
|
...rcsForTest.rcsContent,
|
|
2854
|
-
cardContent: cardContent.map((card
|
|
2869
|
+
cardContent: cardContent.map((card) => {
|
|
2855
2870
|
const rawTitle = card.title || '';
|
|
2856
2871
|
const rawDesc = card.description || '';
|
|
2857
|
-
const resolvedTitle = resolveCarouselTemplateWithMap(rawTitle, cardIndex);
|
|
2858
|
-
const resolvedDesc = resolveCarouselTemplateWithMap(rawDesc, cardIndex);
|
|
2872
|
+
const resolvedTitle = resolveCarouselTemplateWithMap(rawTitle, cardIndex, false);
|
|
2873
|
+
const resolvedDesc = resolveCarouselTemplateWithMap(rawDesc, cardIndex, true);
|
|
2859
2874
|
return { ...card, title: resolvedTitle, description: resolvedDesc };
|
|
2860
2875
|
}),
|
|
2861
2876
|
},
|
|
2862
2877
|
};
|
|
2863
2878
|
} else if (isSlotMappingModeForPreview) {
|
|
2864
2879
|
// Standalone card: coalesce cardVarMapped with the template's slot names so the preview
|
|
2865
|
-
// API receives a correctly-keyed var map for non-carousel templates.
|
|
2866
|
-
// {{N}} slot tokens in title/description themselves (mirrors the carousel branch above) —
|
|
2867
|
-
// otherwise prepareTagExtractionPayload/getRcsPrimaryTagExtractionText send the raw "{{1}}"
|
|
2868
|
-
// token as template text, which the extract-tags API doesn't recognize, so the
|
|
2869
|
-
// synthetic-tag fallback names the row "1" instead of the real tag.
|
|
2880
|
+
// API receives a correctly-keyed var map for non-carousel templates.
|
|
2870
2881
|
const fullCardVarMapped = coalesceCardVarMappedToTemplate(
|
|
2871
2882
|
pickRcsCardVarMappedEntries(cardVarMapped),
|
|
2872
2883
|
templateTitle,
|
|
2873
2884
|
templateDesc,
|
|
2874
2885
|
rcsVarRegex,
|
|
2875
2886
|
);
|
|
2876
|
-
const rawStandaloneTitle = cardContent[0].title || '';
|
|
2877
|
-
const rawStandaloneDesc = cardContent[0].description || '';
|
|
2878
|
-
const standaloneTitleVarCount = (rawStandaloneTitle.match(rcsVarRegex) || []).length;
|
|
2879
|
-
const resolvedStandaloneTitle = resolveTemplateWithMap(rawStandaloneTitle, 0);
|
|
2880
|
-
const resolvedStandaloneDesc = resolveTemplateWithMap(rawStandaloneDesc, standaloneTitleVarCount);
|
|
2881
2887
|
rcsForTest = {
|
|
2882
2888
|
...rcsForTest,
|
|
2883
2889
|
rcsContent: {
|
|
2884
2890
|
...rcsForTest.rcsContent,
|
|
2885
2891
|
cardContent: [
|
|
2886
|
-
{
|
|
2887
|
-
...cardContent[0],
|
|
2888
|
-
title: resolvedStandaloneTitle,
|
|
2889
|
-
description: resolvedStandaloneDesc,
|
|
2890
|
-
cardVarMapped: fullCardVarMapped,
|
|
2891
|
-
},
|
|
2892
|
+
{ ...cardContent[0], cardVarMapped: fullCardVarMapped },
|
|
2892
2893
|
...cardContent.slice(1),
|
|
2893
2894
|
],
|
|
2894
2895
|
},
|
|
@@ -2904,23 +2905,16 @@ const onTitleAddVar = () => {
|
|
|
2904
2905
|
},
|
|
2905
2906
|
},
|
|
2906
2907
|
};
|
|
2907
|
-
|
|
2908
|
-
//
|
|
2909
|
-
//
|
|
2910
|
-
//
|
|
2911
|
-
//
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
const smsFallbackTcFromPayload = smsFallbackRecordForTest?.templateConfigs;
|
|
2915
|
-
const registeredSenderIdsForTest = extractRegisteredSenderIdsFromSmsFallbackRecord(smsFallbackRecordForTest);
|
|
2916
|
-
const hasRegisteredSenderIdsForTest =
|
|
2917
|
-
Array.isArray(registeredSenderIdsForTest) && registeredSenderIdsForTest.length > 0;
|
|
2918
|
-
if ((smsFallbackTcFromPayload && typeof smsFallbackTcFromPayload === 'object') || hasRegisteredSenderIdsForTest) {
|
|
2908
|
+
// Use the smsFallbackTemplateConfigs already built by createPayload() / buildSmsFallBackContentForPayload —
|
|
2909
|
+
// it merges the API baseline (templateId, registeredSenderIds, templateName, etc.) with local
|
|
2910
|
+
// smsFallbackData state. Reading raw smsFallbackData here would miss the API-sourced smsTemplateId
|
|
2911
|
+
// in campaign mode. This also keeps templateName (and all other fields) in sync with the approval
|
|
2912
|
+
// payload automatically.
|
|
2913
|
+
const smsFallbackTcFromPayload = rcs?.rcsContent?.smsFallBackContent?.templateConfigs;
|
|
2914
|
+
if (smsFallbackTcFromPayload && typeof smsFallbackTcFromPayload === 'object') {
|
|
2919
2915
|
out.templateConfigs = {
|
|
2920
|
-
...
|
|
2921
|
-
|
|
2922
|
-
// Real registered sender IDs mean filtering should happen regardless of the smsRegister heuristic.
|
|
2923
|
-
traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister) || hasRegisteredSenderIdsForTest,
|
|
2916
|
+
...smsFallbackTcFromPayload,
|
|
2917
|
+
traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
|
|
2924
2918
|
};
|
|
2925
2919
|
}
|
|
2926
2920
|
return out;
|
|
@@ -3066,12 +3060,16 @@ const onTitleAddVar = () => {
|
|
|
3066
3060
|
['title', 'description'].some((field) => !(card?.[field] || '').trim())
|
|
3067
3061
|
);
|
|
3068
3062
|
if (hasEmptyField) return true;
|
|
3063
|
+
const slotKeysByCard = computeCarouselVarSlotKeys(carouselData, rcsVarRegex);
|
|
3069
3064
|
const unfilledVar = (carouselData || []).some((card, cardIndex) =>
|
|
3070
3065
|
['title', 'description'].some((field) => {
|
|
3066
|
+
const isDescField = field === 'description';
|
|
3071
3067
|
const tokens = splitTemplateVarStringRcs(card?.[field] || '').filter((token) => rcsVarTestRegex.test(token));
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3068
|
+
if (tokens.length === 0) return false;
|
|
3069
|
+
const fieldKeys = (isDescField ? slotKeysByCard[cardIndex]?.description : slotKeysByCard[cardIndex]?.title) ?? [];
|
|
3070
|
+
return tokens.some((token, localVarOrdinal) => {
|
|
3071
|
+
const slotKey = fieldKeys[localVarOrdinal];
|
|
3072
|
+
const slotValue = cardVarMapped?.[slotKey];
|
|
3075
3073
|
return slotValue == null || String(slotValue).trim() === '';
|
|
3076
3074
|
});
|
|
3077
3075
|
})
|
|
@@ -3079,19 +3077,6 @@ const onTitleAddVar = () => {
|
|
|
3079
3077
|
return unfilledVar;
|
|
3080
3078
|
};
|
|
3081
3079
|
|
|
3082
|
-
// Mirror WhatsApp's isEditDoneDisabled: block Done while a CTA button's dynamic URL still
|
|
3083
|
-
// holds the unresolved {{1}} placeholder (no real personalization tag assigned yet).
|
|
3084
|
-
const hasUnresolvedDynamicCtaUrl = () =>
|
|
3085
|
-
isCarouselType
|
|
3086
|
-
? (carouselData || []).some((card) =>
|
|
3087
|
-
(card?.suggestions || []).some((suggestion) =>
|
|
3088
|
-
suggestion?.type === CTA && (suggestion?.url || '').includes('{{1}}')
|
|
3089
|
-
)
|
|
3090
|
-
)
|
|
3091
|
-
: suggestions.some((suggestion) =>
|
|
3092
|
-
suggestion?.type === CTA && (suggestion?.url || '').includes('{{1}}')
|
|
3093
|
-
);
|
|
3094
|
-
|
|
3095
3080
|
const isDisableDone = () => {
|
|
3096
3081
|
if(isEditFlow){
|
|
3097
3082
|
return false;
|
|
@@ -3133,9 +3118,6 @@ const onTitleAddVar = () => {
|
|
|
3133
3118
|
return true;
|
|
3134
3119
|
}
|
|
3135
3120
|
}
|
|
3136
|
-
if (hasUnresolvedDynamicCtaUrl()) {
|
|
3137
|
-
return true;
|
|
3138
|
-
}
|
|
3139
3121
|
if (templateDescError || templateTitleError) {
|
|
3140
3122
|
return true;
|
|
3141
3123
|
}
|
|
@@ -3180,9 +3162,6 @@ const onTitleAddVar = () => {
|
|
|
3180
3162
|
return true;
|
|
3181
3163
|
}
|
|
3182
3164
|
}
|
|
3183
|
-
if (hasUnresolvedDynamicCtaUrl()) {
|
|
3184
|
-
return true;
|
|
3185
|
-
}
|
|
3186
3165
|
if (templateTitleError || templateDescError) {
|
|
3187
3166
|
return true;
|
|
3188
3167
|
}
|