@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.
Files changed (51) hide show
  1. package/app.js +175 -10
  2. package/entry.js +1 -67
  3. package/mfe-exposed-components.js +4 -2
  4. package/package.json +2 -2
  5. package/services/api.js +1 -9
  6. package/styles/containers/layout/_layoutPage.scss +6 -8
  7. package/utils/gtmTrackers/gtmEvents/creativeDetails.js +1 -2
  8. package/utils/rcsPayloadUtils.js +2 -5
  9. package/utils/tests/rcsPayloadUtils.test.js +2 -54
  10. package/v2Components/CapActionButton/constants.js +0 -1
  11. package/v2Components/CapActionButton/index.js +9 -77
  12. package/v2Components/CapActionButton/index.scss +0 -13
  13. package/v2Components/CapActionButton/messages.js +0 -13
  14. package/v2Components/CapActionButton/tests/index.test.js +1 -32
  15. package/v2Components/CommonTestAndPreview/index.js +15 -44
  16. package/v2Components/CommonTestAndPreview/tests/index.test.js +0 -78
  17. package/v2Components/CommonTestAndPreview/utils.js +0 -34
  18. package/v2Components/NavigationBar/index.js +7 -9
  19. package/v2Components/NavigationBar/tests/index.test.js +19 -39
  20. package/v2Components/SmsFallback/index.js +0 -6
  21. package/v2Containers/Cap/constants.js +0 -1
  22. package/v2Containers/Cap/index.js +27 -57
  23. package/v2Containers/CreativesContainer/index.js +8 -11
  24. package/v2Containers/CreativesContainer/tests/index.test.js +0 -53
  25. package/v2Containers/Rcs/carouselUtils.js +73 -46
  26. package/v2Containers/Rcs/components/CarouselCard.js +9 -11
  27. package/v2Containers/Rcs/components/CarouselCardButtons.js +0 -20
  28. package/v2Containers/Rcs/constants.js +7 -2
  29. package/v2Containers/Rcs/index.js +205 -226
  30. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +47 -60
  31. package/v2Containers/Rcs/tests/CarouselCard.test.js +2 -1
  32. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -286
  33. package/v2Containers/Rcs/tests/carouselUtils.test.js +32 -28
  34. package/v2Containers/Rcs/tests/index.test.js +238 -151
  35. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -110
  36. package/v2Containers/Rcs/tests/utils.test.js +17 -36
  37. package/v2Containers/Rcs/utils.js +55 -28
  38. package/v2Containers/SmsTrai/Edit/index.js +24 -35
  39. package/v2Containers/Templates/_templates.scss +1 -1
  40. package/v2Containers/Templates/index.js +0 -1
  41. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +0 -5
  42. package/v2Containers/TemplatesV2/TemplatesV2.style.js +3 -8
  43. package/v2Containers/TemplatesV2/index.js +9 -22
  44. package/AppRoot.js +0 -124
  45. package/app-config.js +0 -61
  46. package/bootstrap.js +0 -185
  47. package/utils/getDataLayer.js +0 -15
  48. package/utils/mfeDetect.js +0 -1
  49. package/utils/mfeFirstPaintReady.js +0 -29
  50. package/utils/mfeHistory.js +0 -62
  51. 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
- if (preserveLiteralValueTokens && !RCS_MUSTACHE_WRAPPED_REGEX.test(trimmedSlotValue)) {
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, cardSlotOffset),
358
- rcsDesc: resolveTemplateWithMap(String(description || ''), cardSlotOffset + titleVarCount),
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
- cardTitle,
394
- cardDescription,
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
- const [rcsFallbackVarSegmentModeActive, setRcsFallbackVarSegmentModeActive] = useState(false);
174
- const useRcsFallbackVarSegmentForCurrentText = isRcsSmsFallback && rcsFallbackVarSegmentModeActive;
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 (!useRcsFallbackVarSegmentForCurrentText) {
199
+ if (!useRcsFallbackVarSegment) {
199
200
  return (
200
201
  <CapRow className="rcs-create-template-message-input">
201
202
  <div className="rcs_text_area_wrapper">
202
- <div className="var-segment-message-editor__var-slot">
203
- <TextArea
204
- id="rcs_fallback_message_textarea"
205
- autosize={{ minRows: 1, maxRows: 3 }}
206
- value={fallbackText}
207
- onChange={(e) => setFallbackText(e.target.value)}
208
- onBlur={() => {
209
- const hasVarToken = (fallbackText.match(COMBINED_SMS_TEMPLATE_VAR_REGEX) || []).length > 0;
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
- const templateMsgHasVarToken = (templateMsg.match(COMBINED_SMS_TEMPLATE_VAR_REGEX) || []).length > 0;
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 (!useRcsFallbackVarSegmentForCurrentText) {
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, useRcsFallbackVarSegmentForCurrentText, fallbackText, fallbackVarMappedData]);
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, loading, fallbackVarMappedData, onRcsFallbackEditorStateChange]);
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 (useRcsFallbackVarSegmentForCurrentText) {
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
- useRcsFallbackVarSegmentForCurrentText,
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
- // Gate on useRcsFallbackVarSegmentForCurrentText (not the static DLT flag), or non-DLT var-token flows lose slot values on save.
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 (!useRcsFallbackVarSegmentForCurrentText) {
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 (useRcsFallbackVarSegmentForCurrentText) {
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
- };