@capillarytech/creatives-library 9.0.35 → 9.0.36-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 (40) hide show
  1. package/package.json +1 -1
  2. package/translations/en.json +2 -2
  3. package/utils/permissions.js +59 -0
  4. package/utils/tests/permissions.test.js +116 -0
  5. package/v2Components/AccessForbidden/_accessForbidden.scss +14 -0
  6. package/v2Components/AccessForbidden/index.js +5 -3
  7. package/v2Components/AccessForbidden/messages.js +3 -2
  8. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberCarouselPreviewCards.js +132 -0
  9. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +108 -15
  10. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +139 -1
  11. package/v2Components/CommonTestAndPreview/UnifiedPreview/_viberCarouselPreviewCards.scss +131 -0
  12. package/v2Components/CommonTestAndPreview/index.js +240 -26
  13. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +364 -0
  14. package/v2Components/NoPermissionWrapper/_noPermissionWrapper.scss +12 -0
  15. package/v2Components/NoPermissionWrapper/index.js +26 -0
  16. package/v2Containers/App/constants.js +5 -0
  17. package/v2Containers/Assets/Gallery/_gallery.scss +6 -0
  18. package/v2Containers/Assets/Gallery/index.js +34 -10
  19. package/v2Containers/Cap/index.js +5 -2
  20. package/v2Containers/Cap/messages.js +4 -0
  21. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +2 -2
  22. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +6 -6
  23. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +4 -4
  24. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +32 -32
  25. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +36 -36
  26. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +118 -118
  27. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +8 -8
  28. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +18 -18
  29. package/v2Containers/Templates/ChannelTypeIllustration.js +13 -2
  30. package/v2Containers/Templates/_templates.scss +201 -0
  31. package/v2Containers/Templates/index.js +244 -110
  32. package/v2Containers/Templates/tests/ChannelTypeIllustration.test.js +1 -1
  33. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +174 -121
  34. package/v2Containers/TemplatesV2/index.js +16 -5
  35. package/v2Containers/Viber/constants.js +21 -0
  36. package/v2Containers/Viber/index.js +719 -49
  37. package/v2Containers/Viber/index.scss +175 -0
  38. package/v2Containers/Viber/messages.js +121 -0
  39. package/v2Containers/Viber/tests/index.test.js +80 -0
  40. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +268 -268
@@ -1,4 +1,5 @@
1
1
  @import '~@capillarytech/cap-ui-library/styles/_variables';
2
+ @import './_viberCarouselPreviewCards.scss';
2
3
 
