@capillarytech/creatives-library 9.0.48 → 9.0.49-alpha.0

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.
@@ -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 {
@@ -1350,10 +1507,7 @@ const CommonTestAndPreview = (props) => {
1350
1507
  const language = formDataObj?.language || formDataObj?.templateLanguage || 'en';
1351
1508
  const mediaType = formDataObj?.mediaType || formDataObj?.templateMediaType || MEDIA_TYPE_TEXT;
1352
1509
  const buttonType = formDataObj?.buttonType || BUTTON_TYPE_NONE;
1353
- const existingVarMapped = formDataObj?.varMapped || {};
1354
- const varMapped = Object.keys(existingVarMapped).length > 0
1355
- ? existingVarMapped
1356
- : (customValuesObj || {});
1510
+ const varMapped = formDataObj?.varMapped || {};
1357
1511
  const isPreviewUrl = formDataObj?.isPreviewUrl || false;
1358
1512
 
1359
1513
  // Extract buttons from ctaData or quickReplyData
@@ -2027,11 +2181,16 @@ const CommonTestAndPreview = (props) => {
2027
2181
  // 1. formDataObj from getTemplateContent (contains both viberPreviewContent and payload fields)
2028
2182
  // 2. formDataObj[0] (array format - legacy)
2029
2183
  // 3. contentStr (direct string - legacy)
2184
+ // Merge content prop so listing / preview-only flows include carousel card tags
2185
+ const viberFormData = getViberMergedFormData(formDataObj);
2186
+ formDataObj = viberFormData;
2030
2187
 
2031
2188
  let messageText = '';
2032
2189
  let imageData = null;
2033
2190
  let videoData = null;
2034
2191
  let buttonData = null;
2192
+ let cardsData = [];
2193
+ let messageType = '';
2035
2194
  let accountId = null;
2036
2195
  let accountDetails = null;
2037
2196
  let scenarioKey = '';
@@ -2046,6 +2205,8 @@ const CommonTestAndPreview = (props) => {
2046
2205
  imageData = formDataObj.image || null;
2047
2206
  videoData = formDataObj.video || null;
2048
2207
  buttonData = formDataObj.button || null;
2208
+ cardsData = formDataObj.cards || [];
2209
+ messageType = formDataObj.type || '';
2049
2210
  accountId = formDataObj.accountId || null;
2050
2211
  accountDetails = formDataObj.accountDetails || null;
2051
2212
  scenarioKey = formDataObj.scenarioKey || VIBER_API_SCENARIO_KEY;
@@ -2072,6 +2233,8 @@ const CommonTestAndPreview = (props) => {
2072
2233
  url: formDataObj?.buttonURL || '',
2073
2234
  };
2074
2235
  }
2236
+ cardsData = viberPreview.cards || formDataObj?.cards || [];
2237
+ messageType = viberPreview.type || formDataObj?.type || '';
2075
2238
  // Extract account info from parent formDataObj if available
2076
2239
  accountId = formDataObj.accountId || null;
2077
2240
  accountDetails = formDataObj.accountDetails || null;
@@ -2114,6 +2277,10 @@ const CommonTestAndPreview = (props) => {
2114
2277
  text: messageText,
2115
2278
  };
2116
2279
 
2280
+ if (messageType === CHANNELS.VIBER) {
2281
+ messageType = '';
2282
+ }
2283
+
2117
2284
  // Add image if present
2118
2285
  if (imageData && imageData.url) {
2119
2286
  viberContent.image = {
@@ -2142,9 +2309,34 @@ const CommonTestAndPreview = (props) => {
2142
2309
  }
2143
2310
  }
2144
2311
 
2145
- // Resolve tags in text if custom values are provided
2146
- if (customValuesObj && Object.keys(customValuesObj).length > 0 && viberContent.text) {
2147
- viberContent.text = resolveTagsInText(viberContent.text, customValuesObj);
2312
+ if (messageType === MEDIA_TYPE_CAROUSEL || (Array.isArray(cardsData) && cardsData.length)) {
2313
+ viberContent.type = MEDIA_TYPE_CAROUSEL;
2314
+ viberContent.cards = (cardsData || []).map((card) => ({
2315
+ text: card?.text || '',
2316
+ mediaUrl: card?.mediaUrl || '',
2317
+ buttons: (card?.buttons || []).map((button) => ({
2318
+ title: button?.title || '',
2319
+ action: button?.action || '',
2320
+ })),
2321
+ }));
2322
+ delete viberContent.button;
2323
+ }
2324
+
2325
+ // Resolve tags in text/cards if custom values are provided
2326
+ if (customValuesObj && Object.keys(customValuesObj).length > 0) {
2327
+ if (viberContent.text) {
2328
+ viberContent.text = resolveTagsInText(viberContent.text, customValuesObj);
2329
+ }
2330
+ if (Array.isArray(viberContent.cards)) {
2331
+ viberContent.cards = resolveViberCarouselCards(viberContent.cards, customValuesObj);
2332
+ }
2333
+ if (viberContent.button) {
2334
+ viberContent.button = {
2335
+ ...viberContent.button,
2336
+ text: resolveTagsInText(viberContent.button.text || '', customValuesObj),
2337
+ url: resolveTagsInText(viberContent.button.url || '', customValuesObj),
2338
+ };
2339
+ }
2148
2340
  }
2149
2341
 
2150
2342
  // Build messageBody JSON string
@@ -2536,33 +2728,39 @@ const CommonTestAndPreview = (props) => {
2536
2728
  // Only use previewDataHtml if preview call was made
2537
2729
  if (hasPreviewCallBeenMade && previewDataHtml?.resolvedBody) {
2538
2730
  resolvedContent = previewDataHtml.resolvedBody;
2731
+ if (typeof resolvedContent === 'string') {
2732
+ try {
2733
+ resolvedContent = JSON.parse(resolvedContent);
2734
+ } catch (e) {
2735
+ resolvedContent = null;
2736
+ }
2737
+ }
2539
2738
  }
2540
2739
 
2541
- // Parse content if it's a string
2542
- let parsedViberContent = content;
2543
- if (typeof content === 'string') {
2544
- try {
2545
- parsedViberContent = JSON.parse(content);
2546
- } catch (e) {
2547
- parsedViberContent = {};
2548
- }
2740
+ // Parse content if it's a string, then merge with formData for carousel + tags in all entry flows
2741
+ let parsedViberContent = getViberMergedFormData();
2742
+ if (!parsedViberContent || typeof parsedViberContent !== 'object') {
2743
+ parsedViberContent = {};
2549
2744
  }
2550
- // Use resolvedContent if available (from preview call), otherwise use raw content
2745
+ // Merge template preview state with API resolved body so carousel type/cards survive slim responses
2551
2746
  if (resolvedContent && typeof resolvedContent === 'object') {
2552
- contentObj = { ...resolvedContent };
2553
- // Merge in accountName and brandName from raw content if not in resolvedContent
2554
- if (parsedViberContent?.accountName && !contentObj.accountName) {
2555
- contentObj.accountName = parsedViberContent.accountName;
2747
+ const base = parsedViberContent && typeof parsedViberContent === 'object' ? parsedViberContent : {};
2748
+ contentObj = mergeViberPreviewWithResolved(base, resolvedContent);
2749
+ if (base.accountName && !contentObj.accountName) {
2750
+ contentObj.accountName = base.accountName;
2556
2751
  }
2557
- if (parsedViberContent?.brandName && !contentObj.brandName) {
2558
- contentObj.brandName = parsedViberContent.brandName;
2752
+ if (base.brandName && !contentObj.brandName) {
2753
+ contentObj.brandName = base.brandName;
2559
2754
  }
2560
2755
  } else {
2561
- // Use raw content if no preview call was made or resolvedContent is not available
2562
- contentObj = {...parsedViberContent, messageContent: resolvedContent};
2563
- if (resolvedContent) {
2564
- contentObj.viberPreviewContent = {...parsedViberContent?.viberPreviewContent, messageContent: resolvedContent};
2565
- }
2756
+ contentObj = parsedViberContent && typeof parsedViberContent === 'object' ? parsedViberContent : {};
2757
+ }
2758
+ if (
2759
+ hasPreviewCallBeenMade
2760
+ && viberPreviewTagValues
2761
+ && Object.keys(viberPreviewTagValues).length > 0
2762
+ ) {
2763
+ contentObj = applyViberCustomValuesToContent(contentObj, viberPreviewTagValues);
2566
2764
  }
2567
2765
  } else if (channel === CHANNELS.ZALO) {
2568
2766
  // For Zalo, content is an object with templatePreviewUrl, templateStatus, etc.
@@ -2946,6 +3144,11 @@ const CommonTestAndPreview = (props) => {
2946
3144
 
2947
3145
  setCustomValues(updatedValues);
2948
3146
 
3147
+ // Viber: do not auto-resolve tags in preview; user must click Update preview
3148
+ if (channel === CHANNELS.VIBER) {
3149
+ return;
3150
+ }
3151
+
2949
3152
  // Update preview with prefilled values (this is a valid preview call trigger)
2950
3153
  // For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped.
2951
3154
  // SMS fallback preview is kept in sync by syncSmsFallbackPreview via smsFallbackPreviewText.
@@ -3028,6 +3231,7 @@ const CommonTestAndPreview = (props) => {
3028
3231
  setPreviewDevice(DESKTOP);
3029
3232
  setPreviewDataHtml('');
3030
3233
  setHasPreviewCallBeenMade(false); // Reset preview call flag
3234
+ setViberPreviewTagValues(null);
3031
3235
  setSelectedTestEntities([]);
3032
3236
  setBeeContent('');
3033
3237
  previousBeeContentRef.current = '';
@@ -3065,6 +3269,7 @@ const CommonTestAndPreview = (props) => {
3065
3269
  const handleClearSelection = () => {
3066
3270
  setSelectedCustomer(null);
3067
3271
  setHasPreviewCallBeenMade(false); // Reset flag when customer is cleared
3272
+ setViberPreviewTagValues(null);
3068
3273
 
3069
3274
  // Clear all preview errors when customer is cleared
3070
3275
  actions.clearPreviewErrors();
@@ -3123,6 +3328,9 @@ const CommonTestAndPreview = (props) => {
3123
3328
  emptyValues,
3124
3329
  selectedCustomer
3125
3330
  );
3331
+ if (channel === CHANNELS.VIBER) {
3332
+ setViberPreviewTagValues({ ...emptyValues });
3333
+ }
3126
3334
  actions.updatePreviewRequested(payload);
3127
3335
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
3128
3336
  };
@@ -3148,6 +3356,9 @@ const CommonTestAndPreview = (props) => {
3148
3356
  customValues,
3149
3357
  selectedCustomer
3150
3358
  );
3359
+ if (channel === CHANNELS.VIBER) {
3360
+ setViberPreviewTagValues({ ...customValues });
3361
+ }
3151
3362
  await actions.updatePreviewRequested(payload);
3152
3363
 
3153
3364
  await syncSmsFallbackPreview(customValues, selectedCustomer);