@capillarytech/creatives-library 8.0.256 → 8.0.257

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 (144) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +2 -1
  4. package/initialReducer.js +2 -0
  5. package/package.json +1 -1
  6. package/services/api.js +10 -0
  7. package/services/tests/api.test.js +34 -0
  8. package/utils/common.js +5 -0
  9. package/utils/commonUtils.js +28 -5
  10. package/utils/tests/commonUtil.test.js +224 -0
  11. package/utils/transformTemplateConfig.js +0 -10
  12. package/v2Components/CapDeviceContent/index.js +61 -56
  13. package/v2Components/CapTagList/index.js +6 -1
  14. package/v2Components/CapTagListWithInput/index.js +5 -1
  15. package/v2Components/CapTagListWithInput/messages.js +1 -1
  16. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  17. package/v2Components/ErrorInfoNote/constants.js +1 -0
  18. package/v2Components/ErrorInfoNote/index.js +457 -72
  19. package/v2Components/ErrorInfoNote/messages.js +36 -6
  20. package/v2Components/ErrorInfoNote/style.scss +282 -6
  21. package/v2Components/FormBuilder/tests/index.test.js +13 -4
  22. package/v2Components/HtmlEditor/HTMLEditor.js +547 -94
  23. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +874 -0
  24. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1441 -133
  25. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
  26. package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
  27. package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
  28. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +23 -102
  29. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -140
  30. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  31. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  32. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
  33. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +4 -4
  34. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  35. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  36. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  37. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  38. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  39. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  40. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
  41. package/v2Components/HtmlEditor/components/PreviewPane/index.js +22 -43
  42. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  43. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +1 -0
  44. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
  45. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +50 -34
  46. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
  47. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +70 -41
  48. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +255 -0
  49. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +364 -0
  50. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  51. package/v2Components/HtmlEditor/constants.js +42 -20
  52. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  53. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +103 -0
  54. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  55. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  56. package/v2Components/HtmlEditor/hooks/useValidation.js +189 -53
  57. package/v2Components/HtmlEditor/index.js +1 -1
  58. package/v2Components/HtmlEditor/messages.js +92 -94
  59. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +94 -45
  60. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
  61. package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
  62. package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
  63. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +134 -102
  64. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  65. package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
  66. package/v2Components/HtmlEditor/utils/validationConstants.js +40 -0
  67. package/v2Components/MobilePushPreviewV2/index.js +32 -7
  68. package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
  69. package/v2Components/TemplatePreview/index.js +47 -32
  70. package/v2Components/TemplatePreview/messages.js +4 -0
  71. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
  72. package/v2Containers/BeeEditor/index.js +172 -90
  73. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
  74. package/v2Containers/BeePopupEditor/constants.js +10 -0
  75. package/v2Containers/BeePopupEditor/index.js +194 -0
  76. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  77. package/v2Containers/CreativesContainer/SlideBoxContent.js +128 -51
  78. package/v2Containers/CreativesContainer/SlideBoxFooter.js +163 -13
  79. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  80. package/v2Containers/CreativesContainer/constants.js +1 -0
  81. package/v2Containers/CreativesContainer/index.js +239 -46
  82. package/v2Containers/CreativesContainer/messages.js +8 -0
  83. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
  84. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  85. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +106 -0
  86. package/v2Containers/Email/actions.js +7 -0
  87. package/v2Containers/Email/constants.js +5 -1
  88. package/v2Containers/Email/index.js +234 -29
  89. package/v2Containers/Email/messages.js +32 -0
  90. package/v2Containers/Email/reducer.js +12 -1
  91. package/v2Containers/Email/sagas.js +61 -7
  92. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  93. package/v2Containers/Email/tests/reducer.test.js +46 -0
  94. package/v2Containers/Email/tests/sagas.test.js +320 -29
  95. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1285 -0
  96. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +211 -21
  97. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  98. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +1880 -0
  99. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
  100. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  101. package/v2Containers/EmailWrapper/constants.js +2 -0
  102. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +629 -77
  103. package/v2Containers/EmailWrapper/index.js +103 -23
  104. package/v2Containers/EmailWrapper/messages.js +65 -1
  105. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +643 -0
  106. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +594 -77
  107. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  108. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  109. package/v2Containers/InApp/actions.js +7 -0
  110. package/v2Containers/InApp/constants.js +20 -4
  111. package/v2Containers/InApp/index.js +802 -359
  112. package/v2Containers/InApp/index.scss +4 -3
  113. package/v2Containers/InApp/messages.js +7 -3
  114. package/v2Containers/InApp/reducer.js +21 -3
  115. package/v2Containers/InApp/sagas.js +29 -9
  116. package/v2Containers/InApp/selectors.js +25 -5
  117. package/v2Containers/InApp/tests/index.test.js +154 -50
  118. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  119. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  120. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  121. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
  122. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  123. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
  124. package/v2Containers/InAppWrapper/constants.js +16 -0
  125. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  126. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  127. package/v2Containers/InAppWrapper/index.js +148 -0
  128. package/v2Containers/InAppWrapper/messages.js +49 -0
  129. package/v2Containers/InappAdvance/index.js +1099 -0
  130. package/v2Containers/InappAdvance/index.scss +10 -0
  131. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  132. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  133. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  134. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  135. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  136. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  137. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  138. package/v2Containers/TagList/index.js +62 -19
  139. package/v2Containers/Templates/_templates.scss +60 -1
  140. package/v2Containers/Templates/index.js +89 -4
  141. package/v2Containers/Templates/messages.js +4 -0
  142. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
  143. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
  144. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
