@capillarytech/creatives-library 9.0.53-alpha.3 → 9.0.54-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.
Files changed (33) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/constants/unified.js +1 -0
  3. package/package.json +1 -1
  4. package/services/api.js +0 -9
  5. package/utils/common.js +4 -0
  6. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberCarouselPreviewCards.js +127 -0
  7. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +106 -15
  8. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +139 -1
  9. package/v2Components/CommonTestAndPreview/UnifiedPreview/_viberCarouselPreviewCards.scss +133 -0
  10. package/v2Components/CommonTestAndPreview/constants.js +2 -0
  11. package/v2Components/CommonTestAndPreview/index.js +243 -26
  12. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +364 -0
  13. package/v2Components/CommonTestAndPreview/tests/utils.test.js +49 -0
  14. package/v2Components/CommonTestAndPreview/utils.js +20 -0
  15. package/v2Containers/CommunicationFlow/CommunicationFlow.js +58 -93
  16. package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +4 -20
  17. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +49 -194
  18. package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +0 -16
  19. package/v2Containers/CommunicationFlow/constants.js +0 -47
  20. package/v2Containers/CommunicationFlow/index.js +20 -15
  21. package/v2Containers/CommunicationFlow/messages.js +0 -4
  22. package/v2Containers/CreativesContainer/index.js +2 -0
  23. package/v2Containers/Rcs/constants.js +1 -0
  24. package/v2Containers/Rcs/index.js +9 -1
  25. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +108 -0
  26. package/v2Containers/Rcs/tests/index.test.js +121 -0
  27. package/v2Containers/Templates/_templates.scss +179 -1
  28. package/v2Containers/Templates/index.js +120 -10
  29. package/v2Containers/Viber/constants.js +23 -0
  30. package/v2Containers/Viber/index.js +718 -43
  31. package/v2Containers/Viber/index.scss +175 -0
  32. package/v2Containers/Viber/messages.js +121 -0
  33. package/v2Containers/Viber/tests/index.test.js +80 -0
@@ -228,6 +228,8 @@ export const CHANNEL_NAME_MOBILE_PUSH = 'MOBILE_PUSH';
228
228
  export const DEFAULT_SENDER = 'test1';
229
229
  export const VIBER_API_SCENARIO_KEY = 'viber-api';
230
230
  export const VIBER_USER_ID_PATTERN = '{{viber_user_id}}';
231
+ /** Fallback title line-height (px) when computed style is unavailable. */
232
+ export const DEFAULT_CAROUSEL_TITLE_LINE_HEIGHT_PX = 18;
231
233
  export const UNSUBSCRIBE_TAG_NAME = 'unsubscribe';
232
234
  export const IN_APP_CHANNEL_NAME = 'in-app';
233
235
  export const MOBILE_PUSH_CHANNEL_NAME = 'mobile push';
@@ -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 {
@@ -1118,6 +1275,7 @@ const CommonTestAndPreview = (props) => {
1118
1275
  ...(suggestionsFormattedForTestMeta.length > 0 && {
1119
1276
  suggestions: suggestionsFormattedForTestMeta,
1120
1277
  }),
1278
+ ...(singleRcsCardPayload?.cardVarMapped && { cardVarMapped: singleRcsCardPayload.cardVarMapped }),
1121
1279
  };
1122
1280
  });
1123
1281
  // Use the component-level smsFallbackContent prop (has rcsSmsFallbackVarMapped) so DLT
@@ -1184,9 +1342,11 @@ const CommonTestAndPreview = (props) => {
1184
1342
  ...(cardContentForTestMetaApi.length > 0 && { cardContent: cardContentForTestMetaApi }),
1185
1343
  };
1186
1344
 
1345
+ const vfTemplateId = rcsContentFromForm?.vfTemplateId;
1187
1346
  const rcsMessageContent = {
1188
1347
  channel: CHANNELS.RCS,
1189
1348
  ...(accountIdForMeta && { accountId: accountIdForMeta }),
1349
+ ...(vfTemplateId && { templateId: vfTemplateId }),
1190
1350
  rcsRichCardContent,
1191
1351
  ...(smsFallBackContent && { smsFallBackContent }),
1192
1352
  };
