@capillarytech/creatives-library 9.0.14 → 9.0.15-alpha.1

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 (175) hide show
  1. package/constants/unified.js +32 -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/common.js +8 -0
  6. package/utils/commonUtils.js +19 -1
  7. package/utils/rcsPayloadUtils.js +92 -0
  8. package/utils/templateVarUtils.js +201 -0
  9. package/utils/tests/rcsPayloadUtils.test.js +226 -0
  10. package/utils/tests/templateVarUtils.test.js +204 -0
  11. package/v2Components/CapActionButton/constants.js +7 -0
  12. package/v2Components/CapActionButton/index.js +168 -109
  13. package/v2Components/CapActionButton/index.scss +156 -5
  14. package/v2Components/CapActionButton/messages.js +19 -3
  15. package/v2Components/CapActionButton/tests/index.test.js +42 -18
  16. package/v2Components/CapImageUpload/index.js +2 -2
  17. package/v2Components/CapTagList/index.js +10 -0
  18. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  24. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +60 -11
  25. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +201 -23
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +355 -76
  28. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
  29. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  30. package/v2Components/CommonTestAndPreview/constants.js +38 -2
  31. package/v2Components/CommonTestAndPreview/index.js +761 -222
  32. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  33. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  34. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  35. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
  36. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -267
  37. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  38. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  39. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  40. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  41. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  42. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  43. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  44. package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
  45. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  46. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
  47. package/v2Components/CommonTestAndPreview/utils.js +84 -0
  48. package/v2Components/FormBuilder/Classic.js +4487 -0
  49. package/v2Components/FormBuilder/Functional/FormBuilderShell.js +366 -0
  50. package/v2Components/FormBuilder/Functional/channels/registry.js +17 -0
  51. package/v2Components/FormBuilder/Functional/channels/sms/buildSubmitPayload.js +9 -0
  52. package/v2Components/FormBuilder/Functional/channels/sms/config.js +30 -0
  53. package/v2Components/FormBuilder/Functional/channels/sms/getEditorErrorDescriptor.js +46 -0
  54. package/v2Components/FormBuilder/Functional/channels/sms/getLiquidContent.js +13 -0
  55. package/v2Components/FormBuilder/Functional/channels/sms/index.js +22 -0
  56. package/v2Components/FormBuilder/Functional/channels/sms/tests/getEditorErrorDescriptor.test.js +52 -0
  57. package/v2Components/FormBuilder/Functional/channels/sms/tests/getLiquidContent.test.js +25 -0
  58. package/v2Components/FormBuilder/Functional/channels/sms/tests/validate.test.js +87 -0
  59. package/v2Components/FormBuilder/Functional/channels/sms/validate.js +89 -0
  60. package/v2Components/FormBuilder/Functional/constants.js +42 -0
  61. package/v2Components/FormBuilder/Functional/core/schema/fieldRegistry.js +38 -0
  62. package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +85 -0
  63. package/v2Components/FormBuilder/Functional/core/store/formReducer.js +81 -0
  64. package/v2Components/FormBuilder/Functional/core/store/selectors.js +30 -0
  65. package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +91 -0
  66. package/v2Components/FormBuilder/Functional/index.js +26 -0
  67. package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +59 -0
  68. package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +31 -0
  69. package/v2Components/FormBuilder/Functional/layout/Section.js +116 -0
  70. package/v2Components/FormBuilder/Functional/renderers/smsRenderers.js +258 -0
  71. package/v2Components/FormBuilder/Functional/tests/channelRegistry.test.js +21 -0
  72. package/v2Components/FormBuilder/Functional/tests/fieldRegistry.test.js +65 -0
  73. package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +97 -0
  74. package/v2Components/FormBuilder/Functional/tests/fixtures/smsParityCases.js +192 -0
  75. package/v2Components/FormBuilder/Functional/tests/formReducer.test.js +129 -0
  76. package/v2Components/FormBuilder/Functional/tests/initializeFormState.test.js +132 -0
  77. package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +40 -0
  78. package/v2Components/FormBuilder/Functional/tests/section.test.js +99 -0
  79. package/v2Components/FormBuilder/Functional/tests/selectors.test.js +67 -0
  80. package/v2Components/FormBuilder/Functional/tests/sms.crossFlowParity.test.js +155 -0
  81. package/v2Components/FormBuilder/Functional/tests/sms.liquid.test.js +172 -0
  82. package/v2Components/FormBuilder/Functional/tests/sms.rollout.test.js +122 -0
  83. package/v2Components/FormBuilder/Functional/tests/sms.shell.parity.test.js +329 -0
  84. package/v2Components/FormBuilder/Functional/tests/smsRenderers.test.js +160 -0
  85. package/v2Components/FormBuilder/Functional/tests/toLegacyFormData.test.js +95 -0
  86. package/v2Components/FormBuilder/_formBuilder.scss +5 -0
  87. package/v2Components/FormBuilder/index.js +41 -4479
  88. package/v2Components/FormBuilder/tests/__snapshots__/sms.characterization.test.js.snap +114 -0
  89. package/v2Components/FormBuilder/tests/entryGate.test.js +106 -0
  90. package/v2Components/FormBuilder/tests/sms.characterization.test.js +336 -0
  91. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  92. package/v2Components/SmsFallback/constants.js +94 -0
  93. package/v2Components/SmsFallback/index.js +958 -0
  94. package/v2Components/SmsFallback/index.scss +266 -0
  95. package/v2Components/SmsFallback/messages.js +78 -0
  96. package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
  97. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  98. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  99. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  100. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
  101. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  102. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  103. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  104. package/v2Components/TemplatePreview/_templatePreview.scss +41 -22
  105. package/v2Components/TemplatePreview/constants.js +2 -0
  106. package/v2Components/TemplatePreview/index.js +143 -31
  107. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  108. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  109. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  110. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  111. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  112. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  113. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  114. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  115. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
  116. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
  117. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  118. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +17 -0
  119. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  120. package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
  121. package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
  122. package/v2Containers/CreativesContainer/constants.js +11 -0
  123. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
  124. package/v2Containers/CreativesContainer/index.js +323 -104
  125. package/v2Containers/CreativesContainer/index.scss +83 -1
  126. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  127. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +79 -34
  128. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  129. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  130. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +333 -90
  131. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  132. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  133. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  134. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  135. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  136. package/v2Containers/Rcs/constants.js +131 -11
  137. package/v2Containers/Rcs/index.js +2602 -807
  138. package/v2Containers/Rcs/index.scss +289 -8
  139. package/v2Containers/Rcs/messages.js +34 -3
  140. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +227 -0
  141. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +73535 -38537
  142. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -8
  143. package/v2Containers/Rcs/tests/index.test.js +147 -36
  144. package/v2Containers/Rcs/tests/mockData.js +38 -0
  145. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  146. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  147. package/v2Containers/Rcs/utils.js +478 -11
  148. package/v2Containers/Sms/Create/index.js +115 -48
  149. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  150. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  151. package/v2Containers/SmsTrai/Create/index.js +9 -4
  152. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  153. package/v2Containers/SmsTrai/Edit/index.js +678 -169
  154. package/v2Containers/SmsTrai/Edit/index.scss +126 -0
  155. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  156. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
  157. package/v2Containers/SmsWrapper/index.js +37 -8
  158. package/v2Containers/TagList/index.js +6 -0
  159. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  160. package/v2Containers/Templates/_templates.scss +171 -12
  161. package/v2Containers/Templates/actions.js +11 -0
  162. package/v2Containers/Templates/constants.js +2 -0
  163. package/v2Containers/Templates/index.js +125 -55
  164. package/v2Containers/Templates/sagas.js +57 -13
  165. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  166. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
  167. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  168. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  169. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  170. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  171. package/v2Containers/TemplatesV2/index.js +86 -23
  172. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  173. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  174. package/v2Containers/Whatsapp/index.js +3 -20
  175. 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,14 @@ 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">