Binary file
package/assets/iOS.png CHANGED
Binary file
@@ -45,6 +45,7 @@ export const GIFT_CARDS = 'GIFT_CARDS';
45
45
  export const PROMO_ENGINE = 'PROMO_ENGINE';
46
46
  export const LIQUID_SUPPORT = 'ENABLE_LIQUID_SUPPORT';
47
47
  export const ENABLE_NEW_MPUSH = 'ENABLE_NEW_MPUSH';
48
+ export const SUPPORT_CK_EDITOR = 'SUPPORT_CK_EDITOR';
48
49
  export const CUSTOM_TAG = 'CustomTagMessage';
49
50
  export const CUSTOMER_EXTENDED_FIELD = 'Customer extended fields';
50
51
  export const EXTENDED_TAG = 'ExtendedTagMessage';
@@ -168,7 +169,7 @@ export const JAPANESE_HIDE_DATE_TAGS = [
168
169
  "dd.mm.yy",
169
170
  "dd Mon",
170
171
  "dd/m/yyyy",
171
- ];
172
+ ];
172
173
 
173
174
  export const LIQUID_SUPPORTED_CHANNELS = [EMAIL, SMS, MOBILE_PUSH, INAPP];
174
175
 
package/initialReducer.js CHANGED
@@ -15,6 +15,7 @@ import galleryReducer from './v2Containers/Assets/Gallery/reducer';
15
15
  import CapCollapsibleLeftNavigationReducer from '@capillarytech/cap-ui-library/CapCollapsibleLeftNavigation/reducer';
16
16
  import { AIRA_REDUCER_DOMAIN, askAiraReducer } from '@capillarytech/cap-ui-library/CapAskAira';
17
17
  import previewAndTestReducer from './v2Components/TestAndPreviewSlidebox/reducer';
18
+ import inAppReducer from './v2Containers/InApp/reducer';
18
19
 
