@capillarytech/creatives-library 8.0.330-alpha.0 → 8.0.330-alpha.2

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 (75) hide show
  1. package/package.json +1 -1
  2. package/utils/tests/tagValidations.test.js +20 -0
  3. package/v2Components/CapActionButton/constants.js +7 -0
  4. package/v2Components/CapActionButton/index.js +167 -109
  5. package/v2Components/CapActionButton/index.scss +157 -6
  6. package/v2Components/CapActionButton/messages.js +19 -3
  7. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  8. package/v2Components/CapTagList/index.js +28 -23
  9. package/v2Components/CapTagList/style.scss +29 -0
  10. package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  11. package/v2Components/CapTagListWithInput/index.js +4 -0
  12. package/v2Components/CapWhatsappCTA/index.js +2 -0
  13. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +1 -0
  14. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
  15. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
  16. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +323 -77
  17. package/v2Components/CommonTestAndPreview/messages.js +8 -0
  18. package/v2Components/CommonTestAndPreview/reducer.js +3 -1
  19. package/v2Components/CommonTestAndPreview/sagas.js +2 -1
  20. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  21. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  22. package/v2Components/FormBuilder/index.js +1 -0
  23. package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
  24. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  25. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
  26. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
  27. package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
  28. package/v2Components/TemplatePreview/constants.js +2 -0
  29. package/v2Components/TemplatePreview/index.js +143 -28
  30. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  31. package/v2Components/mockdata.js +1 -0
  32. package/v2Containers/BeeEditor/index.js +19 -1
  33. package/v2Containers/CreativesContainer/SlideBoxContent.js +28 -1
  34. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +5 -0
  35. package/v2Containers/Email/index.js +78 -39
  36. package/v2Containers/Email/reducer.js +2 -2
  37. package/v2Containers/Email/sagas.js +3 -1
  38. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -2
  39. package/v2Containers/Email/tests/sagas.test.js +230 -0
  40. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +6 -1
  41. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  42. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
  43. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  44. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -1
  45. package/v2Containers/EmailWrapper/index.js +4 -0
  46. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  47. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
  48. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +1 -0
  49. package/v2Containers/MobilePush/Create/index.js +2 -0
  50. package/v2Containers/MobilePush/Edit/index.js +2 -0
  51. package/v2Containers/MobilepushWrapper/index.js +3 -1
  52. package/v2Containers/Rcs/constants.js +79 -5
  53. package/v2Containers/Rcs/index.js +1374 -73
  54. package/v2Containers/Rcs/index.js.rej +1336 -0
  55. package/v2Containers/Rcs/index.scss +191 -0
  56. package/v2Containers/Rcs/index.scss.rej +74 -0
  57. package/v2Containers/Rcs/messages.js +26 -1
  58. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +69173 -118166
  59. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
  60. package/v2Containers/Rcs/tests/index.test.js +132 -94
  61. package/v2Containers/Rcs/tests/utils.test.js +220 -38
  62. package/v2Containers/Rcs/utils.js +77 -1
  63. package/v2Containers/Sms/Edit/index.js +2 -0
  64. package/v2Containers/SmsWrapper/index.js +2 -0
  65. package/v2Containers/TagList/index.js +73 -20
  66. package/v2Containers/TagList/messages.js +4 -0
  67. package/v2Containers/TagList/tests/TagList.test.js +124 -20
  68. package/v2Containers/TagList/tests/mockdata.js +17 -0
  69. package/v2Containers/Templates/_templates.scss +99 -0
  70. package/v2Containers/Templates/index.js +29 -14
  71. package/v2Containers/Viber/index.js +3 -0
  72. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
  73. package/v2Containers/WebPush/Create/index.js +10 -2
  74. package/v2Containers/Whatsapp/index.js +5 -0
  75. package/v2Containers/Zalo/index.js +2 -0