523
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
493
524
  <CapIcon type="launch" size="xs" />
494
525
  {suggestion.text}
495
526
  </CapLabel>,
496
527
  );
497
528
  } else if (suggestion.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
498
529
  renderArray.push(
499
- <CapLabel type="label21" className="rcs-cta-preview">
530
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
500
531
  <CapIcon type="call" size="xs" />
501
532
  {suggestion.text}
502
533
  </CapLabel>,
@@ -509,23 +540,20 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
509
540
  const renderTextPreviewContent = () => (
510
541
  <>
511
542
  {rcsTitle && (
512
- <>
513
543
  <CapLabel
514
544
  type="label5"
515
545
  className="message-pop-item align-left rcs-content"
516
546
  fontWeight="bold"
517
547
  >
518
- {rcsTitle}
548
+ {resolveVarsWithMap(rcsTitle, rcsCardVarMapped)}
519
549
  </CapLabel>
520
- <CapDivider className="whatsapp-divider" />
521
- </>
522
550
  )}
523
551
  {rcsDesc && (
524
552
  <CapLabel
525
553
  type="label5"
526
554
  className="message-pop-item align-left rcs-desc rcs-content"
527
555
  >
528
- {rcsDesc}
556
+ {resolveVarsWithMap(rcsDesc, rcsCardVarMapped)}
529
557
  </CapLabel>
530
558
  )}
531
559
  {renderRcsSuggestionsPreview()}
@@ -542,50 +570,134 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
542
570
  />
543
571
  )}
544
572
  {rcsVideoSrc && (
545
- <div className="video-preview">
573
+ <CapRow className="video-preview">
546
574
  <CapImage
547
575
  src={rcsVideoSrc}
548
576
  className="rcs-image"
549
577
  alt={formatMessage(messages.previewGenerated)}
550
578
  />
551
- <div className="icon-position">
579
+ <CapRow className="icon-position">
552
580
  <CapImage
553
581
  className="video-icon"
554
582
  src={videoPlay}
555
583
  />
556
- </div>
557
- </div>
584
+ </CapRow>
585
+ </CapRow>
558
586
  )}
559
587
  </>
560
588
  );