19
20
  export const initialReducer = {
20
21
  language: languageProviderReducer,
@@ -33,4 +34,5 @@ export const initialReducer = {
33
34
  gallery: galleryReducer,
34
35
  navigationConfig: CapCollapsibleLeftNavigationReducer,
35
36
  previewAndTest: previewAndTestReducer,
37
+ inApp: inAppReducer,
36
38
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.256",
4
+ "version": "8.0.257",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -465,6 +465,11 @@ export const getCmsTemplateSettingsV2 = (cmsType, projectId, cmsMode, langId, is
465
465
  return API.get(url);
466
466
  };
467
467
 
468
+ export const getCmsAccounts = (cmsType) => {
469
+ const url = `${API_ENDPOINT}/cms/accounts?type=${cmsType}`;
470
+ return API.get(url);
471
+ };
472
+
468
473
  export const getCmsTemplateData = (cmsType, projectId, langId) => {
469
474
  const url = `${API_ENDPOINT}/cms/getContent?type=${cmsType}&projectId=${projectId}&langId=${langId}`;
470
475
  return API.get(url);
@@ -720,4 +725,9 @@ export const getAssetStatus = (type, assetId) => {
720
725
  return request(url, getAPICallObject('GET'));
721
726
  };
722
727
 
728
+ export const getBeePopupBuilderToken = () => {
729
+ const url = `${API_ENDPOINT}/common/getInappTokenData`;
730
+ return request(url, getAPICallObject('GET'));
731
+ };
732
+
723
733
  export {request, getAPICallObject};
@@ -26,6 +26,8 @@ import {
26
26
  updateMetaConfig,
27
27
  getMediaDetails,
28
28
  getAssetStatus,
29
+ getBeePopupBuilderToken,
30
+ getCmsAccounts,
29
31
  } from '../api';
30
32
  import { mockData } from './mockData';
31
33
  import getSchema from '../getSchema';
@@ -973,3 +975,35 @@ describe('getAssetStatus', () => {
973
975
  expect(callArgs[0]).toContain('/assets/video/asset-456/status');
974
976
  });
975
977
  });
978
+
979
+ describe('getBeePopupBuilderToken', () => {
980
+ it('should return correct response', async () => {
981
+ global.fetch.mockReturnValue(Promise.resolve({
982
+ status: 200,
983
+ json: () => Promise.resolve({
984
+ status: 200,
985
+ response: 'test',
986
+ }),
987
+ }));
988
+ const result = await getBeePopupBuilderToken();
989
+ expect(result).toEqual({
990
+ status: 200,
991
+ response: 'test',
992
+ });
993
+ });
994
+ });
995
+
996
+ describe('getCmsAccounts', () => {
997
+ it('should return a promise (line 473-476)', () => {
998
+ // Similar to other API tests, just verify it returns a Promise
999
+ const result = getCmsAccounts('bee');
1000
+ expect(result).toBeInstanceOf(Promise);
1001
+ });
1002
+
1003
+ it('should be callable with cmsType parameter', () => {
1004
+ // Verify function exists and can be called
1005
+ expect(typeof getCmsAccounts).toBe('function');
1006
+ const result = getCmsAccounts('bee');
1007
+ expect(result).toBeInstanceOf(Promise);
1008
+ });
1009
+ });
package/utils/common.js CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  BADGES_ISSUE,
23
23
  ENABLE_WECHAT,
24
24
  LIQUID_SUPPORT,
25
+ SUPPORT_CK_EDITOR,
25
26
  ENABLE_NEW_MPUSH
26
27
  } from '../constants/unified';
27
28
  import { apiMessageFormatHandler } from './commonUtils';
@@ -95,6 +96,10 @@ export const hasLiquidSupportFeature = Auth.hasFeatureAccess.bind(
95
96
  LIQUID_SUPPORT,
96
97
  );
97
98
 
99
+ export const hasSupportCKEditor = Auth.hasFeatureAccess.bind(
100
+ null,
101
+ SUPPORT_CK_EDITOR,
102
+ );
98
103
 