@@ -59,6 +59,7 @@ const CodeEditorPaneComponent = ({
59
59
  injectedTags = {},
60
60
  location,
61
61
  eventContextTags = [],
62
+ waitEventContextTags = {},
62
63
  selectedOfferDetails = [],
63
64
  channel,
64
65
  userLocale = 'en',
@@ -289,6 +290,7 @@ const CodeEditorPaneComponent = ({
289
290
  location={location}
290
291
  selectedOfferDetails={selectedOfferDetails}
291
292
  eventContextTags={eventContextTags}
293
+ waitEventContextTags={waitEventContextTags}
292
294
  popoverPlacement="rightTop"
293
295
  />
294
296
  </CapRow>
@@ -327,6 +329,7 @@ CodeEditorPane.propTypes = {
327
329
  injectedTags: PropTypes.object,
328
330
  location: PropTypes.object,
329
331
  eventContextTags: PropTypes.array,
332
+ waitEventContextTags: PropTypes.object,
330
333
  selectedOfferDetails: PropTypes.array,
331
334
  channel: PropTypes.string,
332
335
  userLocale: PropTypes.string,
@@ -537,20 +537,21 @@
537
537
  .unicode-disabled{
538
538
  font-size: 16px;
539
539
  }
540
+ position: absolute;
541
+ overflow: auto;
542
+ top: 0;
543
+ left: 17%;
544
+ white-space: pre-wrap;
545
+ word-break: break-word;
546
+ max-height: 100%;
547
+ line-height: 14px;
548
+ font-size: 10px;
549
+ font-family: 'open-sans';
550
+
540
551
  &.sms {
541
- max-height: 100%;
542
- position: absolute;
543
552
  // width: initial;
544
- overflow: auto;
545
- top: 0;
546
- left: 17%;
547
- white-space: pre-wrap;
548
- word-break: break-word;
549
- line-height: 14px;
550
553
  padding: 0 8px 8px 8px;
551
- font-size: 10px;
552
554
  width: 100%;
553
- font-family: 'open-sans';
554
555
  .rcs-image{
555
556
  width: 100%;
556
557
  height: 123px;
@@ -582,22 +583,12 @@
582
583
  &:not(.sms){
583
584
  padding: 4px;
584
585
  background: #3F51B5;
585
- position: absolute;
586
586
  // width: initial;
587
- overflow: auto;
588
- top: 0;
589
- left: 17%;
590
- white-space: pre-wrap;
591
- word-break: break-word;
592
587
  border-radius: 4px;
593
- max-height: 100%;
594
588
  color: #FFFFFF;
595
589
  width: 70%;
596
590
  min-height: 26px;
597
- line-height: 14px;
598
591
  padding: 8px;
599
- font-size: 10px;
600
- font-family: 'open-sans';
601
592
  }
602
593
  &.message-pop-carousel {
603
594
  position: relative;
@@ -842,6 +833,27 @@
842
833
  }
843
834
  }
844
835
 
836
+ .shell-v2.rcs-preview {
837
+ // Override `.message-pop:not(.sms)` (blue background) for RCS carousel cards.
838
+ .msg-container.sms {
839
+ .message-pop.sms {
840
+ .rcs-carousel-card {
841
+ background: $CAP_WHITE;
842
+ color: $CAP_G01;
843
+ width: 10.4rem;
844
+ left: 0;
845
+ flex-shrink: 0;
846
+ padding: $CAP_SPACE_04 0 $CAP_SPACE_08;
847
+ border-radius: 0.428rem;
848
+
849
+ .carousel-title {
850
+ font-weight: 700 !important;
851
+ }
852
+ }
853
+ }
854
+ }
855
+ }
856
+
845
857
  .align-center {
846
858
  text-align: center;
847
859
  }
@@ -1012,9 +1024,7 @@
1012
1024
  top: 0;
1013
1025
  }
1014
1026
  .video-icon {
1015
- position: absolute;
1016
- right: -17px;
1017
- bottom: -17px;
1027
+ position: sticky;
1018
1028
  }
1019
1029
 
1020
1030
  .zalo-preview-container {
@@ -0,0 +1,2 @@
1
+ /** Matches {{ varName }} placeholders (alphanumeric + underscore) */
2
+ export const TEMPLATE_VAR_REGEX = /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g;
@@ -52,6 +52,7 @@ import { QUICK_REPLY, WHATSAPP_CATEGORIES, PHONE_NUMBER } from '../../v2Containe
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');
@@ -234,7 +235,23 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
234
235
  let content = channel && channel.toLowerCase() === 'sms' ? [this.props.content] : this.props.content;
235
236
  const { formatMessage } = intl;
236
237
  const { rcsPreviewContent, inAppPreviewContent, viberPreviewContent, isBeeFreeTemplate } = content || {};
237
- const { rcsImageSrc, rcsVideoSrc, rcsTitle, rcsDesc, rcsSuggestions } = rcsPreviewContent || {};
238
+ const normalizedRcsPreviewContent = Array.isArray(rcsPreviewContent)
239
+ ? { carouselData: rcsPreviewContent }
240
+ : (rcsPreviewContent || {});
241
+ const {
242
+ rcsImageSrc,
243
+ rcsVideoSrc,
244
+ rcsTitle,
245
+ rcsDesc,
246
+ rcsSuggestions,
247
+ carouselData: rcsCarouselData,
248
+ rcsCarouselData: rcsCarouselDataAlt,
249
+ cardVarMapped: rcsCardVarMapped,
250
+ } = normalizedRcsPreviewContent;
251
+ const resolvedRcsCarouselData =
252
+ Array.isArray(rcsCarouselData) && rcsCarouselData.length > 0
253
+ ? rcsCarouselData
254
+ : rcsCarouselDataAlt;
238
255
  const {
239
256
  videoParams,
240
257
  imageURL,
@@ -315,6 +332,18 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
315
332
  'flex-shrink': 0,
316
333
  'left': 0,
317
334
  };
335
+
336
+ const resolveVarsWithMap = (input, varMap) => {
337
+ if (input === null || input === undefined) return '';
338
+ const str = typeof input === 'string' ? input : String(input);
339
+ return str.replace(TEMPLATE_VAR_REGEX, (token, varName) => {
340
+ const mappedValue = varMap?.[varName];
341
+ if (mappedValue === null || mappedValue === undefined || String(mappedValue) === '') {
342
+ return token;
343
+ }
344
+ return String(mappedValue);
345
+ });
346
+ };
318
347
  const getVideoContent = ({
319
348
  video,
320
349
  actionUrl,
@@ -471,13 +500,15 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
471
500
  const whatsappUpdatedAccountName = whatsappAccountName || templateData?.versions?.base?.content?.whatsapp?.accountName || '';
472
501
  const whatsappUpdatedLen = whatsappContentLen !== undefined ? whatsappContentLen : content?.charCount;
473
502
 
474
- const renderRcsSuggestionsPreview = () => {
503
+ const renderRcsSuggestionsPreview = (suggestionsArg) => {
475
504
  const renderArray = [];
476
- (rcsSuggestions || []).forEach((suggestion) => {
477
- renderArray.push(<CapDivider className="whatsapp-divider" />);
505
+ const suggestions = Array.isArray(suggestionsArg) ? suggestionsArg : (rcsSuggestions || []);
506
+ (suggestions || []).forEach((suggestion, idx) => {
507
+ const suggestionKey = `${suggestion?.type || 'unknown'}-${suggestion?.text || ''}-${idx}`;
508
+ renderArray.push(<CapDivider key={`rcs-divider-${suggestionKey}`} className="whatsapp-divider" />);
478
509
  if (suggestion.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
479
510
  renderArray.push(
480
- <CapLabel type="label21" className="rcs-cta-preview">
511
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
481
512
  <CapIcon
482
513
  type='small-link'
483
514
  size="xs"
@@ -487,14 +518,14 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
487
518
  );
488
519
  } else if (suggestion.type === RCS_BUTTON_TYPES.CTA) {
489
520
  renderArray.push(
490
- <CapLabel type="label21" className="rcs-cta-preview">
521
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
491
522
  <CapIcon type="launch" size="xs" />
492
523
  {suggestion.text}
493
524
  </CapLabel>,
494
525
  );
495
526
  } else if (suggestion.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
496
527
  renderArray.push(
497
- <CapLabel type="label21" className="rcs-cta-preview">
528
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
498
529
  <CapIcon type="call" size="xs" />
499
530
  {suggestion.text}
500
531
  </CapLabel>,
@@ -513,7 +544,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
513
544
  className="message-pop-item align-left rcs-content"
514
545
  fontWeight="bold"
515
546
  >
516
- {rcsTitle}
547
+ {resolveVarsWithMap(rcsTitle, rcsCardVarMapped)}
517
548
  </CapLabel>
518
549
  <CapDivider className="whatsapp-divider" />
519
550
  </>
@@ -523,7 +554,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
523
554
  type="label5"
524
555
  className="message-pop-item align-left rcs-desc rcs-content"
525
556
  >
526
- {rcsDesc}
557
+ {resolveVarsWithMap(rcsDesc, rcsCardVarMapped)}
527
558
  </CapLabel>
528
559
  )}
529
560
  {renderRcsSuggestionsPreview()}
@@ -540,50 +571,134 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
540
571
  />
541
572
  )}
542
573
  {rcsVideoSrc && (
543
- <div className="video-preview">
574
+ <CapRow className="video-preview">
544
575
  <CapImage
545
576
  src={rcsVideoSrc}
546
577
  className="rcs-image"
547
578
  alt={formatMessage(messages.previewGenerated)}
548
579
  />
549
- <div className="icon-position">
580
+ <CapRow className="icon-position">
550
581
  <CapImage
551
582
  className="video-icon"
552
583
  src={videoPlay}
553
584
  />
554
- </div>
555
- </div>
585
+ </CapRow>
586
+ </CapRow>
556
587
  )}
557
588
  </>
558
589
  );
559
590
 
560
591
  const renderRcsPreviewContent = () => {
592
+ const carouselCards = Array.isArray(resolvedRcsCarouselData) ? resolvedRcsCarouselData : [];
593
+ if (carouselCards.length > 0) {
594
+ return (
595
+ <CapRow className="msg-container sms">
596
+ <CapRow className="message-pop sms">
597
+ <CapRow className="msg-container-carousel">
598
+ <CapRow className="scroll-container">
599
+ {carouselCards.map((card, idx) => {
600
+ const key = `rcs-carousel-${idx}-${card?.bodyText || card?.imageSrc || card?.videoPreviewImg || ''}`;
601
+ const isVideo = (card?.mediaType || '').toLowerCase() === 'video';
602
+ const cardSuggestions = Array.isArray(card?.suggestions) ? card.suggestions : [];
603
+ const effectiveCardVarMap = card?.cardVarMapped || rcsCardVarMapped;
604
+ const suggestionsNode = cardSuggestions.length > 0
605
+ ? renderRcsSuggestionsPreview(cardSuggestions)
606
+ : null;
607
+
608
+ const resolvedCardTitle = resolveVarsWithMap(card?.title, effectiveCardVarMap);
609
+ const resolvedCardBodyText = resolveVarsWithMap(card?.bodyText, effectiveCardVarMap);
610
+
611
+ return (
612
+ <CapRow
613
+ key={key}
614
+ className="message-pop align-left message-pop-carousel rcs-carousel-card"
615
+ >
616
+ <CapRow className="whatsapp-content">
617
+ {!isVideo && (
618
+ <CapImage
619
+ src={card?.imageSrc ? card.imageSrc : whatsappImageEmptyPreview}
620
+ className="whatsapp-image"
621
+ alt={formatMessage(messages.previewGenerated)}
622
+ />
623
+ )}
624
+ {isVideo && (
625
+ <CapTooltip title={formatMessage(messages.videoPreviewTooltip)}>
626
+ <CapRow className="video-preview">
627
+ <CapImage
628
+ src={card?.videoPreviewImg ? card.videoPreviewImg : whatsappVideoEmptyPreview}
629
+ className="whatsapp-image"
630
+ alt={formatMessage(messages.previewGenerated)}
631
+ />
632
+ <CapRow className="icon-position">
633
+ <CapImage className="video-icon" src={videoPlay} alt="Play" />
634
+ </CapRow>
635
+ </CapRow>
636
+ </CapTooltip>
637
+ )}
638
+
639
+ {(resolvedCardTitle || resolvedCardBodyText) && (
640
+ <CapRow className="carousel-content">
641
+ {!!resolvedCardTitle && (
642
+ <CapLabel
643
+ type={card?.titleLabelType || 'label1'}
644
+ className="carousel-title"
645
+ >
646
+ {resolvedCardTitle}
647
+ </CapLabel>
648
+ )}
649
+ {!!resolvedCardBodyText && (
650
+ <CapLabel
651
+ type={card?.bodyLabelType || 'label2'}
652
+ className="carousel-message"
653
+ >
654
+ {resolvedCardBodyText}
655
+ </CapLabel>
656
+ )}
657
+ </CapRow>
658
+ )}
659
+
660
+ {!!suggestionsNode && (
661
+ <>
662
+ {suggestionsNode}
663
+ </>
664
+ )}
665
+ </CapRow>
666
+ </CapRow>
667
+ );
668
+ })}
669
+ </CapRow>
670
+ </CapRow>
671
+ </CapRow>
672
+ </CapRow>
673
+ );
674
+ }
675
+
561
676
  if (rcsOrientation === HORIZONTAL) {
562
677
  return rcsType === RIGHT ? (
563
- <div className="msg-container sms">
564
- <div className="message-pop sms horizontal">
678
+ <CapRow className="msg-container sms">
679
+ <CapRow className="message-pop sms horizontal">
565
680
  <CapColumn className="rcs-preview-text" span={12}>{renderTextPreviewContent()}</CapColumn>
566
681
  <CapColumn span={12}>{renderMediaPreviewContent()}</CapColumn>
567
- </div>
568
- </div>
682
+ </CapRow>
683
+ </CapRow>
569
684
 
570
685
  ) : (
571
- <div className="msg-container sms">
572
- <div className="message-pop sms horizontal">
686
+ <CapRow className="msg-container sms">
687
+ <CapRow className="message-pop sms horizontal">
573
688
  <CapColumn span={12}>{renderMediaPreviewContent()}</CapColumn>
574
689
  <CapColumn className="rcs-preview-text" span={12}>{renderTextPreviewContent()}</CapColumn>
575
- </div>
576
- </div>
690
+ </CapRow>
691
+ </CapRow>
577
692
  );
578
693
  }
579
694
 
580
695
  return (
581
- <div className="msg-container sms">
582
- <div className="message-pop sms">
696
+ <CapRow className="msg-container sms">
697
+ <CapRow className="message-pop sms">
583
698
  {renderMediaPreviewContent()}
584
699
  {renderTextPreviewContent()}
585
- </div>
586
- </div>
700
+ </CapRow>
701
+ </CapRow>
587
702
  );
588
703
  };
589
704
 
@@ -1291,14 +1406,14 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
1291
1406
  ""
1292
1407
  )}
1293
1408
  {channel?.toUpperCase() === RCS && (
1294
- <div className="shell-v2 align-center rcs-preview">
1409
+ <CapRow className="shell-v2 align-center rcs-preview">
1295
1410
  <CapImage
1296
1411
  className="preview-image"
1297
1412
  src={rcsIosPreview ? smsMobileIos : smsMobileAndroid}
1298
1413
  alt={formatMessage(messages.previewGenerated)}
1299
1414
  />
1300
1415
  {renderRcsPreviewContent()}
1301
- </div>
1416
+ </CapRow>
1302
1417
 
1303
1418
  )}
1304
1419
  {channel?.toUpperCase() === ZALO && (
@@ -1499,4 +1614,4 @@ TemplatePreview.propTypes = {
1499
1614
  rcsOrientation: PropTypes.string,
1500
1615
  };
1501
1616
 
1502
- export default (injectIntl(TemplatePreview));
1617
+ 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
  });
@@ -94,3 +94,4 @@ export default {
94
94
  totalCount: 7,
95
95
  },
96
96
  };
97
+
@@ -53,11 +53,15 @@ function BeeEditor(props) {
53
53
  BEESelect,
54
54
  currentOrgDetails,
55
55
  eventContextTags,
56
+ waitEventContextTags,
57
+ isGetBeeData,
58
+ getBEEData,
56
59
  } = props;
57
60
  const { saveRowRequest } = BEESelect;
58
61
  const {formatMessage} = intl;
59
62
  const UNSUBSCRIBE = 'unsubscribe';
60
63
  let beePluginInstance = null;
64
+ const isGetBeeDataRef = useRef(isGetBeeData);
61
65
  const categoryOptions = [{key: 'cta', value: 'cta', label: formatMessage(messages.cta)},
62
66
  {key: 'footer', value: 'footer', label: formatMessage(messages.footer)},
63
67
  {key: 'header', value: 'header', label: formatMessage(messages.header)},
@@ -111,6 +115,10 @@ function BeeEditor(props) {
111
115
  tokenDataRef.current = tokenData;
112
116
  }, [tokenData]);
113
117
 
118
+ useEffect(() => {
119
+ isGetBeeDataRef.current = isGetBeeData;
120
+ }, [isGetBeeData]);
121
+
114
122
  useEffect(() => {
115
123
  // Only initialize if we have both tokenData and beeJson
116
124
  const currentTokenData = tokenDataRef.current;
@@ -220,7 +228,13 @@ function BeeEditor(props) {
220
228
  },
221
229
  },
222
230
  onSave: (jsonFile, htmlFile) => {
223
- saveBeeData(jsonFile, htmlFile);
231
+ if (isGetBeeDataRef.current && typeof getBEEData === 'function') {
232
+ getBEEData(jsonFile, htmlFile);
233
+ return;
234
+ }
235
+ if (typeof saveBeeData === 'function') {
236
+ saveBeeData(jsonFile, htmlFile);
237
+ }
224
238
  },
225
239
  onSaveRow: (rowJSON) => {
226
240
  actions.createCustomRow(JSON.parse(rowJSON), callbackSaveRow);
@@ -368,6 +382,7 @@ function BeeEditor(props) {
368
382
  }}
369
383
  onContextChange={onContextChange}
370
384
  eventContextTags={eventContextTags}
385
+ waitEventContextTags={waitEventContextTags}
371
386
  />
372
387
  <CapModal
373
388
  className="custom-row-modal"
@@ -414,6 +429,9 @@ BeeEditor.propTypes = {
414
429
  onContextChange: PropTypes.func,
415
430
  userLocale: PropTypes.string,
416
431
  eventContextTags: PropTypes.array,
432
+ waitEventContextTags: PropTypes.object,
433
+ isGetBeeData: PropTypes.bool,
434
+ getBEEData: PropTypes.func,
417
435
  };
418
436
 
419
437
  const mapStateToProps = () => createStructuredSelector({