@capillarytech/creatives-library 9.0.29 → 9.0.30

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 (147) hide show
  1. package/constants/unified.js +29 -0
  2. package/global-styles.js +1 -1
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +35 -20
  5. package/utils/commonUtils.js +22 -1
  6. package/utils/rcsPayloadUtils.js +102 -0
  7. package/utils/templateVarUtils.js +198 -0
  8. package/utils/tests/rcsPayloadUtils.test.js +295 -0
  9. package/utils/tests/templateVarUtils.test.js +204 -0
  10. package/v2Components/CapActionButton/constants.js +21 -1
  11. package/v2Components/CapActionButton/index.js +261 -144
  12. package/v2Components/CapActionButton/index.scss +245 -14
  13. package/v2Components/CapActionButton/messages.js +32 -3
  14. package/v2Components/CapActionButton/tests/index.test.js +74 -19
  15. package/v2Components/CapImageUpload/index.js +2 -2
  16. package/v2Components/CapTagList/index.js +9 -0
  17. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +58 -11
  24. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +186 -22
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +381 -80
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
  28. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  29. package/v2Components/CommonTestAndPreview/constants.js +35 -2
  30. package/v2Components/CommonTestAndPreview/index.js +770 -231
  31. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  32. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  33. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  34. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
  35. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +412 -257
  36. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +337 -63
  37. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  38. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  39. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  40. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +108 -15
  41. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  42. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  43. package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
  44. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  45. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
  46. package/v2Components/CommonTestAndPreview/tests/utils.test.js +151 -0
  47. package/v2Components/CommonTestAndPreview/utils.js +84 -0
  48. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  49. package/v2Components/SmsFallback/constants.js +94 -0
  50. package/v2Components/SmsFallback/index.js +958 -0
  51. package/v2Components/SmsFallback/index.scss +266 -0
  52. package/v2Components/SmsFallback/messages.js +78 -0
  53. package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
  54. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  55. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  56. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  57. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
  58. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  59. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +471 -0
  60. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  61. package/v2Components/TemplatePreview/_templatePreview.scss +45 -25
  62. package/v2Components/TemplatePreview/constants.js +2 -0
  63. package/v2Components/TemplatePreview/index.js +147 -32
  64. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  65. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  66. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  67. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  68. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  69. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  70. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  71. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  72. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
  73. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
  74. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  75. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +27 -0
  76. package/v2Containers/CreativesContainer/SlideBoxContent.js +37 -4
  77. package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
  78. package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
  79. package/v2Containers/CreativesContainer/constants.js +11 -0
  80. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +77 -0
  81. package/v2Containers/CreativesContainer/index.js +322 -106
  82. package/v2Containers/CreativesContainer/index.scss +102 -1
  83. package/v2Containers/CreativesContainer/tests/CreativesSlideBoxWrapper.test.js +58 -0
  84. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  85. package/v2Containers/CreativesContainer/tests/SlideBoxContent.smsDltPreview.test.js +73 -0
  86. package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +37 -0
  87. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +103 -34
  88. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +193 -15
  89. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +88 -0
  90. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +549 -57
  91. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  92. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  93. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  94. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  95. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  96. package/v2Containers/Rcs/carouselUtils.js +224 -0
  97. package/v2Containers/Rcs/components/CarouselCard.js +317 -0
  98. package/v2Containers/Rcs/components/CarouselCardButtons.js +113 -0
  99. package/v2Containers/Rcs/components/CarouselCardMedia.js +136 -0
  100. package/v2Containers/Rcs/components/CarouselCharacterCount.js +31 -0
  101. package/v2Containers/Rcs/components/CarouselDimensionSelection.js +80 -0
  102. package/v2Containers/Rcs/constants.js +132 -16
  103. package/v2Containers/Rcs/index.js +1822 -946
  104. package/v2Containers/Rcs/index.scss +443 -8
  105. package/v2Containers/Rcs/messages.js +45 -22
  106. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +494 -0
  107. package/v2Containers/Rcs/tests/CarouselCard.test.js +464 -0
  108. package/v2Containers/Rcs/tests/CarouselCardButtons.test.js +211 -0
  109. package/v2Containers/Rcs/tests/CarouselCardMedia.test.js +160 -0
  110. package/v2Containers/Rcs/tests/CarouselCharacterCount.test.js +50 -0
  111. package/v2Containers/Rcs/tests/CarouselDimensionSelection.test.js +119 -0
  112. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +74440 -39387
  113. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +10 -74
  114. package/v2Containers/Rcs/tests/carouselUtils.test.js +916 -0
  115. package/v2Containers/Rcs/tests/index.test.js +275 -40
  116. package/v2Containers/Rcs/tests/mockData.js +38 -0
  117. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +912 -0
  118. package/v2Containers/Rcs/tests/utils.test.js +682 -30
  119. package/v2Containers/Rcs/utils.js +514 -12
  120. package/v2Containers/Sms/Create/index.js +115 -48
  121. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  122. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  123. package/v2Containers/SmsTrai/Create/index.js +9 -4
  124. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  125. package/v2Containers/SmsTrai/Edit/index.js +678 -169
  126. package/v2Containers/SmsTrai/Edit/index.scss +126 -0
  127. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  128. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
  129. package/v2Containers/SmsWrapper/index.js +37 -8
  130. package/v2Containers/TagList/index.js +5 -0
  131. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  132. package/v2Containers/Templates/_templates.scss +196 -12
  133. package/v2Containers/Templates/actions.js +11 -0
  134. package/v2Containers/Templates/constants.js +2 -0
  135. package/v2Containers/Templates/index.js +131 -59
  136. package/v2Containers/Templates/sagas.js +57 -13
  137. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +129 -0
  138. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
  139. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  140. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  141. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  142. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  143. package/v2Containers/TemplatesV2/index.js +88 -25
  144. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  145. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  146. package/v2Containers/Whatsapp/index.js +3 -20
  147. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -52,6 +52,7 @@ import { QUICK_REPLY, WHATSAPP_CATEGORIES, PHONE_NUMBER, TEMPLATE_VARIABLE_REGEX
52
52
  import { RCS_BUTTON_TYPES, LEFT, HORIZONTAL, VERTICAL, RIGHT} from '../../v2Containers/Rcs/constants';
53
53
  import { ANDROID, INAPP_MESSAGE_LAYOUT_TYPES } from '../../v2Containers/InApp/constants';
54
54
  import { CAROUSEL } from '../../v2Containers/MobilePushNew/constants';
55
+ import { TEMPLATE_VAR_REGEX } from './constants';
55
56
 
56
57
  const wechatBodyNew = require('./assets/images/wechat_mobile_android.svg');
57
58
  const smsMobileAndroid = require('./assets/images/sms_mobile_android.svg');
@@ -236,7 +237,23 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
236
237
  let content = channel && channel.toLowerCase() === 'sms' ? [this.props.content] : this.props.content;
237
238
  const { formatMessage } = intl;
238
239
  const { rcsPreviewContent, inAppPreviewContent, viberPreviewContent, isBeeFreeTemplate } = content || {};
239
- const { rcsImageSrc, rcsVideoSrc, rcsTitle, rcsDesc, rcsSuggestions } = rcsPreviewContent || {};
240
+ const normalizedRcsPreviewContent = Array.isArray(rcsPreviewContent)
241
+ ? { carouselData: rcsPreviewContent }
242
+ : (rcsPreviewContent || {});
243
+ const {
244
+ rcsImageSrc,
245
+ rcsVideoSrc,
246
+ rcsTitle,
247
+ rcsDesc,
248
+ rcsSuggestions,
249
+ carouselData: rcsCarouselData,
250
+ rcsCarouselData: rcsCarouselDataAlt,
251
+ cardVarMapped: rcsCardVarMapped,
252
+ } = normalizedRcsPreviewContent;
253
+ const resolvedRcsCarouselData =
254
+ Array.isArray(rcsCarouselData) && rcsCarouselData.length > 0
255
+ ? rcsCarouselData
256
+ : rcsCarouselDataAlt;
240
257
  const {
241
258
  videoParams,
242
259
  imageURL,
@@ -317,6 +334,18 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
317
334
  'flex-shrink': 0,
318
335
  'left': 0,
319
336
  };
337
+
338
+ const resolveVarsWithMap = (input, varMap) => {
339
+ if (input === null || input === undefined) return '';
340
+ const str = typeof input === 'string' ? input : String(input);
341
+ return str.replace(TEMPLATE_VAR_REGEX, (token, varName) => {
342
+ const mappedValue = varMap?.[varName];
343
+ if (mappedValue === null || mappedValue === undefined || String(mappedValue) === '') {
344
+ return token;
345
+ }
346
+ return String(mappedValue);
347
+ });
348
+ };
320
349
  const getVideoContent = ({
321
350
  video,
322
351
  actionUrl,
@@ -473,13 +502,15 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
473
502
  const whatsappUpdatedAccountName = whatsappAccountName || templateData?.versions?.base?.content?.whatsapp?.accountName || '';
474
503
  const whatsappUpdatedLen = whatsappContentLen !== undefined ? whatsappContentLen : content?.charCount;
475
504
 
476
- const renderRcsSuggestionsPreview = () => {
505
+ const renderRcsSuggestionsPreview = (suggestionsArg) => {
477
506
  const renderArray = [];
478
- (rcsSuggestions || []).forEach((suggestion) => {
479
- renderArray.push(<CapDivider className="whatsapp-divider" />);
507
+ const suggestions = Array.isArray(suggestionsArg) ? suggestionsArg : (rcsSuggestions || []);
508
+ (suggestions || []).forEach((suggestion, idx) => {
509
+ const suggestionKey = `${suggestion?.type || 'unknown'}-${suggestion?.text || ''}-${idx}`;
510
+ renderArray.push(<CapDivider key={`rcs-divider-${suggestionKey}`} className="whatsapp-divider" />);
480
511
  if (suggestion.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
481
512
  renderArray.push(
482
- <CapLabel type="label21" className="rcs-cta-preview">
513
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
483
514
  <CapIcon
484
515
  type='small-link'
485
516
  size="xs"
@@ -489,14 +520,17 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
489
520
  );
490
521
  } else if (suggestion.type === RCS_BUTTON_TYPES.CTA) {
491
522
  renderArray.push(
492
- <CapLabel type="label21" className="rcs-cta-preview">
493
- <CapIcon type="launch" size="xs" />
523
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
524
+ {/* "launch" icon's hardcoded SVG <defs>/<mask> ids collide when 2+
525
+ CTA buttons render together, showing as a solid square — "open-in-new"
526
+ is a single flat path with no ids, so it can't collide. */}
527
+ <CapIcon type="open-in-new" size="xs" />
494
528
  {suggestion.text}
495
529
  </CapLabel>,
496
530
  );
497
531
  } else if (suggestion.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
498
532
  renderArray.push(
499
- <CapLabel type="label21" className="rcs-cta-preview">
533
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
500
534
  <CapIcon type="call" size="xs" />
501
535
  {suggestion.text}
502
536
  </CapLabel>,
@@ -509,23 +543,20 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
509
543
  const renderTextPreviewContent = () => (
510
544
  <>
511
545
  {rcsTitle && (
512
- <>
513
546
  <CapLabel
514
547
  type="label5"
515
548
  className="message-pop-item align-left rcs-content"
516
549
  fontWeight="bold"
517
550
  >
518
- {rcsTitle}
551
+ {resolveVarsWithMap(rcsTitle, rcsCardVarMapped)}
519
552
  </CapLabel>
520
- <CapDivider className="whatsapp-divider" />
521
- </>
522
553
  )}
523
554
  {rcsDesc && (
524
555
  <CapLabel
525
556
  type="label5"
526
557
  className="message-pop-item align-left rcs-desc rcs-content"
527
558
  >
528
- {rcsDesc}
559
+ {resolveVarsWithMap(rcsDesc, rcsCardVarMapped)}
529
560
  </CapLabel>
530
561
  )}
531
562
  {renderRcsSuggestionsPreview()}
@@ -542,50 +573,134 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
542
573
  />
543
574
  )}
544
575
  {rcsVideoSrc && (
545
- <div className="video-preview">
576
+ <CapRow className="video-preview">
546
577
  <CapImage
547
578
  src={rcsVideoSrc}
548
579
  className="rcs-image"
549
580
  alt={formatMessage(messages.previewGenerated)}
550
581
  />
551
- <div className="icon-position">
582
+ <CapRow className="icon-position">
552
583
  <CapImage
553
584
  className="video-icon"
554
585
  src={videoPlay}
555
586
  />
556
- </div>
557
- </div>
587
+ </CapRow>
588
+ </CapRow>
558
589
  )}
559
590
  </>
560
591
  );
561
592
 
562
593
  const renderRcsPreviewContent = () => {
594
+ const carouselCards = Array.isArray(resolvedRcsCarouselData) ? resolvedRcsCarouselData : [];
595
+ if (carouselCards.length > 0) {
596
+ return (
597
+ <CapRow className="msg-container sms">
598
+ <CapRow className="message-pop sms">
599
+ <CapRow className="msg-container-carousel">
600
+ <CapRow className="scroll-container">
601
+ {carouselCards.map((card, idx) => {
602
+ const key = `rcs-carousel-${idx}-${card?.bodyText || card?.imageSrc || card?.videoPreviewImg || ''}`;
603
+ const isVideo = (card?.mediaType || '').toLowerCase() === VIDEO;
604
+ const cardSuggestions = Array.isArray(card?.suggestions) ? card.suggestions : [];
605
+ const effectiveCardVarMap = { ...rcsCardVarMapped, ...(card?.cardVarMapped || {}) };
606
+ const suggestionsNode = cardSuggestions.length > 0
607
+ ? renderRcsSuggestionsPreview(cardSuggestions)
608
+ : null;
609
+
610
+ const resolvedCardTitle = resolveVarsWithMap(card?.title, effectiveCardVarMap);
611
+ const resolvedCardBodyText = resolveVarsWithMap(card?.bodyText, effectiveCardVarMap);
612
+
613
+ return (
614
+ <CapRow
615
+ key={key}
616
+ className="message-pop align-left message-pop-carousel rcs-carousel-card"
617
+ >
618
+ <CapRow className="whatsapp-content">
619
+ {!isVideo && (
620
+ <CapImage
621
+ src={card?.imageSrc ? card.imageSrc : whatsappImageEmptyPreview}
622
+ className="whatsapp-image"
623
+ alt={formatMessage(messages.previewGenerated)}
624
+ />
625
+ )}
626
+ {isVideo && (
627
+ <CapTooltip title={formatMessage(messages.videoPreviewTooltip)}>
628
+ <CapRow className="video-preview">
629
+ <CapImage
630
+ src={card?.videoPreviewImg ? card.videoPreviewImg : whatsappVideoEmptyPreview}
631
+ className="whatsapp-image"
632
+ alt={formatMessage(messages.previewGenerated)}
633
+ />
634
+ <CapRow className="icon-position">
635
+ <CapImage className="video-icon" src={videoPlay} alt="Play" />
636
+ </CapRow>
637
+ </CapRow>
638
+ </CapTooltip>
639
+ )}
640
+
641
+ {(resolvedCardTitle || resolvedCardBodyText) && (
642
+ <CapRow className="carousel-content">
643
+ {!!resolvedCardTitle && (
644
+ <CapLabel
645
+ type={card?.titleLabelType || 'label1'}
646
+ className="carousel-title"
647
+ >
648
+ {resolvedCardTitle}
649
+ </CapLabel>
650
+ )}
651
+ {!!resolvedCardBodyText && (
652
+ <CapLabel
653
+ type={card?.bodyLabelType || 'label2'}
654
+ className="carousel-message"
655
+ >
656
+ {resolvedCardBodyText}
657
+ </CapLabel>
658
+ )}
659
+ </CapRow>
660
+ )}
661
+
662
+ {!!suggestionsNode && (
663
+ <>
664
+ {suggestionsNode}
665
+ </>
666
+ )}
667
+ </CapRow>
668
+ </CapRow>
669
+ );
670
+ })}
671
+ </CapRow>
672
+ </CapRow>
673
+ </CapRow>
674
+ </CapRow>
675
+ );
676
+ }
677
+
563
678
  if (rcsOrientation === HORIZONTAL) {
564
679
  return rcsType === RIGHT ? (
565
- <div className="msg-container sms">
566
- <div className="message-pop sms horizontal">
680
+ <CapRow className="msg-container sms">
681
+ <CapRow className="message-pop sms horizontal">
567
682
  <CapColumn className="rcs-preview-text" span={12}>{renderTextPreviewContent()}</CapColumn>
568
683
  <CapColumn span={12}>{renderMediaPreviewContent()}</CapColumn>
569
- </div>
570
- </div>
684
+ </CapRow>
685
+ </CapRow>
571
686
 
572
687
  ) : (
573
- <div className="msg-container sms">
574
- <div className="message-pop sms horizontal">
688
+ <CapRow className="msg-container sms">
689
+ <CapRow className="message-pop sms horizontal">
575
690
  <CapColumn span={12}>{renderMediaPreviewContent()}</CapColumn>
576
691
  <CapColumn className="rcs-preview-text" span={12}>{renderTextPreviewContent()}</CapColumn>
577
- </div>
578
- </div>
692
+ </CapRow>
693
+ </CapRow>
579
694
  );
580
695
  }
581
696
 
582
697
  return (
583
- <div className="msg-container sms">
584
- <div className="message-pop sms">
698
+ <CapRow className="msg-container sms">
699
+ <CapRow className="message-pop sms">
585
700
  {renderMediaPreviewContent()}
586
701
  {renderTextPreviewContent()}
587
- </div>
588
- </div>
702
+ </CapRow>
703
+ </CapRow>
589
704
  );
590
705
  };
591
706
 
@@ -1306,14 +1421,14 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
1306
1421
  ""
1307
1422
  )}
1308
1423
  {channel?.toUpperCase() === RCS && (
1309
- <div className="shell-v2 align-center rcs-preview">
1424
+ <CapRow className="shell-v2 align-center rcs-preview">
1310
1425
  <CapImage
1311
1426
  className="preview-image"
1312
1427
  src={rcsIosPreview ? smsMobileIos : smsMobileAndroid}
1313
1428
  alt={formatMessage(messages.previewGenerated)}
1314
1429
  />
1315
1430
  {renderRcsPreviewContent()}
1316
- </div>
1431
+ </CapRow>
1317
1432
 
1318
1433
  )}
1319
1434
  {channel?.toUpperCase() === ZALO && (
@@ -1515,4 +1630,4 @@ TemplatePreview.propTypes = {
1515
1630
  rcsOrientation: PropTypes.string,
1516
1631
  };
1517
1632
 
1518
- export default (injectIntl(TemplatePreview));
1633
+ export default (injectIntl(TemplatePreview));
@@ -2,6 +2,9 @@ import React from 'react';
2
2
  import { shallowWithIntl } from '../../../helpers/intl-enzym-test-helpers';
3
3
 
4
4
  import { TemplatePreview } from '../index';
5
+ import { RCS } from '../../../v2Containers/CreativesContainer/constants';
6
+ import whatsappImageEmptyPreview from '../assets/images/empty_image_preview.svg';
7
+ import whatsappVideoEmptyPreview from '../assets/images/empty_video_preview.svg';
5
8
 
6
9
  describe('Test Templates container', () => {
7
10
  let renderedComponent;
@@ -69,4 +72,143 @@ describe('Test Templates container', () => {
69
72
  });
70
73
  expect(renderedComponent).toMatchSnapshot();
71
74
  });
75
+
76
+ describe('RCS carousel preview branches', () => {
77
+ const buildRcsContent = (cards) => ({
78
+ rcsPreviewContent: {
79
+ carouselData: cards,
80
+ },
81
+ });
82
+
83
+ const getNodesByClassName = (className) =>
84
+ renderedComponent.findWhere((node) => node.prop('className') === className);
85
+
86
+ it('renders image and video cards with proper media-specific markup', () => {
87
+ renderFunction(
88
+ RCS,
89
+ buildRcsContent([
90
+ {
91
+ mediaType: 'image',
92
+ imageSrc: 'https://example.com/image-card.jpg',
93
+ bodyText: 'image body',
94
+ suggestions: [],
95
+ },
96
+ {
97
+ mediaType: 'video',
98
+ videoPreviewImg: 'https://example.com/video-thumb.jpg',
99
+ bodyText: 'video body',
100
+ suggestions: [],
101
+ },
102
+ ]),
103
+ '',
104
+ 0,
105
+ );
106
+
107
+ // Video card should render tooltip/video icon path; image-only card should not.
108
+ expect(renderedComponent.find('CapTooltip')).toHaveLength(1);
109
+ expect(
110
+ renderedComponent.findWhere((node) => node.prop('className') === 'video-icon')
111
+ ).toHaveLength(1);
112
+
113
+ // Both cards render media containers.
114
+ expect(
115
+ renderedComponent.findWhere((node) => node.prop('className') === 'whatsapp-image')
116
+ ).toHaveLength(2);
117
+ });
118
+
119
+ it('renders per-card suggestions only for cards that provide suggestions', () => {
120
+ renderFunction(
121
+ RCS,
122
+ buildRcsContent([
123
+ {
124
+ mediaType: 'image',
125
+ imageSrc: 'https://example.com/with-suggestions.jpg',
126
+ bodyText: 'has suggestions',
127
+ suggestions: [
128
+ { type: 'QUICK_REPLY', text: 'Reply 1' },
129
+ { type: 'CTA', text: 'Visit' },
130
+ ],
131
+ },
132
+ {
133
+ mediaType: 'image',
134
+ imageSrc: 'https://example.com/no-suggestions.jpg',
135
+ bodyText: 'no suggestions',
136
+ suggestions: [],
137
+ },
138
+ ]),
139
+ '',
140
+ 0,
141
+ );
142
+
143
+ // Suggestion labels/icons should be rendered only from first card.
144
+ expect(getNodesByClassName('rcs-cta-preview')).toHaveLength(2);
145
+ expect(getNodesByClassName('whatsapp-divider')).not.toHaveLength(0);
146
+ });
147
+
148
+ it('falls back to empty preview assets when image/video media src is missing', () => {
149
+ renderFunction(
150
+ RCS,
151
+ buildRcsContent([
152
+ {
153
+ mediaType: 'image',
154
+ bodyText: 'fallback image',
155
+ suggestions: [],
156
+ },
157
+ {
158
+ mediaType: 'video',
159
+ bodyText: 'fallback video',
160
+ suggestions: [],
161
+ },
162
+ ]),
163
+ '',
164
+ 0,
165
+ );
166
+
167
+ const mediaImageNodes = renderedComponent.findWhere(
168
+ (node) => node.name() === 'CapImage' && node.prop('className') === 'whatsapp-image'
169
+ );
170
+ const mediaSrcs = mediaImageNodes.map((node) => node.prop('src'));
171
+
172
+ expect(mediaSrcs).toContain(whatsappImageEmptyPreview);
173
+ expect(mediaSrcs).toContain(whatsappVideoEmptyPreview);
174
+ });
175
+
176
+ it('generates unique carousel keys using rcs-carousel-<idx>-<bodyText|imageSrc|videoPreviewImg>', () => {
177
+ renderFunction(
178
+ RCS,
179
+ buildRcsContent([
180
+ {
181
+ mediaType: 'image',
182
+ imageSrc: 'https://example.com/one.jpg',
183
+ bodyText: 'same body',
184
+ suggestions: [],
185
+ },
186
+ {
187
+ mediaType: 'image',
188
+ imageSrc: 'https://example.com/two.jpg',
189
+ bodyText: 'same body',
190
+ suggestions: [],
191
+ },
192
+ {
193
+ mediaType: 'video',
194
+ videoPreviewImg: 'https://example.com/three-thumb.jpg',
195
+ bodyText: 'same body',
196
+ suggestions: [],
197
+ },
198
+ ]),
199
+ '',
200
+ 0,
201
+ );
202
+
203
+ const cardNodes = getNodesByClassName('message-pop align-left message-pop-carousel rcs-carousel-card');
204
+ const keys = cardNodes.map((node) => node.key());
205
+
206
+ expect(keys).toEqual([
207
+ 'rcs-carousel-0-same body',
208
+ 'rcs-carousel-1-same body',
209
+ 'rcs-carousel-2-same body',
210
+ ]);
211
+ expect(new Set(keys).size).toBe(keys.length);
212
+ });
213
+ });
72
214
  });
@@ -88,12 +88,12 @@ const CustomValuesEditor = ({
88
88
  </CapLabel>
89
89
  </CapRow>
90
90
  {requiredTags.map((tag) => (
91
- <CapRow key={tag.fullPath} className="value-row">
92
- <CapRow className="tag-name">
91
+ <CapRow key={tag.fullPath} type="flex" noWrap className="value-row">
92
+ <CapRow type="flex" className="tag-name">
93
93
  {tag.fullPath}
94
94
  <span className="required-tag-indicator">*</span>
95
95
  </CapRow>
96
- <CapRow className="tag-input">
96
+ <CapRow type="flex" className="tag-input">
97
97
  <CapInput
98
98
  type="text"
99
99
  isRequired
@@ -107,9 +107,9 @@ const CustomValuesEditor = ({
107
107
  </CapRow>
108
108
  ))}
109
109
  {optionalTags.map((tag) => (
110
- <CapRow key={tag.fullPath} className="value-row">
111
- <CapRow className="tag-name">{tag.fullPath}</CapRow>
112
- <CapRow className="tag-input">
110
+ <CapRow key={tag.fullPath} type="flex" noWrap className="value-row">
111
+ <CapRow type="flex" className="tag-name">{tag.fullPath}</CapRow>
112
+ <CapRow type="flex" className="tag-input">
113
113
  <CapInput
114
114
  type="text"
115
115
  className="tag-input-field"
@@ -18,7 +18,7 @@ import injectReducer from '../../utils/injectReducer';
18
18
  import injectSaga from '../../utils/injectSaga';
19
19
 
20
20
  import CommonTestAndPreview from '../CommonTestAndPreview';
21
- import { CHANNELS } from '../CommonTestAndPreview/constants';
21
+ import { CHANNELS, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../CommonTestAndPreview/constants';
22
22
  import * as commonTestAndPreviewActions from '../CommonTestAndPreview/actions';
23
23
  import { commonTestAndPreviewSaga } from '../CommonTestAndPreview/sagas';
24
24
  import commonTestAndPreviewReducer from '../CommonTestAndPreview/reducer';
@@ -78,6 +78,16 @@ TestAndPreviewSlidebox.propTypes = {
78
78
  content: PropTypes.string,
79
79
  beeInstance: PropTypes.object,
80
80
  currentTab: PropTypes.number,
81
+ smsFallbackContent: PropTypes.shape({
82
+ templateContent: PropTypes.string,
83
+ senderId: PropTypes.string,
84
+ templateName: PropTypes.string,
85
+ [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: PropTypes.object,
86
+ }),
87
+ /** Passed to CommonTestAndPreview for RCS test-meta resolution (slot semantics vs full-mode). */
88
+ rcsTestPreviewOptions: PropTypes.shape({
89
+ isLibraryMode: PropTypes.bool,
90
+ }),
81
91
  // Redux props are passed through
82
92
  actions: PropTypes.object.isRequired,
83
93
  extractedTags: PropTypes.array.isRequired,
@@ -109,10 +119,12 @@ TestAndPreviewSlidebox.defaultProps = {
109
119
  currentTab: 1,
110
120
  messageMetaConfigId: null,
111
121
  prefilledValues: {},
122
+ rcsTestPreviewOptions: undefined,
112
123
  senderDetailsByChannel: {},
113
124
  wecrmAccounts: [],
114
125
  isLoadingSenderDetails: false,
115
126
  orgUnitId: -1,
127
+ smsFallbackContent: null,
116
128
  };
117
129
 
118
130
  const mapStateToProps = createStructuredSelector({
@@ -34,6 +34,7 @@ import {
34
34
  GET_PREFILLED_VALUES_SUCCESS,
35
35
  GET_PREFILLED_VALUES_FAILURE,
36
36
  } from './constants';
37
+ import { extractPreviewFromLiquidResponse } from '../CommonTestAndPreview/previewApiUtils';
37
38
 
38
39
  // Search Customers Saga
39
40
  export function* searchCustomersSaga(action) {
@@ -80,11 +81,12 @@ export function* updatePreviewSaga(action) {
80
81
  const customValues = action.payload.resolvedTags;
81
82
 
82
83
  const response = yield call(Api.updateEmailPreview, action.payload);
83
- if (response?.data) {
84
+ const previewPayload = extractPreviewFromLiquidResponse(response);
85
+ if (previewPayload) {
84
86
  yield put({
85
87
  type: UPDATE_PREVIEW_SUCCESS,
86
88
  payload: {
87
- previewData: response.data,
89
+ previewData: previewPayload,
88
90
  customValues, // Pass custom values to be preserved
89
91
  },
90
92
  });
@@ -221,8 +223,13 @@ export function* createMessageMetaSaga(action) {
221
223
  export function* getPrefilledValuesSaga(action) {
222
224
  try {
223
225
  const response = yield call(Api.updateEmailPreview, action.payload);
224
- if (response?.data) {
225
- yield put({ type: GET_PREFILLED_VALUES_SUCCESS, payload: { values: response?.data?.resolvedTagValues } });
226
+ const body =
227
+ response?.data !== undefined && response?.data !== null
228
+ ? response.data
229
+ : response;
230
+ const resolvedTagValues = body?.resolvedTagValues;
231
+ if (resolvedTagValues != null) {
232
+ yield put({ type: GET_PREFILLED_VALUES_SUCCESS, payload: { values: resolvedTagValues } });
226
233
  } else {
227
234
  yield put({ type: GET_PREFILLED_VALUES_FAILURE, payload: { error: response.error || 'Failed to fetch prefilled values' } });
228
235
  }
@@ -136,7 +136,9 @@ describe('TestAndPreviewSlidebox Sagas', () => {
136
136
  describe('updatePreviewSaga', () => {
137
137
  it('should handle successful preview update', () => {
138
138
  const mockResponse = {
139
- data: 'Test Preview Data',
139
+ data: {
140
+ resolvedBody: 'Test Preview Data',
141
+ },
140
142
  };
141
143
  const customValues = { test: 'value' };
142
144
  return expectSaga(sagas.updatePreviewSaga, {
@@ -0,0 +1,2 @@
1
+ /** Default prefix before variable name in variable-slot placeholders. */
2
+ export const VAR_SEGMENT_PLACEHOLDER_PREFIX = 'enter the value for ';