561
589
 
562
590
  const renderRcsPreviewContent = () => {
591
+ const carouselCards = Array.isArray(resolvedRcsCarouselData) ? resolvedRcsCarouselData : [];
592
+ if (carouselCards.length > 0) {
593
+ return (
594
+ <CapRow className="msg-container sms">
595
+ <CapRow className="message-pop sms">
596
+ <CapRow className="msg-container-carousel">
597
+ <CapRow className="scroll-container">
598
+ {carouselCards.map((card, idx) => {
599
+ const key = `rcs-carousel-${idx}-${card?.bodyText || card?.imageSrc || card?.videoPreviewImg || ''}`;
600
+ const isVideo = (card?.mediaType || '').toLowerCase() === VIDEO;
601
+ const cardSuggestions = Array.isArray(card?.suggestions) ? card.suggestions : [];
602
+ const effectiveCardVarMap = { ...rcsCardVarMapped, ...(card?.cardVarMapped || {}) };
603
+ const suggestionsNode = cardSuggestions.length > 0
604
+ ? renderRcsSuggestionsPreview(cardSuggestions)
605
+ : null;
606
+
607
+ const resolvedCardTitle = resolveVarsWithMap(card?.title, effectiveCardVarMap);
608
+ const resolvedCardBodyText = resolveVarsWithMap(card?.bodyText, effectiveCardVarMap);
609
+
610
+ return (
611
+ <CapRow
612
+ key={key}
613
+ className="message-pop align-left message-pop-carousel rcs-carousel-card"
614
+ >
615
+ <CapRow className="whatsapp-content">
616
+ {!isVideo && (
617
+ <CapImage
618
+ src={card?.imageSrc ? card.imageSrc : whatsappImageEmptyPreview}
619
+ className="whatsapp-image"
620
+ alt={formatMessage(messages.previewGenerated)}
621
+ />
622
+ )}
623
+ {isVideo && (
624
+ <CapTooltip title={formatMessage(messages.videoPreviewTooltip)}>
625
+ <CapRow className="video-preview">
626
+ <CapImage
627
+ src={card?.videoPreviewImg ? card.videoPreviewImg : whatsappVideoEmptyPreview}
628
+ className="whatsapp-image"
629
+ alt={formatMessage(messages.previewGenerated)}
630
+ />
631
+ <CapRow className="icon-position">
632
+ <CapImage className="video-icon" src={videoPlay} alt="Play" />
633
+ </CapRow>
634
+ </CapRow>
635
+ </CapTooltip>
636
+ )}
637
+
638
+ {(resolvedCardTitle || resolvedCardBodyText) && (
639
+ <CapRow className="carousel-content">
640
+ {!!resolvedCardTitle && (
641
+ <CapLabel
642
+ type={card?.titleLabelType || 'label1'}
643
+ className="carousel-title"
644
+ >
645
+ {resolvedCardTitle}
646
+ </CapLabel>
647
+ )}
648
+ {!!resolvedCardBodyText && (
649
+ <CapLabel
650
+ type={card?.bodyLabelType || 'label2'}
651
+ className="carousel-message"
652
+ >
653
+ {resolvedCardBodyText}
654
+ </CapLabel>
655
+ )}
656
+ </CapRow>
657
+ )}
658
+
659
+ {!!suggestionsNode && (
660
+ <>
661
+ {suggestionsNode}
662
+ </>
663
+ )}
664
+ </CapRow>
665
+ </CapRow>
666
+ );
667
+ })}
668
+ </CapRow>
669
+ </CapRow>
670
+ </CapRow>
671
+ </CapRow>
672
+ );
673
+ }
674
+
563
675
  if (rcsOrientation === HORIZONTAL) {
564
676
  return rcsType === RIGHT ? (
565
- <div className="msg-container sms">
566
- <div className="message-pop sms horizontal">
677
+ <CapRow className="msg-container sms">
678
+ <CapRow className="message-pop sms horizontal">
567
679
  <CapColumn className="rcs-preview-text" span={12}>{renderTextPreviewContent()}</CapColumn>
568
680
  <CapColumn span={12}>{renderMediaPreviewContent()}</CapColumn>
569
- </div>
570
- </div>
681
+ </CapRow>
682
+ </CapRow>
571
683
 
572
684
  ) : (
573
- <div className="msg-container sms">
574
- <div className="message-pop sms horizontal">
685
+ <CapRow className="msg-container sms">
686
+ <CapRow className="message-pop sms horizontal">
575
687
  <CapColumn span={12}>{renderMediaPreviewContent()}</CapColumn>
576
688
  <CapColumn className="rcs-preview-text" span={12}>{renderTextPreviewContent()}</CapColumn>
577
- </div>
578
- </div>
689
+ </CapRow>
690
+ </CapRow>
579
691
  );
580
692
  }
581
693
 
582
694
  return (
583
- <div className="msg-container sms">
584
- <div className="message-pop sms">
695
+ <CapRow className="msg-container sms">
696
+ <CapRow className="message-pop sms">
585
697
  {renderMediaPreviewContent()}
586
698
  {renderTextPreviewContent()}
587
- </div>
588
- </div>
699
+ </CapRow>
700
+ </CapRow>
589
701
  );
590
702
  };
591
703
 
@@ -1306,14 +1418,14 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
1306
1418
  ""
1307
1419
  )}
1308
1420
  {channel?.toUpperCase() === RCS && (
1309
- <div className="shell-v2 align-center rcs-preview">
1421
+ <CapRow className="shell-v2 align-center rcs-preview">
1310
1422
  <CapImage
1311
1423
  className="preview-image"
1312
1424
  src={rcsIosPreview ? smsMobileIos : smsMobileAndroid}
1313
1425
  alt={formatMessage(messages.previewGenerated)}
1314
1426
  />
1315
1427
  {renderRcsPreviewContent()}
1316
- </div>
1428
+ </CapRow>
1317
1429
 
1318
1430
  )}
1319
1431
  {channel?.toUpperCase() === ZALO && (
@@ -1515,4 +1627,4 @@ TemplatePreview.propTypes = {
1515
1627
  rcsOrientation: PropTypes.string,
1516
1628
  };
1517
1629
 
1518
- export default (injectIntl(TemplatePreview));
1630
+ 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 ';