99
104
  export const hasGiftVoucherFeature = Auth.hasFeatureAccess.bind(
100
105
  null,
@@ -173,20 +173,30 @@ export const validateLiquidTemplateContent = async (
173
173
  // Handle API errors or empty content
174
174
  if (result?.errors?.length > 0 || !validString || isError) {
175
175
  let standardErrors = [];
176
+ let liquidErrors = [];
177
+
178
+ // Empty content errors are NOT from liquid endpoints, so they go to standardErrors
176
179
  if (!validString) {
177
180
  standardErrors = [emptyBodyError];
178
181
  }
179
- let liquidErrors;
180
- if (result && Array.isArray(result?.errors)) {
181
- liquidErrors = result?.errors?.map((error) => {
182
+
183
+ // IMPORTANT: Errors from extractTags and liquidValidation endpoints should ALWAYS be categorized as liquidErrors
184
+ // These endpoints are: /extractTags and /ask-aira-service/creatives_ai/liquidValidation
185
+ // Only errors from these endpoints should appear in Liquid Issues tab
186
+ if (result && Array.isArray(result?.errors) && result.errors.length > 0) {
187
+ // Errors from extractTags or liquidValidation endpoints
188
+ liquidErrors = result.errors.map((error) => {
182
189
  const message = typeof error?.message === "string"
183
190
  ? error.message
184
191
  : somethingWrongMsg;
185
192
  return message;
186
193
  });
187
- } else {
194
+ } else if (isError) {
195
+ // If isError is true, it means the API call to extractTags/liquidValidation failed
196
+ // This is also a liquid endpoint error, so categorize as liquidErrors
188
197
  liquidErrors = [somethingWrongMsg];
189
198
  }
199
+
190
200
  onError({
191
201
  standardErrors,
192
202
  liquidErrors,
@@ -398,7 +408,20 @@ export const validateMobilePushContent = async (formData, options) => {
398
408
  // Helper function to extract content for a platform
399
409
  export const extractContent = (platformData) => {
400
410
  if (!platformData) return '';
401
- const { title, message, ctas } = platformData;
411
+ const { title, message, ctas, isBEEeditor, beeHtml } = platformData;
412
+
413
+ // For BEE editor, extract content from beeHtml
414
+ if (isBEEeditor && beeHtml) {
415
+ // beeHtml can be an object with value property or a string
416
+ const beeHtmlContent = typeof beeHtml === 'string' ? beeHtml : (beeHtml?.value || '');
417
+ return [
418
+ title,
419
+ beeHtmlContent,
420
+ ...((ctas?.map((cta) => cta?.text || cta?.actionLink)) || []),
421
+ ].filter(Boolean).join(' ');
422
+ }
423
+
424
+ // For regular content
402
425
  return [
403
426
  title,
404
427
  message,
@@ -1376,4 +1376,228 @@ describe("validateCarouselCards", () => {
1376
1376
  expect(result.isValid).toBe(true);
1377
1377
  });
1378
1378
  });
1379
+
1380
+ describe('extractContent BEE Editor Logic (L404-L412)', () => {
1381
+ it('extracts content from beeHtml as string', () => {
1382
+ const platformData = {
1383
+ title: 'Test Title',
1384
+ isBEEeditor: true,
1385
+ beeHtml: '<p>BEE HTML Content</p>',
1386
+ ctas: [
1387
+ { text: 'Click Here', actionLink: 'https://example.com' },
1388
+ ],
1389
+ };
1390
+
1391
+ const result = extractContent(platformData);
1392
+
1393
+ expect(result).toContain(platformData.title);
1394
+ expect(result).toContain(platformData.beeHtml);
1395
+ expect(result).toContain(platformData.ctas[0].text);
1396
+ });
1397
+
1398
+ it('extracts content from beeHtml as object with value property', () => {
1399
+ const platformData = {
1400
+ title: 'Test Title',
1401
+ isBEEeditor: true,
1402
+ beeHtml: { value: '<p>BEE HTML from Object</p>' },
1403
+ ctas: [
1404
+ { text: 'Button Text' },
1405
+ ],
1406
+ };
1407
+
1408
+ const result = extractContent(platformData);
1409
+
1410
+ expect(result).toContain(platformData.title);
1411
+ expect(result).toContain(platformData.beeHtml.value);
1412
+ expect(result).toContain(platformData.ctas[0].text);
1413
+ });
1414
+
1415
+ it('handles beeHtml as object without value property', () => {
1416
+ const platformData = {
1417
+ title: 'Test Title',
1418
+ isBEEeditor: true,
1419
+ beeHtml: { someOtherProperty: 'data' },
1420
+ ctas: [],
1421
+ };
1422
+
1423
+ const result = extractContent(platformData);
1424
+
1425
+ // Should extract title and empty beeHtml (since value is undefined)
1426
+ expect(result).toContain('Test Title');
1427
+ expect(result).not.toContain('someOtherProperty');
1428
+ });
1429
+
1430
+ it('extracts ctas with text property', () => {
1431
+ const platformData = {
1432
+ title: 'Title',
1433
+ isBEEeditor: true,
1434
+ beeHtml: '<p>Content</p>',
1435
+ ctas: [
1436
+ { text: 'CTA Text 1' },
1437
+ { text: 'CTA Text 2' },
1438
+ ],
1439
+ };
1440
+
1441
+ const result = extractContent(platformData);
1442
+
1443
+ expect(result).toContain('CTA Text 1');
1444
+ expect(result).toContain('CTA Text 2');
1445
+ });
1446
+
1447
+ it('extracts ctas with actionLink when text is missing', () => {
1448
+ const platformData = {
1449
+ title: 'Title',
1450
+ isBEEeditor: true,
1451
+ beeHtml: '<p>Content</p>',
1452
+ ctas: [
1453
+ { actionLink: 'https://link1.com' },
1454
+ { actionLink: 'https://link2.com' },
1455
+ ],
1456
+ };
1457
+
1458
+ const result = extractContent(platformData);
1459
+
1460
+ expect(result).toContain('https://link1.com');
1461
+ expect(result).toContain('https://link2.com');
1462
+ });
1463
+
1464
+ it('filters out falsy values with filter(Boolean)', () => {
1465
+ const platformData = {
1466
+ title: '',
1467
+ isBEEeditor: true,
1468
+ beeHtml: null,
1469
+ ctas: [
1470
+ { text: null, actionLink: null },
1471
+ { text: 'Valid Text' },
1472
+ ],
1473
+ };
1474
+
1475
+ const result = extractContent(platformData);
1476
+
1477
+ // Should only contain 'Valid Text' after filtering
1478
+ expect(result).toBe('Valid Text');
1479
+ });
1480
+
1481
+ it('handles empty ctas array', () => {
1482
+ const platformData = {
1483
+ title: 'Title Only',
1484
+ isBEEeditor: true,
1485
+ beeHtml: '<p>BEE Content</p>',
1486
+ ctas: [],
1487
+ };
1488
+
1489
+ const result = extractContent(platformData);
1490
+
1491
+ expect(result).toContain('Title Only');
1492
+ expect(result).toContain('<p>BEE Content</p>');
1493
+ });
1494
+
1495
+ it('handles undefined ctas', () => {
1496
+ const platformData = {
1497
+ title: 'Title',
1498
+ isBEEeditor: true,
1499
+ beeHtml: '<p>Content</p>',
1500
+ ctas: undefined,
1501
+ };
1502
+
1503
+ const result = extractContent(platformData);
1504
+
1505
+ expect(result).toContain('Title');
1506
+ expect(result).toContain('<p>Content</p>');
1507
+ });
1508
+
1509
+ it('joins all content with spaces', () => {
1510
+ const platformData = {
1511
+ title: 'Title',
1512
+ isBEEeditor: true,
1513
+ beeHtml: 'HTML',
1514
+ ctas: [
1515
+ { text: 'CTA1' },
1516
+ { text: 'CTA2' },
1517
+ ],
1518
+ };
1519
+
1520
+ const result = extractContent(platformData);
1521
+
1522
+ expect(result).toBe('Title HTML CTA1 CTA2');
1523
+ });
1524
+
1525
+ it('falls back to regular content when not BEE editor', () => {
1526
+ const platformData = {
1527
+ title: 'Title',
1528
+ message: 'Message',
1529
+ isBEEeditor: false,
1530
+ beeHtml: '<p>Should be ignored</p>',
1531
+ ctas: [
1532
+ { text: 'CTA' },
1533
+ ],
1534
+ };
1535
+
1536
+ const result = extractContent(platformData);
1537
+
1538
+ expect(result).toContain('Title');
1539
+ expect(result).toContain('Message');
1540
+ expect(result).toContain('CTA');
1541
+ expect(result).not.toContain('<p>Should be ignored</p>');
1542
+ });
1543
+
1544
+ it('handles null beeHtml', () => {
1545
+ const platformData = {
1546
+ title: 'Title',
1547
+ isBEEeditor: true,
1548
+ beeHtml: null,
1549
+ ctas: [{ text: 'CTA' }],
1550
+ };
1551
+
1552
+ const result = extractContent(platformData);
1553
+
1554
+ expect(result).toContain('Title');
1555
+ expect(result).toContain('CTA');
1556
+ });
1557
+
1558
+ it('handles undefined beeHtml', () => {
1559
+ const platformData = {
1560
+ title: 'Title',
1561
+ isBEEeditor: true,
1562
+ beeHtml: undefined,
1563
+ ctas: [],
1564
+ };
1565
+
1566
+ const result = extractContent(platformData);
1567
+
1568
+ expect(result).toBe('Title');
1569
+ });
1570
+
1571
+ it('handles complex ctas with both text and actionLink', () => {
1572
+ const platformData = {
1573
+ title: 'Title',
1574
+ isBEEeditor: true,
1575
+ beeHtml: 'Content',
1576
+ ctas: [
1577
+ { text: 'CTA Text', actionLink: 'https://example.com' },
1578
+ ],
1579
+ };
1580
+
1581
+ const result = extractContent(platformData);
1582
+
1583
+ // Should prefer text over actionLink
1584
+ expect(result).toContain('CTA Text');
1585
+ expect(result).toContain('Title');
1586
+ expect(result).toContain('Content');
1587
+ });
1588
+
1589
+ it('handles empty string beeHtml', () => {
1590
+ const platformData = {
1591
+ title: 'Title',
1592
+ isBEEeditor: true,
1593
+ beeHtml: '',
1594
+ ctas: [{ text: 'CTA' }],
1595
+ };
1596
+
1597
+ const result = extractContent(platformData);
1598
+
1599
+ expect(result).toBe('Title CTA');
1600
+ });
1601
+ });
1379
1602
  });
1603
+
@@ -101,7 +101,6 @@ export async function transformTemplateConfigWithMediaDetails(templateConfig, ch
101
101
  const contentType = detectMediaContentType(templateConfig);
102
102
 
103
103
  if (contentType === MEDIA_CONTENT_TYPE.NONE) {
104
- console.log('No media content detected, returning original config');
105
104
  return templateConfig;
106
105
  }
107
106
 
@@ -109,24 +108,19 @@ export async function transformTemplateConfigWithMediaDetails(templateConfig, ch
109
108
  let transformDirection;
110
109
  if (forceDirection) {
111
110
  transformDirection = forceDirection;
112
- console.log(`Forced transformation direction: ${transformDirection}`);
113
111
  } else {
114
112
  // Auto-detect based on content
115
113
  if (contentType === MEDIA_CONTENT_TYPE.URLS) {
116
114
  transformDirection = TRANSFORM_DIRECTION.FORWARD; // URLs → tags
117
- console.log('Detected URLs, applying forward transformation (URLs → tags)');
118
115
  } else if (contentType === MEDIA_CONTENT_TYPE.TAGS) {
119
116
  transformDirection = TRANSFORM_DIRECTION.REVERSE; // tags → URLs
120
- console.log('Detected media_content tags, applying reverse transformation (tags → URLs)');
121
117
  } else if (contentType === MEDIA_CONTENT_TYPE.MIXED) {
122
118
  transformDirection = TRANSFORM_DIRECTION.SMART; // Handle mixed content intelligently
123
- console.log('Detected mixed content, applying smart transformation');
124
119
  }
125
120
  }
126
121
 
127
122
  const newConfig = cloneDeep(templateConfig);
128
123
  try {
129
- console.log(`Fetching media details for ${transformDirection} transformation, ID: ${mediaDetailsId}`);
130
124
 
131
125
  // Fetch media details mapping from API
132
126
  const response = await getMediaDetails({ id: mediaDetailsId })
@@ -145,7 +139,6 @@ export async function transformTemplateConfigWithMediaDetails(templateConfig, ch
145
139
  idToUrlMapping[mediaId] = url;
146
140
  });
147
141
 
148
- console.log(`Processing ${Object.keys(urlToIdMapping).length} media mappings`);
149
142
 
150
143
  // Apply transformations based on direction
151
144
  newConfig?.cards?.forEach(card => {
@@ -157,7 +150,6 @@ export async function transformTemplateConfigWithMediaDetails(templateConfig, ch
157
150
  // Forward transformation: URL → tag
158
151
  const mediaDetailId = urlToIdMapping[currentUrl];
159
152
  if (mediaDetailId) {
160
- console.log(`Forward: ${currentUrl} → {{media_content(${mediaDetailId})}}`);
161
153
  card.media.url = `{{media_content(${mediaDetailId})}}`;
162
154
  } else {
163
155
  console.warn(`No media detail ID found for URL: ${currentUrl}`);
@@ -169,7 +161,6 @@ export async function transformTemplateConfigWithMediaDetails(templateConfig, ch
169
161
  if (mediaId) {
170
162
  const actualUrl = idToUrlMapping[mediaId];
171
163
  if (actualUrl) {
172
- console.log(`Reverse: {{media_content(${mediaId})}} → ${actualUrl}`);
173
164
  card.media.url = actualUrl;
174
165
  } else {
175
166
  console.warn(`No URL found for media ID: ${mediaId}`);
@@ -185,7 +176,6 @@ export async function transformTemplateConfigWithMediaDetails(templateConfig, ch
185
176
  console.error('Failed to fetch media details for transformation:', error);
186
177
 
187
178
  // Fallback: try to extract/convert what we can without API
188
- console.log('Falling back to URL extraction method');
189
179
  newConfig.cards.forEach(card => {
190
180
  if (card.media && card.media.url) {
191
181
  if (transformDirection === TRANSFORM_DIRECTION.FORWARD) {
@@ -69,7 +69,7 @@ const CapDeviceContent = (props) => {
69
69
  templateDescErrorHandler,
70
70
  templateTitleError,
71
71
  setTemplateTitleError,
72
- isAiContentBotDisabled
72
+ isAiContentBotDisabled,
73
73
  } = props || {};
74
74
  const { TextArea } = CapInput;
75
75
  const { formatMessage } = intl;
@@ -167,21 +167,22 @@ const CapDeviceContent = (props) => {
167
167
  return (
168
168
  <>
169
169
  <CapRow className="creatives-device-content">
170
- <CapLink
170
+ <CapLink
171
171
  title={isAndroid
172
- ? formatMessage(messages.copyContentFromIOS)
173
- : formatMessage(messages.copyCotentFromAndroid)}
172
+ ? formatMessage(messages.copyContentFromIOS)
173
+ : formatMessage(messages.copyCotentFromAndroid)}
174
174
  className="inapp-copy-content"
175
175
  onClick={onCopyTitleAndContent}
176
- />
176
+ />
177
177
  <CapRow className="creatives-inapp-title">
178
178
  <CapColumn
179
- className="inapp-content-main"
179
+ className="inapp-content-main"
180
180
  >
181
181
  <CapHeading type="h5" className="inapp-title">
182
182
  {formatMessage(messages.title)}
183
183
  </CapHeading>
184
- {getTagList(0)} {/* here 0 signifies the tags for template title */}
184
+ {getTagList(0)}
185
+ {/* here 0 signifies the tags for template title */}
185
186
  </CapColumn>
186
187
  <CapInput
187
188
  id="inapp-title-name-input"
@@ -213,7 +214,7 @@ const CapDeviceContent = (props) => {
213
214
  </CapRow>
214
215
  <CapRow className={`creatives-inapp-message ${!isMediaTypeImage && "message-bottom-margin"}`}>
215
216
  <CapColumn
216
- className="inapp-message-header"
217
+ className="inapp-message-header"
217
218
  >
218
219
  <CapHeading type="h5" className="inapp-message-header-style">
219
220
  {formatMessage(messages.message)}
@@ -222,37 +223,37 @@ const CapDeviceContent = (props) => {
222
223
  {/* here 1 signifies the tags for template message */}
223
224
  </CapColumn>
224
225
  <div className="inapp-create-template-message-input-wrapper">
225
- <TextArea
226
- id="inapp-create-template-message-input"
227
- className="inapp-create-template-message-input"
228
- placeholder={formatMessage(messages.textAreaInputPlaceholder)}
229
- onChange={onTemplateMessageChange}
230
- value={templateMessage || ""}
231
- autosize={{ minRows: 5, maxRows: 5 }}
232
- errorMessage={
233
- templateMessageError && (
234
- <CapError className="inapp-template-message-error">
235
- {templateMessageError}
236
- </CapError>
237
- )
238
- }
239
- />
240
- {!isAiContentBotDisabled && (
241
- <CapAskAira.ContentGenerationBot
242
- text={templateMessage || ""}
243
- setText={(text) => {
244
- onTemplateMessageChange({ target: { value: text } });
245
- }}
246
- iconPlacement="float-br"
247
- iconSize="1.6rem"
248
- rootStyle={{
249
- bottom: "0.2rem",
250
- right: "0.2rem",
251
- left: "auto",
252
- position: "absolute",
253
- }}
226
+ <TextArea
227
+ id="inapp-create-template-message-input"
228
+ className="inapp-create-template-message-input"
229
+ placeholder={formatMessage(messages.textAreaInputPlaceholder)}
230
+ onChange={onTemplateMessageChange}
231
+ value={templateMessage || ""}
232
+ autosize={{ minRows: 5, maxRows: 5 }}
233
+ errorMessage={
234
+ templateMessageError && (
235
+ <CapError className="inapp-template-message-error">
236
+ {templateMessageError}
237
+ </CapError>
238
+ )
239
+ }
254
240
  />
255
- )}
241
+ {!isAiContentBotDisabled && (
242
+ <CapAskAira.ContentGenerationBot
243
+ text={templateMessage || ""}
244
+ setText={(text) => {
245
+ onTemplateMessageChange({ target: { value: text } });
246
+ }}
247
+ iconPlacement="float-br"
248
+ iconSize="1.6rem"
249
+ rootStyle={{
250
+ bottom: "0.2rem",
251
+ right: "0.2rem",
252
+ left: "auto",
253
+ position: "absolute",
254
+ }}
255
+ />
256
+ )}
256
257
  </div>
257
258
  {isMediaTypeImage && (
258
259
  <>
@@ -279,14 +280,16 @@ const CapDeviceContent = (props) => {
279
280
  </CapRow>
280
281
  </CapRow>
281
282
  <CapRow className="inapp-action-link">
282
- <CapCheckbox onChange={onActionLinkCheckBoxChange} checked={addActionLink}/>
283
+ <CapCheckbox onChange={onActionLinkCheckBoxChange} checked={addActionLink} />
283
284
  <CapRow className="inapp-render-heading">
284
285
  <CapHeader
285
- title={<CapRow type="flex">
286
- <CapHeading type="h4">
287
- {formatMessage(messages.addActionLink)}
288
- </CapHeading>
289
- </CapRow>}
286
+ title={(
287
+ <CapRow type="flex">
288
+ <CapHeading type="h4">
289
+ {formatMessage(messages.addActionLink)}
290
+ </CapHeading>
291
+ </CapRow>
292
+ )}
290
293
  description={<CapLabel type="label3">{formatMessage(messages.addActionLinkDesc)}</CapLabel>}
291
294
  />
292
295
  {addActionLink && (
@@ -310,19 +313,21 @@ const CapDeviceContent = (props) => {
310
313
  <CapRow className="inapp-cta-button">
311
314
  <CapHeader
312
315
  className="inapp-render-heading-cta-button"
313
- title={<CapRow type="flex">
314
- <CapHeading type="h4">
315
- {formatMessage(messages.btnLabel)}
316
- </CapHeading>
317
- <CapHeading
318
- type="h6"
319
- className="inapp-optional-label"
320
- >
321
- {formatMessage(messages.optional)}
322
- </CapHeading>
323
- </CapRow>}
316
+ title={(
317
+ <CapRow type="flex">
318
+ <CapHeading type="h4">
319
+ {formatMessage(messages.btnLabel)}
320
+ </CapHeading>
321
+ <CapHeading
322
+ type="h6"
323
+ className="inapp-optional-label"
324
+ >
325
+ {formatMessage(messages.optional)}
326
+ </CapHeading>
327
+ </CapRow>
328
+ )}
324
329
  description={<CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>}
325
- />
330
+ />
326
331
  <CapRadioGroup
327
332
  options={BUTTON_RADIO_OPTIONS}
328
333
  value={buttonType}
@@ -227,6 +227,10 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
227
227
 
228
228
  togglePopoverVisibility = (visible) => {
229
229
  this.setState({visible});
230
+ // Call onVisibleChange callback if provided (for triggering API calls when popover opens)
231
+ if (this.props.onVisibleChange) {
232
+ this.props.onVisibleChange(visible);
233
+ }
230
234
  };
231
235
 
232
236
  renderDynamicTagFlow = () => {
@@ -468,7 +472,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
468
472
  onVisibleChange={this.togglePopoverVisibility}
469
473
  content={contentSection}
470
474
  trigger="click"
471
- placement={channel === EMAIL.toUpperCase() ? "leftTop" : "rightTop"}
475
+ placement={this.props.popoverPlacement || (channel === EMAIL.toUpperCase() ? "leftTop" : "rightTop")}
472
476
  >
473
477
  <CapTooltip
474
478
  title={
@@ -535,6 +539,7 @@ CapTagList.propTypes = {
535
539
  channel: PropTypes.string,
536
540
  disabled: PropTypes.bool,
537
541
  fetchingSchemaError: PropTypes.bool,
542
+ popoverPlacement: PropTypes.string,
538
543
  };
539
544
 
540
545
  CapTagList.defaultValue = {