3
4
  /**
4
5
  * UnifiedPreview Styles
@@ -782,9 +783,13 @@
782
783
 
783
784
  .viber-content-overlay {
784
785
  top: 3.2rem;
785
- .viber-navigation-bar {
786
+ overflow: hidden;
787
+
788
+ .sms-navigation-bar.viber-navigation-bar {
789
+ width: calc(100% - #{$CAP_SPACE_06} - #{$CAP_SPACE_08});
786
790
  margin-left: $CAP_SPACE_06;
787
791
  margin-right: $CAP_SPACE_08;
792
+ box-sizing: border-box;
788
793
  }
789
794
  }
790
795
 
@@ -2407,12 +2412,21 @@
2407
2412
  // Body content follows TemplatePreview Viber structure
2408
2413
  .sms-content-overlay {
2409
2414
  &.viber-preview {
2415
+ overflow: hidden;
2416
+
2410
2417
  // Viber message container
2411
2418
  .viber-message-container {
2412
2419
  position: relative;
2413
2420
  flex: 1;
2414
2421
  display: flex;
2415
2422
  flex-direction: column;
2423
+ overflow-x: hidden;
2424
+ overflow-y: auto;
2425
+ -webkit-overflow-scrolling: touch;
2426
+ min-width: 0;
2427
+ max-width: 100%;
2428
+ box-sizing: border-box;
2429
+ width: calc(100% - #{$CAP_SPACE_06} - #{$CAP_SPACE_08});
2416
2430
  padding: 0 $CAP_SPACE_16;
2417
2431
  background-color: #ffffff;
2418
2432
  margin-left: $CAP_SPACE_06;
@@ -2420,6 +2434,20 @@
2420
2434
  border-bottom-left-radius: 2.6rem;
2421
2435
  border-bottom-right-radius: 2.6rem;
2422
2436
 
2437
+ &.viber-message-container-carousel {
2438
+ padding-left: $CAP_SPACE_08;
2439
+ padding-right: $CAP_SPACE_08;
2440
+ width: calc(100% - #{$CAP_SPACE_04} - #{$CAP_SPACE_04});
2441
+ margin-left: $CAP_SPACE_04;
2442
+ margin-right: $CAP_SPACE_04;
2443
+
2444
+ > .ant-row,
2445
+ .ant-row.msg-container.viber-preview-carousel {
2446
+ margin-left: 0;
2447
+ margin-right: 0;
2448
+ }
2449
+ }
2450
+
2423
2451
  // Brand name display (from TemplatePreview line 1136)
2424
2452
  .viber-brand-name {
2425
2453
  position: absolute;
@@ -2441,6 +2469,16 @@
2441
2469
  flex-direction: row;
2442
2470
  align-items: start;
2443
2471
 
2472
+ &.viber-preview-carousel {
2473
+ width: 100%;
2474
+ max-width: 100%;
2475
+ margin-left: 0;
2476
+ margin-right: 0;
2477
+ max-height: 100%;
2478
+ min-width: 0;
2479
+ box-sizing: border-box;
2480
+ }
2481
+
2444
2482
  // Account icon (from TemplatePreview line 1146-1160)
2445
2483
  .viber-account-icon {
2446
2484
  width: $CAP_SPACE_20;
@@ -2465,6 +2503,22 @@
2465
2503
  border-radius: $CAP_SPACE_04;
2466
2504
  padding: $CAP_SPACE_04;
2467
2505
 
2506
+ &.viber-message-pop-carousel {
2507
+ width: 100%;
2508
+ max-width: 100%;
2509
+ min-width: 0;
2510
+ left: 0;
2511
+ margin-top: 0;
2512
+ padding: 0;
2513
+ background: transparent;
2514
+ display: flex;
2515
+ flex-direction: column;
2516
+ align-items: flex-start;
2517
+ gap: $CAP_SPACE_06;
2518
+ overflow: hidden;
2519
+ box-sizing: border-box;
2520
+ }
2521
+
2468
2522
  // Text Viber preview (from TemplatePreview line 1166-1174)
2469
2523
  .viber-message-text {
2470
2524
  margin: 0.107rem $CAP_SPACE_06 $CAP_SPACE_01 0.5rem;
@@ -2536,6 +2590,85 @@
2536
2590
  }
2537
2591
  }
2538
2592
 
2593
+ .viber-carousel-preview {
2594
+ width: 100%;
2595
+ max-width: 100%;
2596
+ min-width: 0;
2597
+ display: flex;
2598
+ flex-direction: column;
2599
+ align-items: flex-start;
2600
+ gap: $CAP_SPACE_06;
2601
+ overflow: hidden;
2602
+ box-sizing: border-box;
2603
+ }
2604
+
2605
+ .viber-carousel-message-pop,
2606
+ .viber-carousel-cards-pop {
2607
+ width: 100%;
2608
+ max-width: 100%;
2609
+ min-width: 0;
2610
+ background: $CAP_G08;
2611
+ border-radius: $CAP_SPACE_06;
2612
+ padding: $CAP_SPACE_08;
2613
+ box-sizing: border-box;
2614
+ }
2615
+
2616
+ .viber-carousel-message-pop {
2617
+ margin-top: 0;
2618
+ width: 85%;
2619
+ border-radius: 0 $CAP_SPACE_06 $CAP_SPACE_06 $CAP_SPACE_06;
2620
+ display: flex;
2621
+ flex-direction: column;
2622
+ overflow: hidden;
2623
+ }
2624
+
2625
+ .viber-carousel-cards-pop {
2626
+ margin-top: 0;
2627
+ width: 100%;
2628
+ background: transparent;
2629
+ border: none;
2630
+ border-radius: 0;
2631
+ padding: 0;
2632
+ overflow: hidden;
2633
+ }
2634
+
2635
+ .viber-carousel-message-box {
2636
+ width: 100%;
2637
+ min-height: 2.25rem;
2638
+ height: auto;
2639
+ border-radius: $CAP_SPACE_04;
2640
+ background: transparent;
2641
+ padding: 0 $CAP_SPACE_08;
2642
+ display: flex;
2643
+ align-items: center;
2644
+ }
2645
+
2646
+ .viber-carousel-message-box-text {
2647
+ color: $CAP_G01;
2648
+ margin: 0.107rem $CAP_SPACE_06 $CAP_SPACE_01 0.5rem;
2649
+ white-space: normal;
2650
+ word-break: break-word;
2651
+ overflow-wrap: break-word;
2652
+ overflow: hidden;
2653
+ max-width: 100%;
2654
+ box-sizing: border-box;
2655
+ }
2656
+
2657
+ .viber-carousel-message-box-placeholder {
2658
+ width: 100%;
2659
+ height: 0.875rem;
2660
+ border-radius: $CAP_SPACE_04;
2661
+ background: $CAP_G07;
2662
+ }
2663
+
2664
+ .viber-carousel-message-timestamp,
2665
+ .viber-carousel-cards-timestamp {
2666
+ display: block;
2667
+ text-align: right;
2668
+ margin-top: $CAP_SPACE_06;
2669
+ color: $CAP_G04;
2670
+ }
2671
+
2539
2672
  .empty-placeholder {
2540
2673
  height: $CAP_SPACE_08;
2541
2674
  }
@@ -2549,6 +2682,11 @@
2549
2682
  .viber-message-container-ios {
2550
2683
  margin-left: 1rem;
2551
2684
  margin-right: 1rem;
2685
+
2686
+ &.viber-message-container-carousel {
2687
+ margin-left: $CAP_SPACE_04;
2688
+ margin-right: $CAP_SPACE_04;
2689
+ }
2552
2690
  }
2553
2691
  }
2554
2692
  }
@@ -0,0 +1,131 @@
1
+ // Shared Viber carousel card row (Test & Preview device preview)
2
+ .viber-carousel-preview-scroll {
3
+ display: flex;
4
+ flex-direction: row;
5
+ flex-wrap: nowrap;
6
+ align-items: stretch;
7
+ width: 100%;
8
+ max-width: 100%;
9
+ min-width: 0;
10
+ overflow-x: auto;
11
+ overflow-y: hidden;
12
+ padding-right: $CAP_SPACE_12;
13
+ scrollbar-width: none;
14
+ -webkit-overflow-scrolling: touch;
15
+
16
+ &::-webkit-scrollbar {
17
+ display: none;
18
+ }
19
+
20
+ .viber-carousel-preview-card {
21
+ flex: 0 0 68%;
22
+ min-width: 80%;
23
+ margin-right: $CAP_SPACE_08;
24
+ background: $CAP_G09;
25
+ border: 1px solid $CAP_G07;
26
+ border-radius: $CAP_SPACE_12;
27
+ overflow: hidden;
28
+ display: flex;
29
+ flex-direction: column;
30
+ align-self: stretch;
31
+
32
+ &:last-child {
33
+ margin-right: 0;
34
+ }
35
+
36
+ .viber-carousel-preview-card-body {
37
+ flex: 1;
38
+ padding: $CAP_SPACE_08;
39
+ display: flex;
40
+ flex-direction: column;
41
+ min-height: 0;
42
+ }
43
+
44
+ .viber-carousel-preview-text-wrap {
45
+ flex-shrink: 0;
46
+ width: 100%;
47
+
48
+ &--1-line {
49
+ min-height: 1.3em;
50
+ }
51
+
52
+ &--2-line {
53
+ min-height: 2.6em;
54
+ }
55
+ }
56
+
57
+ .viber-carousel-preview-text-inner {
58
+ width: 100%;
59
+ }
60
+
61
+ .viber-carousel-preview-buttons {
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: $CAP_SPACE_06;
65
+ width: 100%;
66
+ flex-shrink: 0;
67
+ }
68
+
69
+ .viber-carousel-preview-image {
70
+ width: 100%;
71
+ height: 10rem;
72
+ object-fit: cover;
73
+ border-radius: 0;
74
+ }
75
+
76
+ .viber-carousel-preview-image-placeholder {
77
+ width: 100%;
78
+ height: 10rem;
79
+ border-radius: 0;
80
+ background: $CAP_G07;
81
+ }
82
+
83
+ .viber-carousel-preview-text {
84
+ color: $CAP_G01;
85
+ line-height: 1.3;
86
+ white-space: normal;
87
+ word-break: break-word;
88
+ display: block;
89
+ width: 100%;
90
+ margin: 0;
91
+ }
92
+
93
+ .viber-carousel-preview-text-placeholder {
94
+ width: 100%;
95
+ height: 0.875rem;
96
+ border-radius: $CAP_SPACE_04;
97
+ background: $CAP_G07;
98
+ min-height: 0.875rem;
99
+ display: block;
100
+ }
101
+
102
+ .viber-carousel-preview-button {
103
+ color: $CAP_WHITE;
104
+ background: $CAP_PURPLE01;
105
+ border-radius: $CAP_SPACE_12;
106
+ text-align: center;
107
+ width: 100%;
108
+ display: flex;
109
+ align-items: center;
110
+ justify-content: center;
111
+ min-height: 1.5rem;
112
+ padding: $CAP_SPACE_06 $CAP_SPACE_08;
113
+ white-space: normal;
114
+ box-sizing: border-box;
115
+ }
116
+
117
+ .viber-carousel-preview-button-placeholder {
118
+ visibility: hidden;
119
+ pointer-events: none;
120
+ padding: 0;
121
+ background: transparent;
122
+ border: none;
123
+ }
124
+
125
+ .viber-carousel-preview-button-secondary {
126
+ color: $CAP_PURPLE01;
127
+ background: transparent;
128
+ border: none;
129
+ }
130
+ }
131
+ }
@@ -246,6 +246,8 @@ const CommonTestAndPreview = (props) => {
246
246
  const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
247
247
  // Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
248
248
  const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
249
+ // Viber: tag values applied to preview only after explicit Update preview / Discard (not while typing)
250
+ const [viberPreviewTagValues, setViberPreviewTagValues] = useState(null);
249
251
  const [previewDataHtml, setPreviewDataHtml] = useState(() => {
250
252
  // Initialize preview data based on channel
251
253
  if (channel === CHANNELS.EMAIL && formData) {
@@ -687,6 +689,159 @@ const CommonTestAndPreview = (props) => {
687
689
  return resolvedText;
688
690
  };
689
691
 
692
+ const getViberMergedFormData = useCallback((formDataOverride) => {
693
+ const formDataObj = formDataOverride && typeof formDataOverride === 'object'
694
+ ? formDataOverride
695
+ : (formData && typeof formData === 'object' ? formData : {});
696
+ let contentObj = {};
697
+ if (content && typeof content === 'object') {
698
+ contentObj = content;
699
+ } else if (typeof content === 'string' && content.trim()) {
700
+ try {
701
+ contentObj = JSON.parse(content);
702
+ } catch (e) {
703
+ contentObj = {};
704
+ }
705
+ }
706
+ const previewCards = formDataObj.viberPreviewContent?.cards
707
+ ?? formDataObj.cards
708
+ ?? contentObj.viberPreviewContent?.cards
709
+ ?? contentObj.cards;
710
+ const isCarousel = Boolean(
711
+ formDataObj.type === MEDIA_TYPE_CAROUSEL
712
+ || contentObj.type === MEDIA_TYPE_CAROUSEL
713
+ || (Array.isArray(previewCards) && previewCards.length > 0),
714
+ );
715
+ const mergedPreview = {
716
+ ...(contentObj.viberPreviewContent || {}),
717
+ ...(formDataObj.viberPreviewContent || {}),
718
+ ...(Array.isArray(previewCards) && previewCards.length ? { cards: previewCards } : {}),
719
+ ...(isCarousel ? {
720
+ type: MEDIA_TYPE_CAROUSEL,
721
+ showCarouselEditorPreview: true,
722
+ } : {}),
723
+ };
724
+ return {
725
+ ...contentObj,
726
+ ...formDataObj,
727
+ ...(Array.isArray(previewCards) && previewCards.length ? { cards: previewCards } : {}),
728
+ ...(isCarousel ? { type: MEDIA_TYPE_CAROUSEL } : {}),
729
+ ...(Object.keys(mergedPreview).length ? { viberPreviewContent: mergedPreview } : {}),
730
+ };
731
+ }, [formData, content]);
732
+
733
+ const getViberCarouselCardsFromFormData = (formDataObj) => {
734
+ const previewCards = formDataObj?.viberPreviewContent?.cards;
735
+ if (Array.isArray(previewCards) && previewCards.length) {
736
+ return previewCards;
737
+ }
738
+ const rootCards = formDataObj?.cards;
739
+ if (Array.isArray(rootCards) && rootCards.length) {
740
+ return rootCards;
741
+ }
742
+ return [];
743
+ };
744
+
745
+ const getViberTagExtractionContent = (formDataObj, contentStr = '') => {
746
+ const messageText = formDataObj?.messageContent
747
+ || formDataObj?.viberPreviewContent?.messageContent
748
+ || contentStr
749
+ || '';
750
+ const cardFieldTexts = getViberCarouselCardsFromFormData(formDataObj).flatMap((card) => {
751
+ const fields = [card?.text || ''];
752
+ (card?.buttons || []).forEach((button) => {
753
+ fields.push(button?.title || '', button?.action || '');
754
+ });
755
+ return fields;
756
+ });
757
+ return [messageText, ...cardFieldTexts]
758
+ .filter((value) => typeof value === 'string' && value.trim())
759
+ .join(' ');
760
+ };
761
+
762
+ const resolveViberCarouselCards = (cards, tagValues) => {
763
+ if (!Array.isArray(cards) || !tagValues || !Object.keys(tagValues).length) {
764
+ return cards;
765
+ }
766
+ return cards.map((card) => ({
767
+ ...card,
768
+ text: resolveTagsInText(card?.text || '', tagValues),
769
+ buttons: (card?.buttons || []).map((button) => ({
770
+ ...button,
771
+ title: resolveTagsInText(button?.title || '', tagValues),
772
+ action: resolveTagsInText(button?.action || '', tagValues),
773
+ })),
774
+ }));
775
+ };
776
+
777
+ const applyViberCustomValuesToContent = (contentObj, tagValues) => {
778
+ if (!contentObj || typeof contentObj !== 'object' || !tagValues || !Object.keys(tagValues).length) {
779
+ return contentObj;
780
+ }
781
+ const result = { ...contentObj };
782
+ if (typeof result.messageContent === 'string') {
783
+ result.messageContent = resolveTagsInText(result.messageContent, tagValues);
784
+ }
785
+ if (Array.isArray(result.cards)) {
786
+ result.cards = resolveViberCarouselCards(result.cards, tagValues);
787
+ }
788
+ if (result.viberPreviewContent && typeof result.viberPreviewContent === 'object') {
789
+ const preview = result.viberPreviewContent;
790
+ result.viberPreviewContent = {
791
+ ...preview,
792
+ messageContent: resolveTagsInText(preview.messageContent || '', tagValues),
793
+ cards: resolveViberCarouselCards(preview.cards, tagValues),
794
+ };
795
+ }
796
+ return result;
797
+ };
798
+
799
+ const mergeViberPreviewWithResolved = (base, resolved) => {
800
+ if (!resolved || typeof resolved !== 'object') {
801
+ return base && typeof base === 'object' ? base : {};
802
+ }
803
+ const baseObj = base && typeof base === 'object' ? base : {};
804
+ const basePreview = baseObj.viberPreviewContent || {};
805
+ const resolvedPreview = resolved.viberPreviewContent || {};
806
+ const baseCards = basePreview.cards || baseObj.cards || [];
807
+ const resolvedCards = resolvedPreview.cards || resolved.cards || [];
808
+ const mergedCards = baseCards.length
809
+ ? baseCards.map((card, index) => {
810
+ const resolvedCard = resolvedCards[index];
811
+ if (!resolvedCard) {
812
+ return card;
813
+ }
814
+ return {
815
+ ...card,
816
+ ...(resolvedCard.text != null ? { text: resolvedCard.text } : {}),
817
+ ...(resolvedCard.mediaUrl != null ? { mediaUrl: resolvedCard.mediaUrl } : {}),
818
+ buttons: (card.buttons || []).map((button, buttonIndex) => ({
819
+ ...button,
820
+ ...(resolvedCard.buttons?.[buttonIndex] || {}),
821
+ })),
822
+ };
823
+ })
824
+ : resolvedCards;
825
+ const resolvedMessage = resolved.messageContent ?? resolvedPreview.messageContent;
826
+
827
+ return {
828
+ ...baseObj,
829
+ ...resolved,
830
+ viberPreviewContent: {
831
+ ...basePreview,
832
+ ...resolvedPreview,
833
+ type: resolvedPreview.type || basePreview.type || baseObj.type || (mergedCards.length ? MEDIA_TYPE_CAROUSEL : ''),
834
+ cards: mergedCards.length ? mergedCards : (resolvedPreview.cards || basePreview.cards),
835
+ showCarouselEditorPreview: Boolean(
836
+ basePreview.showCarouselEditorPreview
837
+ || resolvedPreview.showCarouselEditorPreview
838
+ || mergedCards.length > 0,
839
+ ),
840
+ ...(resolvedMessage != null ? { messageContent: resolvedMessage } : {}),
841
+ },
842
+ };
843
+ };
844
+
690
845
  /**
691
846
  * Common handler for saving test customers (both new and existing)
692
847
  */
