@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
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
7
7
|
REGEX_SPECIAL_CHARS_ESCAPE_PATTERN,
|
|
8
8
|
RCS_STRIP_MUSTACHE_DELIMITERS_REGEX,
|
|
9
|
-
RCS_MUSTACHE_WRAPPED_REGEX,
|
|
10
9
|
} from './constants';
|
|
11
10
|
import './index.scss';
|
|
12
11
|
// import { formatMessage } from '../../../utils/intl';
|
|
@@ -197,6 +196,50 @@ export function coalesceCardVarMappedToTemplate(
|
|
|
197
196
|
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
198
197
|
const semanticVarName = getVarNameFromToken(token);
|
|
199
198
|
if (!semanticVarName) return;
|
|
199
|
+
|
|
200
|
+
if (RCS_NUMERIC_VAR_NAME_REGEX.test(semanticVarName)) {
|
|
201
|
+
// Carousel numeric placeholder ({{3}}, {{4}}, …): the slot key IS the token digit itself —
|
|
202
|
+
// numbers are allocated globally across the whole carousel (getNextCarouselVarToken), not
|
|
203
|
+
// re-numbered per card, so the position-based `slotIndexZeroBased + 1` key below would target
|
|
204
|
+
// the wrong slot for every card after the first (e.g. card 2's first token is digit "3", not "1").
|
|
205
|
+
const numericSlotKey = semanticVarName;
|
|
206
|
+
const trimmedSlotValue = String(lookupSourceMap[numericSlotKey] ?? '').trim();
|
|
207
|
+
coalescedMap[numericSlotKey] = trimmedSlotValue;
|
|
208
|
+
// The slot's value may itself be a mustache-wrapped tag reference (e.g. "3" -> "{{last_name}}")
|
|
209
|
+
// — mirror it onto the bare semantic key too so VarSegment editors keyed by tag name also
|
|
210
|
+
// prepopulate (same rule as syncCardVarMappedSemanticsFromSlots).
|
|
211
|
+
const mustacheInnerMatch = trimmedSlotValue.match(/^\{\{([^}]+)\}\}$/);
|
|
212
|
+
const innerSemanticName = mustacheInnerMatch?.[1]?.trim();
|
|
213
|
+
if (innerSemanticName && innerSemanticName !== numericSlotKey) {
|
|
214
|
+
const existingInnerValue = String(coalescedMap[innerSemanticName] ?? '').trim();
|
|
215
|
+
if (!existingInnerValue) {
|
|
216
|
+
coalescedMap[innerSemanticName] = trimmedSlotValue;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// A numeric slot may already self-reference this exact tag (e.g. "4" -> "{{last_name}}") when
|
|
223
|
+
// the template's own numeric placeholder has since been resolved to this tag in the text (see
|
|
224
|
+
// mapRcsCardContentForConsumerWithResolvedTags). That numeric key is the source of truth — don't
|
|
225
|
+
// fall through to the position-based legacy lookup below, which targets an unrelated (or
|
|
226
|
+
// nonexistent) slot and would clobber the real value with an empty string.
|
|
227
|
+
const mustacheSelfReference = `{{${semanticVarName}}}`;
|
|
228
|
+
const existingNumericKeyForSemantic = Object.keys(lookupSourceMap).find((key) => (
|
|
229
|
+
RCS_NUMERIC_VAR_NAME_REGEX.test(key) && lookupSourceMap[key] === mustacheSelfReference
|
|
230
|
+
));
|
|
231
|
+
if (existingNumericKeyForSemantic) {
|
|
232
|
+
const trimmedSlotValue = String(lookupSourceMap[existingNumericKeyForSemantic] ?? '').trim();
|
|
233
|
+
coalescedMap[existingNumericKeyForSemantic] = trimmedSlotValue;
|
|
234
|
+
if (!seenSemanticVarNames.has(semanticVarName)) {
|
|
235
|
+
seenSemanticVarNames.add(semanticVarName);
|
|
236
|
+
coalescedMap[semanticVarName] = trimmedSlotValue;
|
|
237
|
+
}
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Legacy: template embeds the semantic token directly ({{user_name}}); older payloads stored
|
|
242
|
+
// the resolved value under a position-based numeric key ("1", "2", …) by order of appearance.
|
|
200
243
|
const numericSlotKey = String(slotIndexZeroBased + 1);
|
|
201
244
|
const isRepeatOfSemanticName = seenSemanticVarNames.has(semanticVarName);
|
|
202
245
|
const skipSharedSemanticLookup =
|
|
@@ -207,12 +250,6 @@ export function coalesceCardVarMappedToTemplate(
|
|
|
207
250
|
valueFromSource = lookupSourceMap[semanticVarName];
|
|
208
251
|
}
|
|
209
252
|
}
|
|
210
|
-
if (valueFromSource === undefined || valueFromSource === null) {
|
|
211
|
-
valueFromSource = lookupSourceMap[String(slotIndexZeroBased + 1)];
|
|
212
|
-
}
|
|
213
|
-
if (valueFromSource === undefined || valueFromSource === null) {
|
|
214
|
-
valueFromSource = lookupSourceMap[slotIndexZeroBased + 1];
|
|
215
|
-
}
|
|
216
253
|
const trimmedSlotValue = valueFromSource == null ? '' : String(valueFromSource).trim();
|
|
217
254
|
coalescedMap[numericSlotKey] = trimmedSlotValue;
|
|
218
255
|
if (!seenSemanticVarNames.has(semanticVarName)) {
|
|
@@ -291,11 +328,6 @@ export function isRcsTextOnlyCardMediaType(mediaType) {
|
|
|
291
328
|
*
|
|
292
329
|
* @param {boolean} [preserveLiteralValueTokens=false] When true, a slot value that is NOT itself a
|
|
293
330
|
* `{{...}}`-wrapped tag (e.g. a literal preview string like "SAVE20" typed for a voucher/offer tag)
|
|
294
|
-
* is left as the raw token instead of being baked into the returned string. Personalization-style
|
|
295
|
-
* values (themselves another mustache tag, e.g. `{{loyalty_points}}`) still resolve either way — those
|
|
296
|
-
* are internal-alias renames, not literal content. Pass true for payloads that get persisted/replayed
|
|
297
|
-
* (see `mapRcsCardContentForConsumerWithResolvedTags`) so a voucher's saved token survives for the
|
|
298
|
-
* editor to re-render an editable slot from, and for the backend to resolve per-recipient at send time.
|
|
299
331
|
*
|
|
300
332
|
* @param {number} [cardSlotOffset=0] Starting global slot index for this card's title. Carousel cards
|
|
301
333
|
* beyond index 0 must pass the running total of every prior card's var count — numeric `{{N}}` tokens
|
|
@@ -309,8 +341,6 @@ export function resolveRcsCardPreviewStrings(
|
|
|
309
341
|
cardVarMapped,
|
|
310
342
|
isLibraryMode = false,
|
|
311
343
|
textOnlyCard = false,
|
|
312
|
-
preserveLiteralValueTokens = false,
|
|
313
|
-
cardSlotOffset = 0,
|
|
314
344
|
) {
|
|
315
345
|
const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
|
|
316
346
|
const getVarNameFromToken = (token = '') =>
|
|
@@ -339,7 +369,12 @@ export function resolveRcsCardPreviewStrings(
|
|
|
339
369
|
);
|
|
340
370
|
if (slotValue == null || String(slotValue).trim() === '') return elem;
|
|
341
371
|
const trimmedSlotValue = String(slotValue).trim();
|
|
342
|
-
|
|
372
|
+
const isPlainNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(key);
|
|
373
|
+
if (
|
|
374
|
+
preserveLiteralValueTokens
|
|
375
|
+
&& !isPlainNumericSlot
|
|
376
|
+
&& !RCS_MUSTACHE_WRAPPED_REGEX.test(trimmedSlotValue)
|
|
377
|
+
) {
|
|
343
378
|
return elem;
|
|
344
379
|
}
|
|
345
380
|
return String(slotValue);
|
|
@@ -354,8 +389,8 @@ export function resolveRcsCardPreviewStrings(
|
|
|
354
389
|
? 0
|
|
355
390
|
: (effectiveTitle.match(rcsVarRegex) || []).length;
|
|
356
391
|
return {
|
|
357
|
-
rcsTitle: textOnlyCard ? '' : resolveTemplateWithMap(effectiveTitle,
|
|
358
|
-
rcsDesc: resolveTemplateWithMap(String(description || ''),
|
|
392
|
+
rcsTitle: textOnlyCard ? '' : resolveTemplateWithMap(effectiveTitle, 0),
|
|
393
|
+
rcsDesc: resolveTemplateWithMap(String(description || ''), titleVarCount),
|
|
359
394
|
};
|
|
360
395
|
}
|
|
361
396
|
|
|
@@ -376,7 +411,6 @@ export function mapRcsCardContentForConsumerWithResolvedTags(
|
|
|
376
411
|
: {};
|
|
377
412
|
const list = Array.isArray(cardContentArray) ? cardContentArray : [];
|
|
378
413
|
const isLibraryMode = isFullMode !== true;
|
|
379
|
-
let carouselSlotOffset = 0;
|
|
380
414
|
return list.map((card) => {
|
|
381
415
|
if (!card || typeof card !== 'object') return card;
|
|
382
416
|
const nested =
|
|
@@ -387,22 +421,15 @@ export function mapRcsCardContentForConsumerWithResolvedTags(
|
|
|
387
421
|
const nestedClean = pickRcsCardVarMappedEntries(nested);
|
|
388
422
|
const merged = { ...rootClean, ...nestedClean };
|
|
389
423
|
const textOnly = isRcsTextOnlyCardMediaType(card.mediaType);
|
|
390
|
-
const cardTitle = card.title ?? '';
|
|
391
|
-
const cardDescription = card.description ?? '';
|
|
392
424
|
const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
|
|
393
|
-
|
|
394
|
-
|
|
425
|
+
card.title ?? '',
|
|
426
|
+
card.description ?? '',
|
|
395
427
|
merged,
|
|
396
428
|
isLibraryMode,
|
|
397
429
|
textOnly,
|
|
398
|
-
true,
|
|
399
|
-
carouselSlotOffset,
|
|
400
430
|
);
|
|
401
|
-
const effectiveTitleForCount = textOnly ? '' : String(cardTitle);
|
|
402
|
-
const titleVarCount = (effectiveTitleForCount.match(rcsVarRegex) || []).length;
|
|
403
|
-
const descVarCount = (String(cardDescription).match(rcsVarRegex) || []).length;
|
|
404
|
-
carouselSlotOffset += titleVarCount + descVarCount;
|
|
405
431
|
const { cardVarMapped: _drop, ...cardRest } = card;
|
|
432
|
+
|
|
406
433
|
return {
|
|
407
434
|
...cardRest,
|
|
408
435
|
title: rcsTitle,
|
|
@@ -169,11 +169,12 @@ export const SmsTraiEdit = (props) => {
|
|
|
169
169
|
return traiDltEnabled || hasTraiDltFeature() || isRcsEditFlow;
|
|
170
170
|
}, [isRcsSmsFallback, traiDltEnabled, isRcsEditFlow]);
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
/**
|
|
173
|
+
* RCS SMS fallback: always show character count vs TRAI max (`SMS_TRAI_CONTENT_MAX_LENGTH`).
|
|
174
|
+
* Do not use `totalCharacters` ({smsCount} SMS via length/160) here: resolved template + variable
|
|
175
|
+
* values can exceed one GSM segment while DLT still shows a single registered template — length/160
|
|
176
|
+
* is only a rough segment hint and reads as “wrong SMS count” in campaigns.
|
|
177
|
+
*/
|
|
177
178
|
const renderDescriptionCharacterCount = (className = "rcs-character-count") => (
|
|
178
179
|
<CapLabel type="label1" className={className}>
|
|
179
180
|
{formatMessage(messages.charactersCountLabel, {
|
|
@@ -195,24 +196,18 @@ export const SmsTraiEdit = (props) => {
|
|
|
195
196
|
};
|
|
196
197
|
|
|
197
198
|
const renderRcsFallbackMessage = (str = '') => {
|
|
198
|
-
if (!
|
|
199
|
+
if (!useRcsFallbackVarSegment) {
|
|
199
200
|
return (
|
|
200
201
|
<CapRow className="rcs-create-template-message-input">
|
|
201
202
|
<div className="rcs_text_area_wrapper">
|
|
202
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (hasVarToken) setRcsFallbackVarSegmentModeActive(true);
|
|
211
|
-
}}
|
|
212
|
-
placeholder={formatMessage(rcsMessages.fallbackMsgPlaceholder)}
|
|
213
|
-
data-testid="rcs_fallback_plain_text_area"
|
|
214
|
-
/>
|
|
215
|
-
</div>
|
|
203
|
+
<TextArea
|
|
204
|
+
id="rcs_fallback_message_textarea"
|
|
205
|
+
autosize={{ minRows: 4, maxRows: 12 }}
|
|
206
|
+
value={fallbackText}
|
|
207
|
+
onChange={(e) => setFallbackText(e.target.value)}
|
|
208
|
+
placeholder={formatMessage(rcsMessages.fallbackMsgPlaceholder)}
|
|
209
|
+
data-testid="rcs_fallback_plain_text_area"
|
|
210
|
+
/>
|
|
216
211
|
{renderDescriptionCharacterCount()}
|
|
217
212
|
</div>
|
|
218
213
|
</CapRow>
|
|
@@ -367,9 +362,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
367
362
|
const templateBase = get(activeTemplateSourceForInit, 'versions.base', {});
|
|
368
363
|
const unicodeValidity = get(templateBase, 'unicode-validity', true);
|
|
369
364
|
const templateMsg = get(activeTemplateSourceForInit, 'versions.base.sms-editor', '') || '';
|
|
370
|
-
|
|
371
|
-
setRcsFallbackVarSegmentModeActive(templateMsgHasVarToken);
|
|
372
|
-
if (!templateMsgHasVarToken) {
|
|
365
|
+
if (!useRcsFallbackVarSegment) {
|
|
373
366
|
setFallbackText(templateMsg);
|
|
374
367
|
setFallbackVarMappedData({});
|
|
375
368
|
setUpdatedSmsEditor(String(templateMsg).split(''));
|
|
@@ -436,7 +429,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
436
429
|
|
|
437
430
|
useEffect(() => {
|
|
438
431
|
if (!isRcsSmsFallback) return;
|
|
439
|
-
if (!
|
|
432
|
+
if (!useRcsFallbackVarSegment) {
|
|
440
433
|
const plainFallbackSmsText = fallbackText || '';
|
|
441
434
|
setUpdatedSmsEditor(plainFallbackSmsText.split(''));
|
|
442
435
|
setTotalMessageLength(plainFallbackSmsText.length);
|
|
@@ -448,17 +441,15 @@ export const SmsTraiEdit = (props) => {
|
|
|
448
441
|
);
|
|
449
442
|
setUpdatedSmsEditor(resolvedFallbackDisplay.split(''));
|
|
450
443
|
setTotalMessageLength(resolvedFallbackDisplay.length);
|
|
451
|
-
}, [isRcsSmsFallback,
|
|
444
|
+
}, [isRcsSmsFallback, useRcsFallbackVarSegment, fallbackText, fallbackVarMappedData]);
|
|
452
445
|
|
|
453
446
|
useEffect(() => {
|
|
454
447
|
if (!isRcsSmsFallback) return;
|
|
455
|
-
// Skip until hydration populates fallbackVarMappedData, or this wipes the parent's rcsSmsFallbackVarMapped before init restores it.
|
|
456
|
-
if (loading) return;
|
|
457
448
|
if (typeof onRcsFallbackEditorStateChange !== 'function') return;
|
|
458
449
|
onRcsFallbackEditorStateChange({
|
|
459
450
|
rcsSmsFallbackVarMapped: fallbackVarMappedData || {},
|
|
460
451
|
});
|
|
461
|
-
}, [isRcsSmsFallback,
|
|
452
|
+
}, [isRcsSmsFallback, fallbackVarMappedData, onRcsFallbackEditorStateChange]);
|
|
462
453
|
|
|
463
454
|
useEffect(() => {
|
|
464
455
|
if (!isRcsSmsFallback) return;
|
|
@@ -541,7 +532,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
541
532
|
// TRAI/DLT VarSegment: `validateIfTagClosed` only understands paired `{{…}}` and breaks on
|
|
542
533
|
// legitimate `{#…#}` / mixed TRAI shapes (extra `{`/`}` counts). Do not tie Done to it;
|
|
543
534
|
// slot completeness is enforced by `areAllRcsSmsFallbackVarSlotsFilled` on the RCS screen.
|
|
544
|
-
if (
|
|
535
|
+
if (useRcsFallbackVarSegment) {
|
|
545
536
|
tagValidationResponseRef.current = {};
|
|
546
537
|
updateIsTagValidationError(false);
|
|
547
538
|
return;
|
|
@@ -582,7 +573,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
582
573
|
tags,
|
|
583
574
|
isRcsSmsFallback,
|
|
584
575
|
isFullMode,
|
|
585
|
-
|
|
576
|
+
useRcsFallbackVarSegment,
|
|
586
577
|
fallbackText,
|
|
587
578
|
fallbackVarMappedData,
|
|
588
579
|
getDefaultTags,
|
|
@@ -721,8 +712,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
721
712
|
...baseWithoutDerivedFields,
|
|
722
713
|
'sms-editor': fallbackText || '',
|
|
723
714
|
'unicode-validity': isUnicodeAllowed,
|
|
724
|
-
|
|
725
|
-
...(useRcsFallbackVarSegmentForCurrentText && {
|
|
715
|
+
...(useRcsFallbackVarSegment && {
|
|
726
716
|
'rcs-sms-fallback-var-mapped': fallbackVarMappedData || {},
|
|
727
717
|
}),
|
|
728
718
|
};
|
|
@@ -788,9 +778,8 @@ export const SmsTraiEdit = (props) => {
|
|
|
788
778
|
|
|
789
779
|
const onTagSelect = (data) => {
|
|
790
780
|
if (isRcsSmsFallback) {
|
|
791
|
-
if (!
|
|
781
|
+
if (!useRcsFallbackVarSegment) {
|
|
792
782
|
setFallbackText((prev) => `${prev || ''}{{${data}}}`);
|
|
793
|
-
setRcsFallbackVarSegmentModeActive(true);
|
|
794
783
|
return;
|
|
795
784
|
}
|
|
796
785
|
if (!fallbackFocusedId) return;
|
|
@@ -960,7 +949,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
960
949
|
|
|
961
950
|
const calculateTotalMessageLength = () => {
|
|
962
951
|
if (isRcsSmsFallback) {
|
|
963
|
-
if (
|
|
952
|
+
if (useRcsFallbackVarSegment) {
|
|
964
953
|
const resolved = getFallbackResolvedContent(fallbackText || '', fallbackVarMappedData || {});
|
|
965
954
|
setTotalMessageLength(resolved.length);
|
|
966
955
|
} else {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
@import '~@capillarytech/cap-ui-library/styles/_variables.scss';
|
|
2
2
|
|
|
3
3
|
.ant-tabs-content{
|
|
4
|
-
margin-top: $CAP_SPACE_08;
|
|
5
4
|
// .creatives-templates-list.full-mode{
|
|
6
5
|
.v2-pagination-container, .v2-pagination-container-half {
|
|
7
6
|
.ant-tabs-tabpane-active{
|
|
@@ -1428,6 +1427,7 @@
|
|
|
1428
1427
|
justify-content: space-between;
|
|
1429
1428
|
align-items: center;
|
|
1430
1429
|
gap: $CAP_SPACE_08;
|
|
1430
|
+
margin-right: $CAP_SPACE_32;
|
|
1431
1431
|
}
|
|
1432
1432
|
|
|
1433
1433
|
.template-listing-more-btn {
|
|
@@ -4603,7 +4603,6 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4603
4603
|
<CapButton
|
|
4604
4604
|
className={`create-new-${channelLowerCase} margin-l-8 margin-b-12`}
|
|
4605
4605
|
type={"primary"}
|
|
4606
|
-
isAddBtn
|
|
4607
4606
|
disabled={this.isCreateDisabled()}
|
|
4608
4607
|
onClick={this.createTemplate}
|
|
4609
4608
|
>
|
|
@@ -239,7 +239,6 @@ exports[`Test Templates container Should render sms illustration when no templat
|
|
|
239
239
|
<CapButton
|
|
240
240
|
className="create-new-sms margin-l-8 margin-b-12"
|
|
241
241
|
disabled={false}
|
|
242
|
-
isAddBtn={true}
|
|
243
242
|
onClick={[Function]}
|
|
244
243
|
type="primary"
|
|
245
244
|
>
|
|
@@ -814,7 +813,6 @@ exports[`Test Templates container Should render temlates when whatsapp templates
|
|
|
814
813
|
<CapButton
|
|
815
814
|
className="create-new-whatsapp margin-l-8 margin-b-12"
|
|
816
815
|
disabled={false}
|
|
817
|
-
isAddBtn={true}
|
|
818
816
|
onClick={[Function]}
|
|
819
817
|
type="primary"
|
|
820
818
|
>
|
|
@@ -1225,7 +1223,6 @@ exports[`Test Templates container Test max templates exceeded 1`] = `
|
|
|
1225
1223
|
<CapButton
|
|
1226
1224
|
className="create-new-whatsapp margin-l-8 margin-b-12"
|
|
1227
1225
|
disabled={false}
|
|
1228
|
-
isAddBtn={true}
|
|
1229
1226
|
onClick={[Function]}
|
|
1230
1227
|
type="primary"
|
|
1231
1228
|
>
|
|
@@ -1699,7 +1696,6 @@ exports[`Test Templates container Test max templates not exceeded 1`] = `
|
|
|
1699
1696
|
<CapButton
|
|
1700
1697
|
className="create-new-whatsapp margin-l-8 margin-b-12"
|
|
1701
1698
|
disabled={false}
|
|
1702
|
-
isAddBtn={true}
|
|
1703
1699
|
onClick={[Function]}
|
|
1704
1700
|
type="primary"
|
|
1705
1701
|
>
|
|
@@ -2173,7 +2169,6 @@ exports[`Test Templates container Test max templates warning 1`] = `
|
|
|
2173
2169
|
<CapButton
|
|
2174
2170
|
className="create-new-whatsapp margin-l-8 margin-b-12"
|
|
2175
2171
|
disabled={false}
|
|
2176
|
-
isAddBtn={true}
|
|
2177
2172
|
onClick={[Function]}
|
|
2178
2173
|
type="primary"
|
|
2179
2174
|
>
|
|
@@ -10,9 +10,10 @@ export default css`
|
|
|
10
10
|
|
|
11
11
|
.component-wrapper {
|
|
12
12
|
${(props) => props.isFullMode ? `
|
|
13
|
+
max-width: 71.25rem;
|
|
13
14
|
margin: 0 auto;
|
|
14
15
|
width: 100%;
|
|
15
|
-
padding: 0;
|
|
16
|
+
padding: 0.714rem 0;
|
|
16
17
|
/* Only main channel tabs content, not HTML Editor validation panel tabs */
|
|
17
18
|
> .cap-tab-v2 > .ant-tabs-content-holder > .ant-tabs-content,
|
|
18
19
|
> .cap-tab-v2 > .ant-tabs-content {
|
|
@@ -96,12 +97,6 @@ export default css`
|
|
|
96
97
|
`;
|
|
97
98
|
|
|
98
99
|
export const CapTabStyle = css`
|
|
99
|
-
${(props) => props.isFullMode ? `margin-top: ${CAP_SPACE_24}
|
|
100
|
-
}
|
|
101
|
-
.ant-tabs-nav-list {
|
|
102
|
-
gap: 2rem;
|
|
103
|
-
}
|
|
104
|
-
.ant-tabs-tab + .ant-tabs-tab {
|
|
105
|
-
margin: 0 !important;
|
|
100
|
+
${(props) => props.isFullMode ? `margin-top: ${CAP_SPACE_24}` : ``
|
|
106
101
|
}
|
|
107
102
|
`;
|
|
@@ -10,7 +10,7 @@ import { connect } from 'react-redux';
|
|
|
10
10
|
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
|
|
11
11
|
import { createStructuredSelector } from 'reselect';
|
|
12
12
|
import { bindActionCreators, compose } from 'redux';
|
|
13
|
-
import { CapTab, CapCustomCard, CapButton, CapIcon, CapSpin, CapTooltip } from '@capillarytech/cap-ui-library';
|
|
13
|
+
import { CapTab, CapCustomCard, CapButton, CapHeader, CapIcon, CapSpin, CapTooltip } from '@capillarytech/cap-ui-library';
|
|
14
14
|
import { find, get, pick } from 'lodash';
|
|
15
15
|
import Helmet from 'react-helmet';
|
|
16
16
|
|
|
@@ -47,9 +47,6 @@ import {
|
|
|
47
47
|
NORMALIZED_CHANNEL_ALIASES,
|
|
48
48
|
SMS,
|
|
49
49
|
} from "../CreativesContainer/constants";
|
|
50
|
-
import { MFEEventBus } from '@capillarytech/cap-ui-utils';
|
|
51
|
-
import { isMFEMode } from '../../utils/mfeDetect';
|
|
52
|
-
import appConfig from '../../app-config';
|
|
53
50
|
|
|
54
51
|
const { CapCustomCardList } = CapCustomCard;
|
|
55
52
|
|
|
@@ -57,7 +54,6 @@ const StyledCapTab = withStyles(CapTab, CapTabStyle);
|
|
|
57
54
|
export class TemplatesV2 extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
|
58
55
|
constructor(props) {
|
|
59
56
|
super(props);
|
|
60
|
-
this.lcpReported = false;
|
|
61
57
|
let defaultChannel = get(this, 'props.channel') || get(this, 'props.params.channel') || 'sms';
|
|
62
58
|
if (defaultChannel === LOYALTY) {
|
|
63
59
|
defaultChannel = 'sms';
|
|
@@ -226,23 +222,6 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
226
222
|
}
|
|
227
223
|
}
|
|
228
224
|
|
|
229
|
-
componentDidUpdate(prevProps) {
|
|
230
|
-
if (
|
|
231
|
-
isMFEMode() &&
|
|
232
|
-
!this.lcpReported &&
|
|
233
|
-
!this.props.Templates.getAllTemplatesInProgress &&
|
|
234
|
-
prevProps.Templates.getAllTemplatesInProgress
|
|
235
|
-
) {
|
|
236
|
-
this.lcpReported = true;
|
|
237
|
-
MFEEventBus.emit('mfe:segment', {
|
|
238
|
-
action: 'stop',
|
|
239
|
-
appId: appConfig.appName,
|
|
240
|
-
type: 'lcp',
|
|
241
|
-
timestamp: performance.now(),
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
225
|
componentWillReceiveProps(nextProps) {
|
|
247
226
|
if (this.props.channel !== nextProps.channel) {
|
|
248
227
|
const panes = this.setChannelContent(nextProps.channel, this.state.panes);
|
|
@@ -443,6 +422,14 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
443
422
|
/>
|
|
444
423
|
)}
|
|
445
424
|
<section className="component-wrapper">
|
|
425
|
+
{isFullMode && (
|
|
426
|
+
<CapHeader
|
|
427
|
+
title={<FormattedMessage {...messages.creatives} />}
|
|
428
|
+
{...(!useLocalTemplates && {
|
|
429
|
+
description: <FormattedMessage {...messages.creativesDesc} />,
|
|
430
|
+
})}
|
|
431
|
+
/>
|
|
432
|
+
)}
|
|
446
433
|
{hideChannelTabsForLocalSms ? (
|
|
447
434
|
<section className="templates-v2-local-sms-pane">{activeLocalPane?.content}</section>
|
|
448
435
|
) : (
|
package/AppRoot.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import React, { useMemo, useEffect, useRef } from 'react';
|
|
2
|
-
import { StyleSheetManager } from 'styled-components';
|
|
3
|
-
import { Provider } from 'react-redux';
|
|
4
|
-
import { configureStore } from '@capillarytech/vulcan-react-sdk/utils';
|
|
5
|
-
import { createBrowserHistory } from 'history';
|
|
6
|
-
import ConfigProvider from 'antd/lib/config-provider';
|
|
7
|
-
import { getCapThemeConfig, loadCapUI } from '@capillarytech/cap-ui-library/utils';
|
|
8
|
-
|
|
9
|
-
import LanguageProvider from 'v2Containers/LanguageProvider';
|
|
10
|
-
import App from './containers/App';
|
|
11
|
-
import './styles/main.scss';
|
|
12
|
-
import { initialReducer } from './initialReducer';
|
|
13
|
-
import { translationMessages } from './i18n';
|
|
14
|
-
import initialState from './initialState';
|
|
15
|
-
import pathConfig from './config/path';
|
|
16
|
-
import { isMFEMode } from './utils/mfeDetect';
|
|
17
|
-
import { emitFirstPaintReadyIfNotDataLanding } from './utils/mfeFirstPaintReady';
|
|
18
|
-
import rebaseHistory from './utils/mfeHistory';
|
|
19
|
-
import { MFEEventBus } from '@capillarytech/cap-ui-utils';
|
|
20
|
-
import appConfig from './app-config';
|
|
21
|
-
|
|
22
|
-
// Module-scope SDK init — MUST run before configureStore.
|
|
23
|
-
loadCapUI();
|
|
24
|
-
|
|
25
|
-
const AppRoot = ({ basename: _basename = '/creatives/ui', history: hostHistory }) => {
|
|
26
|
-
const { store, history } = useMemo(() => {
|
|
27
|
-
const hist = hostHistory
|
|
28
|
-
? rebaseHistory(hostHistory, pathConfig.publicPath)
|
|
29
|
-
: createBrowserHistory({ basename: pathConfig.publicPath });
|
|
30
|
-
const st = configureStore(initialState, initialReducer, hist);
|
|
31
|
-
return { store: st, history: hist };
|
|
32
|
-
}, [hostHistory]);
|
|
33
|
-
|
|
34
|
-
// MFE style isolation: route THIS remote's styled-components output (including any
|
|
35
|
-
// createGlobalStyle) into a tagged <div data-mfe-app> in <head>, instead of the shared
|
|
36
|
-
// default styled-components <style>. On unmount the container is removed, so all of this
|
|
37
|
-
// remote's styled-components CSS goes with it — no global-style leak into other remotes.
|
|
38
|
-
// Standalone (non-MFE) is untouched: styles inject into <head> as usual.
|
|
39
|
-
const styleTarget = useMemo(() => {
|
|
40
|
-
if (!isMFEMode() || typeof document === 'undefined') return null;
|
|
41
|
-
const el = document.createElement('div');
|
|
42
|
-
el.setAttribute('data-mfe-app', 'creatives/ui');
|
|
43
|
-
document.head.appendChild(el);
|
|
44
|
-
return el;
|
|
45
|
-
}, []);
|
|
46
|
-
|
|
47
|
-
useEffect(() => () => {
|
|
48
|
-
if (styleTarget) styleTarget.remove();
|
|
49
|
-
}, [styleTarget]);
|
|
50
|
-
|
|
51
|
-
const fcpReported = useRef(false);
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
if (isMFEMode() && !fcpReported.current) {
|
|
54
|
-
fcpReported.current = true;
|
|
55
|
-
requestAnimationFrame(() => {
|
|
56
|
-
MFEEventBus.emit('mfe:segment', {
|
|
57
|
-
action: 'stop',
|
|
58
|
-
appId: appConfig.appName,
|
|
59
|
-
type: 'fcp',
|
|
60
|
-
timestamp: performance.now(),
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}, []);
|
|
65
|
-
|
|
66
|
-
// Non-landing routes (anything outside the TemplatesV2 listing pages) never emit
|
|
67
|
-
// the data-settled 'lcp'; emit it at first paint so the host drops the skeleton
|
|
68
|
-
// overlay immediately instead of waiting out the grace timer.
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
emitFirstPaintReadyIfNotDataLanding();
|
|
71
|
-
}, []);
|
|
72
|
-
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
if (!isMFEMode()) {
|
|
75
|
-
const { startStandalone } = require('locize');
|
|
76
|
-
startStandalone();
|
|
77
|
-
}
|
|
78
|
-
}, []);
|
|
79
|
-
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (isMFEMode()) {
|
|
82
|
-
MFEEventBus.emit('gtm:init', {
|
|
83
|
-
appId: appConfig.appName,
|
|
84
|
-
containerId: appConfig.gtm.trackingId,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}, []);
|
|
88
|
-
|
|
89
|
-
// In MFE mode the HOST's index.html is served, not creatives' — so BeePlugin.js
|
|
90
|
-
// (declared in creatives/index.html) is never loaded. Inject it here so BeeEditor works.
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
if (isMFEMode() && !window.BeePlugin && !document.querySelector('script[src*="BeePlugin.js"]')) {
|
|
93
|
-
const script = document.createElement('script');
|
|
94
|
-
script.src = 'https://app-rsrc.getbee.io/plugin/BeePlugin.js';
|
|
95
|
-
script.type = 'text/javascript';
|
|
96
|
-
document.head.appendChild(script);
|
|
97
|
-
}
|
|
98
|
-
}, []);
|
|
99
|
-
|
|
100
|
-
useEffect(() => {
|
|
101
|
-
const unsub = MFEEventBus.on('creatives:navigate', ({ path }) => {
|
|
102
|
-
if (path) history.push(path);
|
|
103
|
-
});
|
|
104
|
-
return unsub;
|
|
105
|
-
}, [history]);
|
|
106
|
-
|
|
107
|
-
const tree = (
|
|
108
|
-
<Provider store={store}>
|
|
109
|
-
<LanguageProvider messages={translationMessages}>
|
|
110
|
-
<ConfigProvider theme={getCapThemeConfig()}>
|
|
111
|
-
<App history={history} />
|
|
112
|
-
</ConfigProvider>
|
|
113
|
-
</LanguageProvider>
|
|
114
|
-
</Provider>
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
return styleTarget ? (
|
|
118
|
-
<StyleSheetManager target={styleTarget}>{tree}</StyleSheetManager>
|
|
119
|
-
) : (
|
|
120
|
-
tree
|
|
121
|
-
);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export default AppRoot;
|
package/app-config.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
appName: 'cap-creatives-ui',
|
|
3
|
-
intouchBaseUrl: 'nightly.intouch.capillarytech.com',
|
|
4
|
-
prefix: '/creatives/ui',
|
|
5
|
-
isHostedOnPlatform: false,
|
|
6
|
-
appType: 'native',
|
|
7
|
-
bugsnag: {
|
|
8
|
-
useBugsnag: false,
|
|
9
|
-
apiKey: '',
|
|
10
|
-
retainSourceMaps: false,
|
|
11
|
-
},
|
|
12
|
-
useSourceMaps: true,
|
|
13
|
-
i18n: {
|
|
14
|
-
useI18n: true,
|
|
15
|
-
customI18n: false,
|
|
16
|
-
localI18n: false,
|
|
17
|
-
appNames: ['cap_creatives_ui'],
|
|
18
|
-
locales: [],
|
|
19
|
-
defaultLocale: null,
|
|
20
|
-
},
|
|
21
|
-
gtm: {
|
|
22
|
-
useGTM: true,
|
|
23
|
-
trackingId: 'GTM-MC4TRPX',
|
|
24
|
-
projectId: 'GTM-MC4TRPX',
|
|
25
|
-
},
|
|
26
|
-
useNavigationComponent: true,
|
|
27
|
-
useTestSetup: true,
|
|
28
|
-
newrelic: {
|
|
29
|
-
enabled: true,
|
|
30
|
-
licenseKey: '082da40fff',
|
|
31
|
-
environments: {
|
|
32
|
-
nightly: {
|
|
33
|
-
appId: '718411553',
|
|
34
|
-
},
|
|
35
|
-
staging: {
|
|
36
|
-
appId: '718413027',
|
|
37
|
-
},
|
|
38
|
-
ushc: {
|
|
39
|
-
appId: '718413124',
|
|
40
|
-
},
|
|
41
|
-
apac: {
|
|
42
|
-
appId: '718413134',
|
|
43
|
-
},
|
|
44
|
-
'north-america': {
|
|
45
|
-
appId: '718413144',
|
|
46
|
-
},
|
|
47
|
-
apac2: {
|
|
48
|
-
appId: '718413155',
|
|
49
|
-
},
|
|
50
|
-
tata: {
|
|
51
|
-
appId: '718413165',
|
|
52
|
-
},
|
|
53
|
-
sea: {
|
|
54
|
-
appId: '718413175',
|
|
55
|
-
},
|
|
56
|
-
eu: {
|
|
57
|
-
appId: '718413185',
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
};
|