@capillarytech/creatives-library 8.0.359 → 8.0.360-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.
@@ -189,6 +189,8 @@ const CommonTestAndPreview = (props) => {
189
189
  const [previewDevice, setPreviewDevice] = useState(initialDevice);
190
190
  // Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
191
191
  const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
192
+ // Viber: tag values applied to preview only after explicit Update preview / Discard (not while typing)
193
+ const [viberPreviewTagValues, setViberPreviewTagValues] = useState(null);
192
194
  const [previewDataHtml, setPreviewDataHtml] = useState(() => {
193
195
  // Initialize preview data based on channel
194
196
  if (channel === CHANNELS.EMAIL && formData) {
@@ -456,6 +458,159 @@ const CommonTestAndPreview = (props) => {
456
458
  return resolvedText;
457
459
  };
458
460
 
461
+ const getViberMergedFormData = useCallback((formDataOverride) => {
462
+ const formDataObj = formDataOverride && typeof formDataOverride === 'object'
463
+ ? formDataOverride
464
+ : (formData && typeof formData === 'object' ? formData : {});
465
+ let contentObj = {};
466
+ if (content && typeof content === 'object') {
467
+ contentObj = content;
468
+ } else if (typeof content === 'string' && content.trim()) {
469
+ try {
470
+ contentObj = JSON.parse(content);
471
+ } catch (e) {
472
+ contentObj = {};
473
+ }
474
+ }
475
+ const previewCards = formDataObj.viberPreviewContent?.cards
476
+ ?? formDataObj.cards
477
+ ?? contentObj.viberPreviewContent?.cards
478
+ ?? contentObj.cards;
479
+ const isCarousel = Boolean(
480
+ formDataObj.type === MEDIA_TYPE_CAROUSEL
481
+ || contentObj.type === MEDIA_TYPE_CAROUSEL
482
+ || (Array.isArray(previewCards) && previewCards.length > 0),
483
+ );
484
+ const mergedPreview = {
485
+ ...(contentObj.viberPreviewContent || {}),
486
+ ...(formDataObj.viberPreviewContent || {}),
487
+ ...(Array.isArray(previewCards) && previewCards.length ? { cards: previewCards } : {}),
488
+ ...(isCarousel ? {
489
+ type: MEDIA_TYPE_CAROUSEL,
490
+ showCarouselEditorPreview: true,
491
+ } : {}),
492
+ };
493
+ return {
494
+ ...contentObj,
495
+ ...formDataObj,
496
+ ...(Array.isArray(previewCards) && previewCards.length ? { cards: previewCards } : {}),
497
+ ...(isCarousel ? { type: MEDIA_TYPE_CAROUSEL } : {}),
498
+ ...(Object.keys(mergedPreview).length ? { viberPreviewContent: mergedPreview } : {}),
499
+ };
500
+ }, [formData, content]);
501
+
502
+ const getViberCarouselCardsFromFormData = (formDataObj) => {
503
+ const previewCards = formDataObj?.viberPreviewContent?.cards;
504
+ if (Array.isArray(previewCards) && previewCards.length) {
505
+ return previewCards;
506
+ }
507
+ const rootCards = formDataObj?.cards;
508
+ if (Array.isArray(rootCards) && rootCards.length) {
509
+ return rootCards;
510
+ }
511
+ return [];
512
+ };
513
+
514
+ const getViberTagExtractionContent = (formDataObj, contentStr = '') => {
515
+ const messageText = formDataObj?.messageContent
516
+ || formDataObj?.viberPreviewContent?.messageContent
517
+ || contentStr
518
+ || '';
519
+ const cardFieldTexts = getViberCarouselCardsFromFormData(formDataObj).flatMap((card) => {
520
+ const fields = [card?.text || ''];
521
+ (card?.buttons || []).forEach((button) => {
522
+ fields.push(button?.title || '', button?.action || '');
523
+ });
524
+ return fields;
525
+ });
526
+ return [messageText, ...cardFieldTexts]
527
+ .filter((value) => typeof value === 'string' && value.trim())
528
+ .join(' ');
529
+ };
530
+
531
+ const resolveViberCarouselCards = (cards, tagValues) => {
532
+ if (!Array.isArray(cards) || !tagValues || !Object.keys(tagValues).length) {
533
+ return cards;
534
+ }
535
+ return cards.map((card) => ({
536
+ ...card,
537
+ text: resolveTagsInText(card?.text || '', tagValues),
538
+ buttons: (card?.buttons || []).map((button) => ({
539
+ ...button,
540
+ title: resolveTagsInText(button?.title || '', tagValues),
541
+ action: resolveTagsInText(button?.action || '', tagValues),
542
+ })),
543
+ }));
544
+ };
545
+
546
+ const applyViberCustomValuesToContent = (contentObj, tagValues) => {
547
+ if (!contentObj || typeof contentObj !== 'object' || !tagValues || !Object.keys(tagValues).length) {
548
+ return contentObj;
549
+ }
550
+ const result = { ...contentObj };
551
+ if (typeof result.messageContent === 'string') {
552
+ result.messageContent = resolveTagsInText(result.messageContent, tagValues);
553
+ }
554
+ if (Array.isArray(result.cards)) {
555
+ result.cards = resolveViberCarouselCards(result.cards, tagValues);
556
+ }
557
+ if (result.viberPreviewContent && typeof result.viberPreviewContent === 'object') {
558
+ const preview = result.viberPreviewContent;
559
+ result.viberPreviewContent = {
560
+ ...preview,
561
+ messageContent: resolveTagsInText(preview.messageContent || '', tagValues),
562
+ cards: resolveViberCarouselCards(preview.cards, tagValues),
563
+ };
564
+ }
565
+ return result;
566
+ };
567
+
568
+ const mergeViberPreviewWithResolved = (base, resolved) => {
569
+ if (!resolved || typeof resolved !== 'object') {
570
+ return base && typeof base === 'object' ? base : {};
571
+ }
572
+ const baseObj = base && typeof base === 'object' ? base : {};
573
+ const basePreview = baseObj.viberPreviewContent || {};
574
+ const resolvedPreview = resolved.viberPreviewContent || {};
575
+ const baseCards = basePreview.cards || baseObj.cards || [];
576
+ const resolvedCards = resolvedPreview.cards || resolved.cards || [];
577
+ const mergedCards = baseCards.length
578
+ ? baseCards.map((card, index) => {
579
+ const resolvedCard = resolvedCards[index];
580
+ if (!resolvedCard) {
581
+ return card;
582
+ }
583
+ return {
584
+ ...card,
585
+ ...(resolvedCard.text != null ? { text: resolvedCard.text } : {}),
586
+ ...(resolvedCard.mediaUrl != null ? { mediaUrl: resolvedCard.mediaUrl } : {}),
587
+ buttons: (card.buttons || []).map((button, buttonIndex) => ({
588
+ ...button,
589
+ ...(resolvedCard.buttons?.[buttonIndex] || {}),
590
+ })),
591
+ };
592
+ })
593
+ : resolvedCards;
594
+ const resolvedMessage = resolved.messageContent ?? resolvedPreview.messageContent;
595
+
596
+ return {
597
+ ...baseObj,
598
+ ...resolved,
599
+ viberPreviewContent: {
600
+ ...basePreview,
601
+ ...resolvedPreview,
602
+ type: resolvedPreview.type || basePreview.type || baseObj.type || (mergedCards.length ? MEDIA_TYPE_CAROUSEL : ''),
603
+ cards: mergedCards.length ? mergedCards : (resolvedPreview.cards || basePreview.cards),
604
+ showCarouselEditorPreview: Boolean(
605
+ basePreview.showCarouselEditorPreview
606
+ || resolvedPreview.showCarouselEditorPreview
607
+ || mergedCards.length > 0,
608
+ ),
609
+ ...(resolvedMessage != null ? { messageContent: resolvedMessage } : {}),
610
+ },
611
+ };
612
+ };
613
+
459
614
  /**
460
615
  * Common handler for saving test customers (both new and existing)
461
616
  */
@@ -663,11 +818,13 @@ const CommonTestAndPreview = (props) => {
663
818
  templateContent: formDataObj?.bodyText || contentStr,
664
819
  };
665
820
 
666
- case CHANNELS.VIBER:
821
+ case CHANNELS.VIBER: {
822
+ const viberFormData = getViberMergedFormData(formDataObj);
667
823
  return {
668
- templateSubject: formDataObj?.messageTitle || '',
669
- templateContent: contentStr,
824
+ templateSubject: viberFormData?.messageTitle || '',
825
+ templateContent: getViberTagExtractionContent(viberFormData, contentStr),
670
826
  };
827
+ }
671
828
 
672
829
  case CHANNELS.WEBPUSH:
673
830
  return {
@@ -1562,11 +1719,16 @@ const CommonTestAndPreview = (props) => {
1562
1719
  // 1. formDataObj from getTemplateContent (contains both viberPreviewContent and payload fields)
1563
1720
  // 2. formDataObj[0] (array format - legacy)
1564
1721
  // 3. contentStr (direct string - legacy)
1722
+ // Merge content prop so listing / preview-only flows include carousel card tags
1723
+ const viberFormData = getViberMergedFormData(formDataObj);
1724
+ formDataObj = viberFormData;
1565
1725
 
1566
1726
  let messageText = '';
1567
1727
  let imageData = null;
1568
1728
  let videoData = null;
1569
1729
  let buttonData = null;
1730
+ let cardsData = [];
1731
+ let messageType = '';
1570
1732
  let accountId = null;
1571
1733
  let accountDetails = null;
1572
1734
  let scenarioKey = '';
@@ -1581,6 +1743,8 @@ const CommonTestAndPreview = (props) => {
1581
1743
  imageData = formDataObj.image || null;
1582
1744
  videoData = formDataObj.video || null;
1583
1745
  buttonData = formDataObj.button || null;
1746
+ cardsData = formDataObj.cards || [];
1747
+ messageType = formDataObj.type || '';
1584
1748
  accountId = formDataObj.accountId || null;
1585
1749
  accountDetails = formDataObj.accountDetails || null;
1586
1750
  scenarioKey = formDataObj.scenarioKey || VIBER_API_SCENARIO_KEY;
@@ -1607,6 +1771,8 @@ const CommonTestAndPreview = (props) => {
1607
1771
  url: formDataObj?.buttonURL || '',
1608
1772
  };
1609
1773
  }
1774
+ cardsData = viberPreview.cards || formDataObj?.cards || [];
1775
+ messageType = viberPreview.type || formDataObj?.type || '';
1610
1776
  // Extract account info from parent formDataObj if available
1611
1777
  accountId = formDataObj.accountId || null;
1612
1778
  accountDetails = formDataObj.accountDetails || null;
@@ -1649,6 +1815,10 @@ const CommonTestAndPreview = (props) => {
1649
1815
  text: messageText,
1650
1816
  };
1651
1817
 
1818
+ if (messageType === CHANNELS.VIBER) {
1819
+ messageType = '';
1820
+ }
1821
+
1652
1822
  // Add image if present
1653
1823
  if (imageData && imageData.url) {
1654
1824
  viberContent.image = {
@@ -1677,9 +1847,34 @@ const CommonTestAndPreview = (props) => {
1677
1847
  }
1678
1848
  }
1679
1849
 
1680
- // Resolve tags in text if custom values are provided
1681
- if (customValuesObj && Object.keys(customValuesObj).length > 0 && viberContent.text) {
1682
- viberContent.text = resolveTagsInText(viberContent.text, customValuesObj);
1850
+ if (messageType === MEDIA_TYPE_CAROUSEL || (Array.isArray(cardsData) && cardsData.length)) {
1851
+ viberContent.type = MEDIA_TYPE_CAROUSEL;
1852
+ viberContent.cards = (cardsData || []).map((card) => ({
1853
+ text: card?.text || '',
1854
+ mediaUrl: card?.mediaUrl || '',
1855
+ buttons: (card?.buttons || []).map((button) => ({
1856
+ title: button?.title || '',
1857
+ action: button?.action || '',
1858
+ })),
1859
+ }));
1860
+ delete viberContent.button;
1861
+ }
1862
+
1863
+ // Resolve tags in text/cards if custom values are provided
1864
+ if (customValuesObj && Object.keys(customValuesObj).length > 0) {
1865
+ if (viberContent.text) {
1866
+ viberContent.text = resolveTagsInText(viberContent.text, customValuesObj);
1867
+ }
1868
+ if (Array.isArray(viberContent.cards)) {
1869
+ viberContent.cards = resolveViberCarouselCards(viberContent.cards, customValuesObj);
1870
+ }
1871
+ if (viberContent.button) {
1872
+ viberContent.button = {
1873
+ ...viberContent.button,
1874
+ text: resolveTagsInText(viberContent.button.text || '', customValuesObj),
1875
+ url: resolveTagsInText(viberContent.button.url || '', customValuesObj),
1876
+ };
1877
+ }
1683
1878
  }
1684
1879
 
1685
1880
  // Build messageBody JSON string
@@ -2059,33 +2254,39 @@ const CommonTestAndPreview = (props) => {
2059
2254
  // Only use previewDataHtml if preview call was made
2060
2255
  if (hasPreviewCallBeenMade && previewDataHtml?.resolvedBody) {
2061
2256
  resolvedContent = previewDataHtml.resolvedBody;
2257
+ if (typeof resolvedContent === 'string') {
2258
+ try {
2259
+ resolvedContent = JSON.parse(resolvedContent);
2260
+ } catch (e) {
2261
+ resolvedContent = null;
2262
+ }
2263
+ }
2062
2264
  }
2063
2265
 
2064
- // Parse content if it's a string
2065
- let parsedViberContent = content;
2066
- if (typeof content === 'string') {
2067
- try {
2068
- parsedViberContent = JSON.parse(content);
2069
- } catch (e) {
2070
- parsedViberContent = {};
2071
- }
2266
+ // Parse content if it's a string, then merge with formData for carousel + tags in all entry flows
2267
+ let parsedViberContent = getViberMergedFormData();
2268
+ if (!parsedViberContent || typeof parsedViberContent !== 'object') {
2269
+ parsedViberContent = {};
2072
2270
  }
2073
- // Use resolvedContent if available (from preview call), otherwise use raw content
2271
+ // Merge template preview state with API resolved body so carousel type/cards survive slim responses
2074
2272
  if (resolvedContent && typeof resolvedContent === 'object') {
2075
- contentObj = { ...resolvedContent };
2076
- // Merge in accountName and brandName from raw content if not in resolvedContent
2077
- if (parsedViberContent?.accountName && !contentObj.accountName) {
2078
- contentObj.accountName = parsedViberContent.accountName;
2273
+ const base = parsedViberContent && typeof parsedViberContent === 'object' ? parsedViberContent : {};
2274
+ contentObj = mergeViberPreviewWithResolved(base, resolvedContent);
2275
+ if (base.accountName && !contentObj.accountName) {
2276
+ contentObj.accountName = base.accountName;
2079
2277
  }
2080
- if (parsedViberContent?.brandName && !contentObj.brandName) {
2081
- contentObj.brandName = parsedViberContent.brandName;
2278
+ if (base.brandName && !contentObj.brandName) {
2279
+ contentObj.brandName = base.brandName;
2082
2280
  }
2083
2281
  } else {
2084
- // Use raw content if no preview call was made or resolvedContent is not available
2085
- contentObj = {...parsedViberContent, messageContent: resolvedContent};
2086
- if (resolvedContent) {
2087
- contentObj.viberPreviewContent = {...parsedViberContent?.viberPreviewContent, messageContent: resolvedContent};
2088
- }
2282
+ contentObj = parsedViberContent && typeof parsedViberContent === 'object' ? parsedViberContent : {};
2283
+ }
2284
+ if (
2285
+ hasPreviewCallBeenMade
2286
+ && viberPreviewTagValues
2287
+ && Object.keys(viberPreviewTagValues).length > 0
2288
+ ) {
2289
+ contentObj = applyViberCustomValuesToContent(contentObj, viberPreviewTagValues);
2089
2290
  }
2090
2291
  } else if (channel === CHANNELS.ZALO) {
2091
2292
  // For Zalo, content is an object with templatePreviewUrl, templateStatus, etc.
@@ -2341,6 +2542,8 @@ const CommonTestAndPreview = (props) => {
2341
2542
  setShowJSON(false);
2342
2543
  setTagsExtracted(false);
2343
2544
  setPreviewDevice(DESKTOP);
2545
+ setHasPreviewCallBeenMade(false);
2546
+ setViberPreviewTagValues(null);
2344
2547
  setSelectedTestEntities([]);
2345
2548
  actions.clearPrefilledValues();
2346
2549
  } else {
@@ -2470,6 +2673,11 @@ const CommonTestAndPreview = (props) => {
2470
2673
 
2471
2674
  setCustomValues(updatedValues);
2472
2675
 
2676
+ // Viber: do not auto-resolve tags in preview; user must click Update preview
2677
+ if (channel === CHANNELS.VIBER) {
2678
+ return;
2679
+ }
2680
+
2473
2681
  // Update preview with prefilled values (this is a valid preview call trigger)
2474
2682
  const payload = preparePreviewPayload(
2475
2683
  channel,
@@ -2547,6 +2755,7 @@ const CommonTestAndPreview = (props) => {
2547
2755
  setPreviewDevice(DESKTOP);
2548
2756
  setPreviewDataHtml('');
2549
2757
  setHasPreviewCallBeenMade(false); // Reset preview call flag
2758
+ setViberPreviewTagValues(null);
2550
2759
  setSelectedTestEntities([]);
2551
2760
  setBeeContent('');
2552
2761
  previousBeeContentRef.current = '';
@@ -2588,6 +2797,7 @@ const CommonTestAndPreview = (props) => {
2588
2797
  const handleClearSelection = () => {
2589
2798
  setSelectedCustomer(null);
2590
2799
  setHasPreviewCallBeenMade(false); // Reset flag when customer is cleared
2800
+ setViberPreviewTagValues(null);
2591
2801
 
2592
2802
  // Clear all preview errors when customer is cleared
2593
2803
  actions.clearPreviewErrors();
@@ -2647,6 +2857,9 @@ const CommonTestAndPreview = (props) => {
2647
2857
  emptyValues,
2648
2858
  selectedCustomer
2649
2859
  );
2860
+ if (channel === CHANNELS.VIBER) {
2861
+ setViberPreviewTagValues({ ...emptyValues });
2862
+ }
2650
2863
  actions.updatePreviewRequested(payload);
2651
2864
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
2652
2865
  };
@@ -2664,6 +2877,9 @@ const CommonTestAndPreview = (props) => {
2664
2877
  customValues,
2665
2878
  selectedCustomer
2666
2879
  );
2880
+ if (channel === CHANNELS.VIBER) {
2881
+ setViberPreviewTagValues({ ...customValues });
2882
+ }
2667
2883
  await actions.updatePreviewRequested(payload);
2668
2884
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
2669
2885
  } catch (error) {
@@ -2685,6 +2901,8 @@ const CommonTestAndPreview = (props) => {
2685
2901
  const activeTab = currentTabData?.activeTab;
2686
2902
  const templateContent = currentTabData?.[activeTab]?.['template-content'];
2687
2903
  contentToExtract = templateContent || contentToExtract;
2904
+ } else if (channel === CHANNELS.VIBER) {
2905
+ contentToExtract = getViberTagExtractionContent(getViberMergedFormData(), contentToExtract);
2688
2906
  }
2689
2907
 
2690
2908
  // Check for personalization tags (excluding unsubscribe)