@@ -965,11 +1120,13 @@ const CommonTestAndPreview = (props) => {
965
1120
  templateContent: formDataObj?.bodyText || contentStr,
966
1121
  };
967
1122
 
968
- case CHANNELS.VIBER:
1123
+ case CHANNELS.VIBER: {
1124
+ const viberFormData = getViberMergedFormData(formDataObj);
969
1125
  return {
970
- templateSubject: formDataObj?.messageTitle || '',
971
- templateContent: contentStr,
1126
+ templateSubject: viberFormData?.messageTitle || '',
1127
+ templateContent: getViberTagExtractionContent(viberFormData, contentStr),
972
1128
  };
1129
+ }
973
1130
 
974
1131
  case CHANNELS.WEBPUSH:
975
1132
  return {
@@ -2023,11 +2180,16 @@ const CommonTestAndPreview = (props) => {
2023
2180
  // 1. formDataObj from getTemplateContent (contains both viberPreviewContent and payload fields)
2024
2181
  // 2. formDataObj[0] (array format - legacy)
2025
2182
  // 3. contentStr (direct string - legacy)
2183
+ // Merge content prop so listing / preview-only flows include carousel card tags
2184
+ const viberFormData = getViberMergedFormData(formDataObj);
2185
+ formDataObj = viberFormData;
2026
2186
 
2027
2187
  let messageText = '';
2028
2188
  let imageData = null;
2029
2189
  let videoData = null;
2030
2190
  let buttonData = null;
2191
+ let cardsData = [];
2192
+ let messageType = '';
2031
2193
  let accountId = null;
2032
2194
  let accountDetails = null;
2033
2195
  let scenarioKey = '';
@@ -2042,6 +2204,8 @@ const CommonTestAndPreview = (props) => {
2042
2204
  imageData = formDataObj.image || null;
2043
2205
  videoData = formDataObj.video || null;
2044
2206
  buttonData = formDataObj.button || null;
2207
+ cardsData = formDataObj.cards || [];
2208
+ messageType = formDataObj.type || '';
2045
2209
  accountId = formDataObj.accountId || null;
2046
2210
  accountDetails = formDataObj.accountDetails || null;
2047
2211
  scenarioKey = formDataObj.scenarioKey || VIBER_API_SCENARIO_KEY;
@@ -2068,6 +2232,8 @@ const CommonTestAndPreview = (props) => {
2068
2232
  url: formDataObj?.buttonURL || '',
2069
2233
  };
2070
2234
  }
2235
+ cardsData = viberPreview.cards || formDataObj?.cards || [];
2236
+ messageType = viberPreview.type || formDataObj?.type || '';
2071
2237
  // Extract account info from parent formDataObj if available
2072
2238
  accountId = formDataObj.accountId || null;
2073
2239
  accountDetails = formDataObj.accountDetails || null;
@@ -2110,6 +2276,10 @@ const CommonTestAndPreview = (props) => {
2110
2276
  text: messageText,
2111
2277
  };
2112
2278
 
2279
+ if (messageType === CHANNELS.VIBER) {
2280
+ messageType = '';
2281
+ }
2282
+
2113
2283
  // Add image if present
2114
2284
  if (imageData && imageData.url) {
2115
2285
  viberContent.image = {
@@ -2138,9 +2308,34 @@ const CommonTestAndPreview = (props) => {
2138
2308
  }
2139
2309
  }
2140
2310
 
2141
- // Resolve tags in text if custom values are provided
2142
- if (customValuesObj && Object.keys(customValuesObj).length > 0 && viberContent.text) {
2143
- viberContent.text = resolveTagsInText(viberContent.text, customValuesObj);
2311
+ if (messageType === MEDIA_TYPE_CAROUSEL || (Array.isArray(cardsData) && cardsData.length)) {
2312
+ viberContent.type = MEDIA_TYPE_CAROUSEL;
2313
+ viberContent.cards = (cardsData || []).map((card) => ({
2314
+ text: card?.text || '',
2315
+ mediaUrl: card?.mediaUrl || '',
2316
+ buttons: (card?.buttons || []).map((button) => ({
2317
+ title: button?.title || '',
2318
+ action: button?.action || '',
2319
+ })),
2320
+ }));
2321
+ delete viberContent.button;
2322
+ }
2323
+
2324
+ // Resolve tags in text/cards if custom values are provided
2325
+ if (customValuesObj && Object.keys(customValuesObj).length > 0) {
2326
+ if (viberContent.text) {
2327
+ viberContent.text = resolveTagsInText(viberContent.text, customValuesObj);
2328
+ }
2329
+ if (Array.isArray(viberContent.cards)) {
2330
+ viberContent.cards = resolveViberCarouselCards(viberContent.cards, customValuesObj);
2331
+ }
2332
+ if (viberContent.button) {
2333
+ viberContent.button = {
2334
+ ...viberContent.button,
2335
+ text: resolveTagsInText(viberContent.button.text || '', customValuesObj),
2336
+ url: resolveTagsInText(viberContent.button.url || '', customValuesObj),
2337
+ };
2338
+ }
2144
2339
  }
2145
2340
 
2146
2341
  // Build messageBody JSON string
@@ -2532,33 +2727,39 @@ const CommonTestAndPreview = (props) => {
2532
2727
  // Only use previewDataHtml if preview call was made
2533
2728
  if (hasPreviewCallBeenMade && previewDataHtml?.resolvedBody) {
2534
2729
  resolvedContent = previewDataHtml.resolvedBody;
2730
+ if (typeof resolvedContent === 'string') {
2731
+ try {
2732
+ resolvedContent = JSON.parse(resolvedContent);
2733
+ } catch (e) {
2734
+ resolvedContent = null;
2735
+ }
2736
+ }
2535
2737
  }
2536
2738
 
2537
- // Parse content if it's a string
2538
- let parsedViberContent = content;
2539
- if (typeof content === 'string') {
2540
- try {
2541
- parsedViberContent = JSON.parse(content);
2542
- } catch (e) {
2543
- parsedViberContent = {};
2544
- }
2739
+ // Parse content if it's a string, then merge with formData for carousel + tags in all entry flows
2740
+ let parsedViberContent = getViberMergedFormData();
2741
+ if (!parsedViberContent || typeof parsedViberContent !== 'object') {
2742
+ parsedViberContent = {};
2545
2743
  }
2546
- // Use resolvedContent if available (from preview call), otherwise use raw content
2744
+ // Merge template preview state with API resolved body so carousel type/cards survive slim responses
2547
2745
  if (resolvedContent && typeof resolvedContent === 'object') {
2548
- contentObj = { ...resolvedContent };
2549
- // Merge in accountName and brandName from raw content if not in resolvedContent
2550
- if (parsedViberContent?.accountName && !contentObj.accountName) {
2551
- contentObj.accountName = parsedViberContent.accountName;
2746
+ const base = parsedViberContent && typeof parsedViberContent === 'object' ? parsedViberContent : {};
2747
+ contentObj = mergeViberPreviewWithResolved(base, resolvedContent);
2748
+ if (base.accountName && !contentObj.accountName) {
2749
+ contentObj.accountName = base.accountName;
2552
2750
  }
2553
- if (parsedViberContent?.brandName && !contentObj.brandName) {
2554
- contentObj.brandName = parsedViberContent.brandName;
2751
+ if (base.brandName && !contentObj.brandName) {
2752
+ contentObj.brandName = base.brandName;
2555
2753
  }
2556
2754
  } else {
2557
- // Use raw content if no preview call was made or resolvedContent is not available
2558
- contentObj = {...parsedViberContent, messageContent: resolvedContent};
2559
- if (resolvedContent) {
2560
- contentObj.viberPreviewContent = {...parsedViberContent?.viberPreviewContent, messageContent: resolvedContent};
2561
- }
2755
+ contentObj = parsedViberContent && typeof parsedViberContent === 'object' ? parsedViberContent : {};
2756
+ }
2757
+ if (
2758
+ hasPreviewCallBeenMade
2759
+ && viberPreviewTagValues
2760
+ && Object.keys(viberPreviewTagValues).length > 0
2761
+ ) {
2762
+ contentObj = applyViberCustomValuesToContent(contentObj, viberPreviewTagValues);
2562
2763
  }
2563
2764
  } else if (channel === CHANNELS.ZALO) {
2564
2765
  // For Zalo, content is an object with templatePreviewUrl, templateStatus, etc.
@@ -2942,6 +3143,11 @@ const CommonTestAndPreview = (props) => {
2942
3143
 
2943
3144
  setCustomValues(updatedValues);
2944
3145
 
3146
+ // Viber: do not auto-resolve tags in preview; user must click Update preview
3147
+ if (channel === CHANNELS.VIBER) {
3148
+ return;
3149
+ }
3150
+
2945
3151
  // Update preview with prefilled values (this is a valid preview call trigger)
2946
3152
  // For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped.
2947
3153
  // SMS fallback preview is kept in sync by syncSmsFallbackPreview via smsFallbackPreviewText.
@@ -3024,6 +3230,7 @@ const CommonTestAndPreview = (props) => {
3024
3230
  setPreviewDevice(DESKTOP);
3025
3231
  setPreviewDataHtml('');
3026
3232
  setHasPreviewCallBeenMade(false); // Reset preview call flag
3233
+ setViberPreviewTagValues(null);
3027
3234
  setSelectedTestEntities([]);
3028
3235
  setBeeContent('');
3029
3236
  previousBeeContentRef.current = '';
@@ -3061,6 +3268,7 @@ const CommonTestAndPreview = (props) => {
3061
3268
  const handleClearSelection = () => {
3062
3269
  setSelectedCustomer(null);
3063
3270
  setHasPreviewCallBeenMade(false); // Reset flag when customer is cleared
3271
+ setViberPreviewTagValues(null);
3064
3272
 
3065
3273
  // Clear all preview errors when customer is cleared
3066
3274
  actions.clearPreviewErrors();
@@ -3119,6 +3327,9 @@ const CommonTestAndPreview = (props) => {
3119
3327
  emptyValues,
3120
3328
  selectedCustomer
3121
3329
  );
3330
+ if (channel === CHANNELS.VIBER) {
3331
+ setViberPreviewTagValues({ ...emptyValues });
3332
+ }
3122
3333
  actions.updatePreviewRequested(payload);
3123
3334
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
3124
3335
  };
@@ -3144,6 +3355,9 @@ const CommonTestAndPreview = (props) => {
3144
3355
  customValues,
3145
3356
  selectedCustomer
3146
3357
  );
3358
+ if (channel === CHANNELS.VIBER) {
3359
+ setViberPreviewTagValues({ ...customValues });
3360
+ }
3147
3361
  await actions.updatePreviewRequested(payload);
3148
3362
 
3149
3363
  await syncSmsFallbackPreview(customValues, selectedCustomer);