@@ -2024,11 +2184,16 @@ const CommonTestAndPreview = (props) => {
2024
2184
  // 1. formDataObj from getTemplateContent (contains both viberPreviewContent and payload fields)
2025
2185
  // 2. formDataObj[0] (array format - legacy)
2026
2186
  // 3. contentStr (direct string - legacy)
2187
+ // Merge content prop so listing / preview-only flows include carousel card tags
2188
+ const viberFormData = getViberMergedFormData(formDataObj);
2189
+ formDataObj = viberFormData;
2027
2190
 
2028
2191
  let messageText = '';
2029
2192
  let imageData = null;
2030
2193
  let videoData = null;
2031
2194
  let buttonData = null;
2195
+ let cardsData = [];
2196
+ let messageType = '';
2032
2197
  let accountId = null;
2033
2198
  let accountDetails = null;
2034
2199
  let scenarioKey = '';
@@ -2043,6 +2208,8 @@ const CommonTestAndPreview = (props) => {
2043
2208
  imageData = formDataObj.image || null;
2044
2209
  videoData = formDataObj.video || null;
2045
2210
  buttonData = formDataObj.button || null;
2211
+ cardsData = formDataObj.cards || [];
2212
+ messageType = formDataObj.type || '';
2046
2213
  accountId = formDataObj.accountId || null;
2047
2214
  accountDetails = formDataObj.accountDetails || null;
2048
2215
  scenarioKey = formDataObj.scenarioKey || VIBER_API_SCENARIO_KEY;
@@ -2069,6 +2236,8 @@ const CommonTestAndPreview = (props) => {
2069
2236
  url: formDataObj?.buttonURL || '',
2070
2237
  };
2071
2238
  }
2239
+ cardsData = viberPreview.cards || formDataObj?.cards || [];
2240
+ messageType = viberPreview.type || formDataObj?.type || '';
2072
2241
  // Extract account info from parent formDataObj if available
2073
2242
  accountId = formDataObj.accountId || null;
2074
2243
  accountDetails = formDataObj.accountDetails || null;
@@ -2111,6 +2280,10 @@ const CommonTestAndPreview = (props) => {
2111
2280
  text: messageText,
2112
2281
  };
2113
2282
 
2283
+ if (messageType === CHANNELS.VIBER) {
2284
+ messageType = '';
2285
+ }
2286
+
2114
2287
  // Add image if present
2115
2288
  if (imageData && imageData.url) {
2116
2289
  viberContent.image = {
@@ -2139,9 +2312,34 @@ const CommonTestAndPreview = (props) => {
2139
2312
  }
2140
2313
  }
2141
2314
 
2142
- // Resolve tags in text if custom values are provided
2143
- if (customValuesObj && Object.keys(customValuesObj).length > 0 && viberContent.text) {
2144
- viberContent.text = resolveTagsInText(viberContent.text, customValuesObj);
2315
+ if (messageType === MEDIA_TYPE_CAROUSEL || (Array.isArray(cardsData) && cardsData.length)) {
2316
+ viberContent.type = MEDIA_TYPE_CAROUSEL;
2317
+ viberContent.cards = (cardsData || []).map((card) => ({
2318
+ text: card?.text || '',
2319
+ mediaUrl: card?.mediaUrl || '',
2320
+ buttons: (card?.buttons || []).map((button) => ({
2321
+ title: button?.title || '',
2322
+ action: button?.action || '',
2323
+ })),
2324
+ }));
2325
+ delete viberContent.button;
2326
+ }
2327
+
2328
+ // Resolve tags in text/cards if custom values are provided
2329
+ if (customValuesObj && Object.keys(customValuesObj).length > 0) {
2330
+ if (viberContent.text) {
2331
+ viberContent.text = resolveTagsInText(viberContent.text, customValuesObj);
2332
+ }
2333
+ if (Array.isArray(viberContent.cards)) {
2334
+ viberContent.cards = resolveViberCarouselCards(viberContent.cards, customValuesObj);
2335
+ }
2336
+ if (viberContent.button) {
2337
+ viberContent.button = {
2338
+ ...viberContent.button,
2339
+ text: resolveTagsInText(viberContent.button.text || '', customValuesObj),
2340
+ url: resolveTagsInText(viberContent.button.url || '', customValuesObj),
2341
+ };
2342
+ }
2145
2343
  }
2146
2344
 
2147
2345
  // Build messageBody JSON string
@@ -2533,33 +2731,39 @@ const CommonTestAndPreview = (props) => {
2533
2731
  // Only use previewDataHtml if preview call was made
2534
2732
  if (hasPreviewCallBeenMade && previewDataHtml?.resolvedBody) {
2535
2733
  resolvedContent = previewDataHtml.resolvedBody;
2734
+ if (typeof resolvedContent === 'string') {
2735
+ try {
2736
+ resolvedContent = JSON.parse(resolvedContent);
2737
+ } catch (e) {
2738
+ resolvedContent = null;
2739
+ }
2740
+ }
2536
2741
  }
2537
2742
 
2538
- // Parse content if it's a string
2539
- let parsedViberContent = content;
2540
- if (typeof content === 'string') {
2541
- try {
2542
- parsedViberContent = JSON.parse(content);
2543
- } catch (e) {
2544
- parsedViberContent = {};
2545
- }
2743
+ // Parse content if it's a string, then merge with formData for carousel + tags in all entry flows
2744
+ let parsedViberContent = getViberMergedFormData();
2745
+ if (!parsedViberContent || typeof parsedViberContent !== 'object') {
2746
+ parsedViberContent = {};
2546
2747
  }
2547
- // Use resolvedContent if available (from preview call), otherwise use raw content
2748
+ // Merge template preview state with API resolved body so carousel type/cards survive slim responses
2548
2749
  if (resolvedContent && typeof resolvedContent === 'object') {
2549
- contentObj = { ...resolvedContent };
2550
- // Merge in accountName and brandName from raw content if not in resolvedContent
2551
- if (parsedViberContent?.accountName && !contentObj.accountName) {
2552
- contentObj.accountName = parsedViberContent.accountName;
2750
+ const base = parsedViberContent && typeof parsedViberContent === 'object' ? parsedViberContent : {};
2751
+ contentObj = mergeViberPreviewWithResolved(base, resolvedContent);
2752
+ if (base.accountName && !contentObj.accountName) {
2753
+ contentObj.accountName = base.accountName;
2553
2754
  }
2554
- if (parsedViberContent?.brandName && !contentObj.brandName) {
2555
- contentObj.brandName = parsedViberContent.brandName;
2755
+ if (base.brandName && !contentObj.brandName) {
2756
+ contentObj.brandName = base.brandName;
2556
2757
  }
2557
2758
  } else {
2558
- // Use raw content if no preview call was made or resolvedContent is not available
2559
- contentObj = {...parsedViberContent, messageContent: resolvedContent};
2560
- if (resolvedContent) {
2561
- contentObj.viberPreviewContent = {...parsedViberContent?.viberPreviewContent, messageContent: resolvedContent};
2562
- }
2759
+ contentObj = parsedViberContent && typeof parsedViberContent === 'object' ? parsedViberContent : {};
2760
+ }
2761
+ if (
2762
+ hasPreviewCallBeenMade
2763
+ && viberPreviewTagValues
2764
+ && Object.keys(viberPreviewTagValues).length > 0
2765
+ ) {
2766
+ contentObj = applyViberCustomValuesToContent(contentObj, viberPreviewTagValues);
2563
2767
  }
2564
2768
  } else if (channel === CHANNELS.ZALO) {
2565
2769
  // For Zalo, content is an object with templatePreviewUrl, templateStatus, etc.
@@ -2943,6 +3147,11 @@ const CommonTestAndPreview = (props) => {
2943
3147
 
2944
3148
  setCustomValues(updatedValues);
2945
3149
 
3150
+ // Viber: do not auto-resolve tags in preview; user must click Update preview
3151
+ if (channel === CHANNELS.VIBER) {
3152
+ return;
3153
+ }
3154
+
2946
3155
  // Update preview with prefilled values (this is a valid preview call trigger)
2947
3156
  // For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped.
2948
3157
  // SMS fallback preview is kept in sync by syncSmsFallbackPreview via smsFallbackPreviewText.
@@ -3025,6 +3234,7 @@ const CommonTestAndPreview = (props) => {
3025
3234
  setPreviewDevice(DESKTOP);
3026
3235
  setPreviewDataHtml('');
3027
3236
  setHasPreviewCallBeenMade(false); // Reset preview call flag
3237
+ setViberPreviewTagValues(null);
3028
3238
  setSelectedTestEntities([]);
3029
3239
  setBeeContent('');
3030
3240
  previousBeeContentRef.current = '';
@@ -3062,6 +3272,7 @@ const CommonTestAndPreview = (props) => {
3062
3272
  const handleClearSelection = () => {
3063
3273
  setSelectedCustomer(null);
3064
3274
  setHasPreviewCallBeenMade(false); // Reset flag when customer is cleared
3275
+ setViberPreviewTagValues(null);
3065
3276
 
3066
3277
  // Clear all preview errors when customer is cleared
3067
3278
  actions.clearPreviewErrors();
@@ -3120,6 +3331,9 @@ const CommonTestAndPreview = (props) => {
3120
3331
  emptyValues,
3121
3332
  selectedCustomer
3122
3333
  );
3334
+ if (channel === CHANNELS.VIBER) {
3335
+ setViberPreviewTagValues({ ...emptyValues });
3336
+ }
3123
3337
  actions.updatePreviewRequested(payload);
3124
3338
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
3125
3339
  };
@@ -3145,6 +3359,9 @@ const CommonTestAndPreview = (props) => {
3145
3359
  customValues,
3146
3360
  selectedCustomer
3147
3361
  );
3362
+ if (channel === CHANNELS.VIBER) {
3363
+ setViberPreviewTagValues({ ...customValues });
3364
+ }
3148
3365
  await actions.updatePreviewRequested(payload);
3149
3366
 
3150
3367
  await syncSmsFallbackPreview(customValues, selectedCustomer);