@capillarytech/creatives-library 8.0.271 → 8.0.272

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 (149) 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/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +17 -35
  9. package/tests/integration/TemplateCreation/api-response.js +31 -1
  10. package/tests/integration/TemplateCreation/msw-handler.js +2 -0
  11. package/utils/common.js +5 -0
  12. package/utils/commonUtils.js +28 -5
  13. package/utils/tests/commonUtil.test.js +224 -0
  14. package/utils/transformTemplateConfig.js +0 -10
  15. package/v2Components/CapDeviceContent/index.js +61 -56
  16. package/v2Components/CapTagList/index.js +6 -1
  17. package/v2Components/CapTagListWithInput/index.js +5 -1
  18. package/v2Components/CapTagListWithInput/messages.js +1 -1
  19. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  20. package/v2Components/ErrorInfoNote/constants.js +1 -0
  21. package/v2Components/ErrorInfoNote/index.js +402 -72
  22. package/v2Components/ErrorInfoNote/messages.js +32 -6
  23. package/v2Components/ErrorInfoNote/style.scss +278 -6
  24. package/v2Components/FormBuilder/tests/index.test.js +13 -4
  25. package/v2Components/HtmlEditor/HTMLEditor.js +418 -99
  26. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +870 -0
  27. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1882 -133
  28. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
  29. package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
  30. package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
  31. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +23 -102
  32. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -140
  33. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  34. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  35. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -1
  36. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +31 -6
  37. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  38. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  39. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  40. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  41. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  43. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +7 -10
  44. package/v2Components/HtmlEditor/components/PreviewPane/index.js +22 -43
  45. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  46. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +18 -0
  47. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +36 -31
  48. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +46 -34
  49. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
  50. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +52 -46
  51. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +277 -0
  52. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +295 -0
  53. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  54. package/v2Components/HtmlEditor/constants.js +45 -20
  55. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  56. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +351 -16
  57. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  58. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  59. package/v2Components/HtmlEditor/hooks/useValidation.js +213 -56
  60. package/v2Components/HtmlEditor/index.js +1 -1
  61. package/v2Components/HtmlEditor/messages.js +102 -94
  62. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +214 -45
  63. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
  64. package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
  65. package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
  66. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +158 -124
  67. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  68. package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
  69. package/v2Components/HtmlEditor/utils/validationConstants.js +38 -0
  70. package/v2Components/MobilePushPreviewV2/constants.js +6 -0
  71. package/v2Components/MobilePushPreviewV2/index.js +33 -7
  72. package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
  73. package/v2Components/TemplatePreview/index.js +47 -32
  74. package/v2Components/TemplatePreview/messages.js +4 -0
  75. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
  76. package/v2Containers/BeeEditor/index.js +172 -90
  77. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
  78. package/v2Containers/BeePopupEditor/constants.js +10 -0
  79. package/v2Containers/BeePopupEditor/index.js +194 -0
  80. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  81. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
  82. package/v2Containers/CreativesContainer/SlideBoxFooter.js +156 -13
  83. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  84. package/v2Containers/CreativesContainer/constants.js +1 -0
  85. package/v2Containers/CreativesContainer/index.js +251 -47
  86. package/v2Containers/CreativesContainer/messages.js +8 -0
  87. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
  88. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  89. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +103 -0
  90. package/v2Containers/Email/actions.js +7 -0
  91. package/v2Containers/Email/constants.js +5 -1
  92. package/v2Containers/Email/index.js +234 -29
  93. package/v2Containers/Email/messages.js +32 -0
  94. package/v2Containers/Email/reducer.js +12 -1
  95. package/v2Containers/Email/sagas.js +61 -7
  96. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  97. package/v2Containers/Email/tests/reducer.test.js +46 -0
  98. package/v2Containers/Email/tests/sagas.test.js +320 -29
  99. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1246 -0
  100. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +212 -21
  101. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  102. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +2472 -0
  103. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
  104. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  105. package/v2Containers/EmailWrapper/constants.js +2 -0
  106. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +627 -79
  107. package/v2Containers/EmailWrapper/index.js +103 -23
  108. package/v2Containers/EmailWrapper/messages.js +65 -1
  109. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +955 -0
  110. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +596 -82
  111. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  112. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  113. package/v2Containers/InApp/actions.js +7 -0
  114. package/v2Containers/InApp/constants.js +20 -4
  115. package/v2Containers/InApp/index.js +802 -360
  116. package/v2Containers/InApp/index.scss +4 -3
  117. package/v2Containers/InApp/messages.js +7 -3
  118. package/v2Containers/InApp/reducer.js +21 -3
  119. package/v2Containers/InApp/sagas.js +29 -9
  120. package/v2Containers/InApp/selectors.js +25 -5
  121. package/v2Containers/InApp/tests/index.test.js +154 -50
  122. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  123. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  124. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  125. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
  126. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  127. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
  128. package/v2Containers/InAppWrapper/constants.js +16 -0
  129. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  130. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  131. package/v2Containers/InAppWrapper/index.js +148 -0
  132. package/v2Containers/InAppWrapper/messages.js +49 -0
  133. package/v2Containers/InappAdvance/index.js +1099 -0
  134. package/v2Containers/InappAdvance/index.scss +10 -0
  135. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  136. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  137. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  138. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  139. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  140. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  141. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  142. package/v2Containers/TagList/index.js +62 -19
  143. package/v2Containers/Templates/_templates.scss +60 -1
  144. package/v2Containers/Templates/index.js +89 -4
  145. package/v2Containers/Templates/messages.js +4 -0
  146. package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
  147. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
  148. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
  149. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
@@ -208,13 +208,31 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
208
208
  const isBEESupport = (this.props.location.query.isBEESupport !== "false") || false;
209
209
  const isBEEAppEnable = this.checkBeeEditorAllowedForLibrary();
210
210
  if (!_.isEmpty(this.props.Templates.BEETemplate)) {
211
- if (this.props.Templates.BEETemplate.versions.base.is_drag_drop && isBEEAppEnable ) {
211
+ const isDragDrop = this.props.Templates.BEETemplate.versions?.base?.is_drag_drop;
212
+
213
+ if (isDragDrop && isBEEAppEnable ) {
212
214
  this.setState({isDragDrop: true});
213
215
  }
214
216
  if (this.props.params.id) {
215
- this.props.actions.getCmsSetting(BEE_PLUGIN, _.get(this.props.Templates.BEETemplate, 'versions.base.drag_drop_id', this.props.Templates.BEETemplate?._id), 'open', undefined, isBEESupport, isBEEAppEnable);
217
+ // Extract drag_drop_id - check multiple possible paths
218
+ const activeTab = this.props.Templates.BEETemplate.versions?.base?.activeTab || 'en';
219
+ const activeTabData = this.props.Templates.BEETemplate.versions?.base?.[activeTab] || {};
220
+ const dragDropId = activeTabData.drag_drop_id
221
+ || activeTabData.id
222
+ || _.get(this.props.Templates.BEETemplate, 'versions.base.drag_drop_id')
223
+ || _.get(this.props.Templates.BEETemplate, 'versions.base.id')
224
+ || this.props.Templates.BEETemplate?._id;
225
+ this.props.actions.getCmsSetting(BEE_PLUGIN, dragDropId, 'open', undefined, isBEESupport, isBEEAppEnable);
216
226
  } else if (this.props.location.query.module !== "library" || (this.props.location.query.module === "library" && !this.props.templateData)) {
217
- this.props.actions.getCmsSetting(BEE_PLUGIN, _.get(this.props.Templates.BEETemplate, 'versions.base.drag_drop_id', this.props.Templates.BEETemplate?._id), 'create', undefined, isBEESupport, isBEEAppEnable);
227
+ // Extract drag_drop_id - check multiple possible paths
228
+ const activeTab = this.props.Templates.BEETemplate.versions?.base?.activeTab || 'en';
229
+ const activeTabData = this.props.Templates.BEETemplate.versions?.base?.[activeTab] || {};
230
+ const dragDropId = activeTabData.drag_drop_id
231
+ || activeTabData.id
232
+ || _.get(this.props.Templates.BEETemplate, 'versions.base.drag_drop_id')
233
+ || _.get(this.props.Templates.BEETemplate, 'versions.base.id')
234
+ || this.props.Templates.BEETemplate?._id;
235
+ this.props.actions.getCmsSetting(BEE_PLUGIN, dragDropId, 'create', undefined, isBEESupport, isBEEAppEnable);
218
236
  }
219
237
  }
220
238
  this.setState({ content: (this.props.Templates.selectedEmailLayout ? this.props.Templates.selectedEmailLayout : ''), formData});
@@ -237,11 +255,52 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
237
255
  if (this.props.location.query.type === 'embedded') {
238
256
  this.showNext();
239
257
  }
258
+
259
+ // Check if BEETemplate was set after componentWillMount but before componentDidMount
260
+ // This can happen if BEETemplate is set asynchronously
261
+ if (!_.isEmpty(this.props.Templates.BEETemplate) && !this.state.isDragDrop) {
262
+ const isBEESupport = (this.props.location.query.isBEESupport !== "false") || false;
263
+ const isBEEAppEnable = this.checkBeeEditorAllowedForLibrary();
264
+ const beeTemplate = this.props.Templates.BEETemplate;
265
+ const activeTab = beeTemplate.versions?.base?.activeTab || 'en';
266
+ const activeTabData = beeTemplate.versions?.base?.[activeTab] || {};
267
+ const isDragDrop = activeTabData.is_drag_drop
268
+ || beeTemplate.versions?.base?.is_drag_drop
269
+ || beeTemplate.base?.is_drag_drop
270
+ || beeTemplate.is_drag_drop;
271
+
272
+ if (isDragDrop && isBEEAppEnable) {
273
+ this.setState({isDragDrop: true});
274
+
275
+ const dragDropId = activeTabData.drag_drop_id
276
+ || activeTabData.id
277
+ || _.get(beeTemplate, 'versions.base.drag_drop_id')
278
+ || _.get(beeTemplate, 'versions.base.id')
279
+ || beeTemplate._id;
280
+
281
+ if (this.props.params.id) {
282
+ const activeTabForLang = beeTemplate.versions?.base?.activeTab || 'en';
283
+ this.props.actions.getCmsSetting(BEE_PLUGIN, dragDropId, 'open', activeTabForLang, isBEESupport, isBEEAppEnable);
284
+ } else {
285
+ this.props.actions.getCmsSetting(BEE_PLUGIN, dragDropId, 'create', undefined, isBEESupport, isBEEAppEnable);
286
+ }
287
+ }
288
+ }
240
289
  }
241
290
 
242
291
 
243
- checkBeeEditorAllowedForLibrary = () => {
244
- const { isFullMode = false, editor } = this.props || {};
292
+ checkBeeEditorAllowedForLibrary = (props = null) => {
293
+ // Allow passing props for use in componentWillReceiveProps (to use nextProps)
294
+ const componentProps = props || this.props;
295
+ const { isFullMode = false, editor, Email } = componentProps || {};
296
+ // IMPORTANT: For isBEEAppEnable API parameter, use API response if available
297
+ // This ensures consistent behavior across full mode and library mode
298
+ // The API response (Email.isBeeEnabled) represents the actual org setting
299
+ if (Email?.isBeeEnabled !== undefined && Email?.isBeeEnabled !== null) {
300
+ return Email.isBeeEnabled;
301
+ }
302
+ // Fallback to mode-based logic for UI purposes (editor selection, etc.)
303
+ // But for API calls, this should ideally use the API response
245
304
  if ((editor === "BEE" && !isFullMode) || isFullMode) {
246
305
  return true;
247
306
  }
@@ -342,9 +401,61 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
342
401
  // this.props.globalActions.fetchSchemaForEntity(query);
343
402
  // }
344
403
 
404
+ // Check if BEETemplate was just set (for new flow when template details load)
405
+ // This handles the case where BEETemplate is set after componentWillMount
406
+ const wasBEETemplateEmpty = _.isEmpty(this.props.Templates.BEETemplate);
407
+ const isBEETemplateNowSet = !_.isEmpty(nextProps.Templates.BEETemplate);
408
+ const isBEEAppEnable = this.checkBeeEditorAllowedForLibrary(nextProps);
409
+ const isBEESupport = (nextProps.location.query.isBEESupport !== "false") || false;
410
+
411
+ if (wasBEETemplateEmpty && isBEETemplateNowSet && isBEEAppEnable) {
412
+ const beeTemplate = nextProps.Templates.BEETemplate;
413
+ const isDragDrop = beeTemplate.versions?.base?.is_drag_drop
414
+ || beeTemplate.versions?.base?.[beeTemplate.versions?.base?.activeTab || 'en']?.is_drag_drop
415
+ || beeTemplate.base?.is_drag_drop
416
+ || beeTemplate.is_drag_drop;
417
+
418
+ // Check if we're in edit mode - check multiple sources for id
419
+ const hasParamsId = nextProps.params?.id
420
+ || nextProps.location?.query?.id
421
+ || nextProps.location?.params?.id
422
+ || (nextProps.location?.pathname && nextProps.location.pathname.includes('/edit/'));
423
+
424
+ if (isDragDrop) {
425
+ this.setState({isDragDrop: true});
426
+
427
+ // Extract drag_drop_id - check multiple possible paths
428
+ // Priority: versions.base[activeTab].drag_drop_id > versions.base[activeTab].id > versions.base.drag_drop_id > _id
429
+ const activeTab = beeTemplate.versions?.base?.activeTab || 'en';
430
+ const activeTabData = beeTemplate.versions?.base?.[activeTab] || {};
431
+ let dragDropId = activeTabData.drag_drop_id
432
+ || activeTabData.id
433
+ || beeTemplate.versions?.base?.drag_drop_id
434
+ || beeTemplate.versions?.base?.id
435
+ || beeTemplate.base?.drag_drop_id
436
+ || beeTemplate.base?.id
437
+ || beeTemplate._id;
438
+
439
+ // Call getCmsSetting for BEE template - use 'open' mode if in edit mode
440
+ // Extract langId from active tab
441
+ const activeTabForLang = beeTemplate.versions?.base?.activeTab || 'en';
442
+ if (hasParamsId) {
443
+ this.props.actions.getCmsSetting(BEE_PLUGIN, dragDropId, 'open', activeTabForLang, isBEESupport, isBEEAppEnable);
444
+ } else if (nextProps.location.query.module !== "library" || (nextProps.location.query.module === "library" && !nextProps.templateData)) {
445
+ this.props.actions.getCmsSetting(BEE_PLUGIN, dragDropId, 'create', undefined, isBEESupport, isBEEAppEnable);
446
+ }
447
+ }
448
+ }
449
+
345
450
  if (this.state.isEdit && !this.state.editDataSet && !_.isEmpty(nextProps.Email.templateDetails) && !_.isEmpty(this.state.schema)) {
346
451
  this.setState({editDataSet: true}, () => {
347
452
  this.setEditData(nextProps.Email.templateDetails);
453
+ // Update template name in parent if available
454
+ if (this.props.isFullMode && this.props.showTemplateName && nextProps.Email.templateDetails.name) {
455
+ const formData = this.state.formData;
456
+ formData['template-name'] = nextProps.Email.templateDetails.name;
457
+ this.props.showTemplateName({formData, onFormDataChange: this.onFormDataChange});
458
+ }
348
459
  });
349
460
  }
350
461
  if (this.state.isEdit && nextProps.location.query.module === "library" && !_.isEmpty(nextProps.templateData) && !this.props.params.id && !nextProps.isGetFormData && _.isEmpty(_.get(this, `state.formData['template-subject']`))) {
@@ -383,7 +494,7 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
383
494
  const formData = _.cloneDeep(this.state.formData);
384
495
 
385
496
  const schema = _.cloneDeep(this.state.schema);
386
- const langIndex = formData[this.state.currentTab - 1].selectedLanguages.indexOf(langId);
497
+ const langIndex = formData[this.state.currentTab - 1]?.selectedLanguages?.indexOf(langId);
387
498
 
388
499
  const temp = (schema.containers || {})[this.state.currentTab - 1];
389
500
 
@@ -391,32 +502,91 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
391
502
  if (nextProps.Email.CmsSettings.isDragDrop && this.checkBeeEditorAllowedForLibrary()) {
392
503
  const beeJson = `BEEeditor${currentTab > 1 ? currentTab : ''}json`;
393
504
  const beeToken = `BEEeditor${currentTab > 1 ? currentTab : ''}token`;
394
- let beeJsonValue = _.get(nextProps, 'Templates.BEETemplate.versions.base.json-content', '');
505
+ let beeJsonValue = '';
395
506
  const selectedId = _.get(this.props, 'Email.templateDetails._id', '') || _.get(this.props, 'Templates.BEETemplate.versions.base.drag_drop_id', '');
396
- if (this.state.isEdit) {
397
- if (this.props.location.query.module === "library") {
398
- beeJsonValue = _.get(this.state, `formData[${currentTab - 1}][${langId}].json-content`, '');
399
- } else {
400
- beeJsonValue = _.get(nextProps, `Email.templateDetails.versions.base[${langId}].json-content`, '');
507
+
508
+ // Get beeJsonValue from template data - check multiple sources
509
+ // First check if it's already in formData (from setEditData)
510
+ beeJsonValue = _.get(this.state, `formData[${currentTab - 1}][${langId}].json-content`, '');
511
+
512
+ // Also check formData.base
513
+ if (!beeJsonValue) {
514
+ beeJsonValue = _.get(this.state, `formData.base[${langId}].json-content`, '');
515
+ }
516
+
517
+ // Always check templateDetails and BEETemplate regardless of mode
518
+ if (!beeJsonValue) {
519
+ // Check Email.templateDetails first
520
+ const templateDetails = nextProps.Email.templateDetails;
521
+ if (templateDetails && templateDetails.versions && templateDetails.versions.base) {
522
+ const baseVersion = templateDetails.versions.base;
523
+ // Try language-specific path first (e.g., base.en.json-content)
524
+ if (baseVersion[langId] && baseVersion[langId]['json-content']) {
525
+ beeJsonValue = baseVersion[langId]['json-content'];
526
+ } else if (baseVersion['json-content']) {
527
+ // Fallback to base-level json-content
528
+ beeJsonValue = baseVersion['json-content'];
529
+ }
530
+ }
531
+
532
+ // If still not found, check BEETemplate
533
+ if (!beeJsonValue) {
534
+ const beeTemplate = nextProps.Templates.BEETemplate;
535
+ if (beeTemplate && beeTemplate.versions && beeTemplate.versions.base) {
536
+ const beeBase = beeTemplate.versions.base;
537
+ if (beeBase[langId] && beeBase[langId]['json-content']) {
538
+ beeJsonValue = beeBase[langId]['json-content'];
539
+ } else if (beeBase['json-content']) {
540
+ beeJsonValue = beeBase['json-content'];
541
+ }
542
+ }
401
543
  }
402
544
  }
545
+ // Ensure we have a valid beeJsonValue - if it's a string, try to parse it to verify it's valid JSON
546
+ let finalBeeJsonValue = beeJsonValue;
547
+ if (beeJsonValue && typeof beeJsonValue === 'string') {
548
+ try {
549
+ // Try to parse to verify it's valid JSON
550
+ JSON.parse(beeJsonValue);
551
+ } catch (e) {
552
+ console.warn('[Email] componentWillReceiveProps - beeJsonValue is not valid JSON, using as-is:', e);
553
+ }
554
+ }
555
+
556
+ // Preserve existing formData values, especially template-subject
557
+ const existingFormData = formData[`${currentTab - 1}`][langId] || {};
403
558
  formData[`${currentTab - 1}`][langId] = {
404
- ...formData[`${currentTab - 1}`][langId],
559
+ ...existingFormData,
405
560
  is_drag_drop: true,
406
- [beeJson]: beeJsonValue,
561
+ [beeJson]: finalBeeJsonValue,
407
562
  [beeToken]: nextProps.Email.CmsSettings.tokenData,
408
563
  id: selectedId,
564
+ // Also store json-content for reference
565
+ 'json-content': finalBeeJsonValue,
409
566
  };
410
- _.forEach(temp.panes, (pane, index) => {
411
- const tempPane = pane;
412
- //
413
- if (parseInt(index, 10) === parseInt(langIndex, 10)) {
414
- //
415
- tempPane.sections[0].inputFields[0].cols[1].colStyle = {display: ""};
416
- tempPane.sections[0].inputFields[0].cols[0].colStyle = {display: "none"};
567
+
568
+ // Ensure template-subject is preserved at the top level
569
+ if (formData['template-subject'] === undefined || formData['template-subject'] === '') {
570
+ const subjectFromEditData = _.get(nextProps, 'Email.templateDetails.versions.base.subject', '');
571
+ if (subjectFromEditData) {
572
+ formData['template-subject'] = subjectFromEditData;
417
573
  }
418
- });
419
- this.setState({schema, isSchemaChanged: true, loadingStatus: this.state.loadingStatus + 1});
574
+ }
575
+
576
+ if (langIndex !== undefined && langIndex !== -1 && temp && temp.panes) {
577
+ _.forEach(temp.panes, (pane, index) => {
578
+ const tempPane = pane;
579
+ //
580
+ if (parseInt(index, 10) === parseInt(langIndex, 10)) {
581
+ //
582
+ if (tempPane.sections && tempPane.sections[0] && tempPane.sections[0].inputFields && tempPane.sections[0].inputFields[0] && tempPane.sections[0].inputFields[0].cols) {
583
+ tempPane.sections[0].inputFields[0].cols[1].colStyle = {display: ""};
584
+ tempPane.sections[0].inputFields[0].cols[0].colStyle = {display: "none"};
585
+ }
586
+ }
587
+ });
588
+ }
589
+ this.setState({schema, isSchemaChanged: true, loadingStatus: this.state.loadingStatus + 1, formData});
420
590
  } else {
421
591
  _.forEach(temp.panes, (pane, index) => {
422
592
  const tempPane = pane;
@@ -713,8 +883,18 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
713
883
  const msgString = JSON.stringify(msg);
714
884
  const langId = formData[this.state.currentTab - 1].activeTab;
715
885
  const langIndex = formData[this.state.currentTab - 1].selectedLanguages.indexOf(langId);
716
- const win = document.getElementById(`edmeditor${(langIndex + 1) > 1 ? (langIndex + 1) : ''}`).contentWindow;
717
- win.postMessage(msgString, '*');
886
+ const elementToSelect = document.getElementById(`edmeditor${(langIndex + 1) > 1 ? (langIndex + 1) : ''}`);
887
+ if (elementToSelect) {
888
+ try {
889
+ const win = elementToSelect.contentWindow;
890
+ if (win) {
891
+ win.postMessage(msgString, '*');
892
+ }
893
+ } catch (error) {
894
+ // Handle cross-origin frame access errors
895
+ console.warn('Failed to access iframe contentWindow (cross-origin restriction):', error);
896
+ }
897
+ }
718
898
  } else if (data === "unsubscribe" && this.state.editorInstanse) {
719
899
  const anchor = `<a href='{{${data}}}'>${data}</a>`;
720
900
  this.state.editorInstanse.insertHtml(`${anchor}`);
@@ -897,6 +1077,11 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
897
1077
  const isBEESupport = (this.props.location.query.isBEESupport !== "false") || false;
898
1078
  const formData = _.cloneDeep(this.state.formData);
899
1079
 
1080
+ // Set template name from editData if available
1081
+ if (editData.name) {
1082
+ formData['template-name'] = editData.name;
1083
+ }
1084
+
900
1085
  const schema = (this.props.location.query.type === 'embedded') ? this.removeStandAlone(this.state.schema) : _.cloneDeep(this.state.schema);
901
1086
  const containers = _.cloneDeep(schema.containers.slice());
902
1087
  let tabKey = "";
@@ -905,7 +1090,8 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
905
1090
  const type = this.props.location.query.type;
906
1091
 
907
1092
  formData['template-name'] = editData.name;
908
- formData['template-subject'] = _.get(editData, 'versions.base.subject');
1093
+ const subject = _.get(editData, 'versions.base.subject', '');
1094
+ formData['template-subject'] = subject;
909
1095
  formData.base = editData.versions.base;
910
1096
 
911
1097
  _.forEach(editData.versions.history, (data1, index) => {
@@ -986,8 +1172,13 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
986
1172
 
987
1173
 
988
1174
  this.setState({ formData, schema, tabKey, currentTab, tabCount: editData.versions.history.length, loadingStatus: this.state.loadingStatus + 1 }, () => {
989
- if (this.props.isFullMode) {
990
- this.props.showTemplateName({formData, onFormDataChange: this.onFormDataChange});
1175
+ if (this.props.isFullMode && this.props.showTemplateName) {
1176
+ // Ensure template name is set before showing it
1177
+ const updatedFormData = _.cloneDeep(formData);
1178
+ if (editData.name && !updatedFormData['template-name']) {
1179
+ updatedFormData['template-name'] = editData.name;
1180
+ }
1181
+ this.props.showTemplateName({formData: updatedFormData, onFormDataChange: this.onFormDataChange});
991
1182
  }
992
1183
  const isBEEAppEnable = this.checkBeeEditorAllowedForLibrary();
993
1184
  _.forEach((editData.versions.base.selectedLanguages), (language) => {
@@ -2663,6 +2854,7 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
2663
2854
  getTemplateDetailsInProgress,
2664
2855
  assetUploading,
2665
2856
  createTemplateInProgress,
2857
+ fetchingCmsSettings,
2666
2858
  } = this.props.Email;
2667
2859
  let isLoading =
2668
2860
  isLoadingMetaEntities ||
@@ -2671,7 +2863,7 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
2671
2863
  ) || (
2672
2864
  assetUploading !== undefined && assetUploading
2673
2865
  ) || (
2674
- Email && (fetchingCmsData || getTemplateDetailsInProgress)
2866
+ Email && (fetchingCmsData || getTemplateDetailsInProgress || fetchingCmsSettings)
2675
2867
  );
2676
2868
 
2677
2869
  if (!isLoading) {
@@ -2993,6 +3185,19 @@ function mapDispatchToProps(dispatch) {
2993
3185
  const withReducer = injectReducer({ key: 'email', reducer: v2EmailReducer });
2994
3186
  const withEmailSaga = injectSaga({ key: 'email', saga: v2EmailSagas });
2995
3187
 
3188
+ // Base Email component without saga registration (for use from EmailWrapper)
3189
+ // EmailWrapper already registers the saga, so we don't need to register it here
3190
+ export const EmailWithoutSaga = withCreatives({
3191
+ WrappedComponent: Email,
3192
+ mapStateToProps,
3193
+ mapDispatchToProps,
3194
+ userAuth: true,
3195
+ sagas: [], // No saga - EmailWrapper registers it
3196
+ reducers: [withReducer],
3197
+ });
3198
+
3199
+ // Email component with saga registration (for direct use from SlideBoxContent in Edit mode)
3200
+ // When Email is used directly (not as child of EmailWrapper), it needs to register the saga
2996
3201
  export default withCreatives({
2997
3202
  WrappedComponent: Email,
2998
3203
  mapStateToProps,
@@ -302,4 +302,36 @@ export default defineMessages({
302
302
  id: 'creatives.containersV2.Email.base64ImageError',
303
303
  defaultMessage: 'Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.',
304
304
  },
305
+ "editorTypeTitle": {
306
+ id: 'creatives.containersV2.Email.editorTypeTitle',
307
+ defaultMessage: 'Editor type',
308
+ },
309
+ "htmlEditorTitle": {
310
+ id: 'creatives.containersV2.Email.htmlEditorTitle',
311
+ defaultMessage: 'HTML editor',
312
+ },
313
+ "htmlEditorDescription": {
314
+ id: 'creatives.containersV2.Email.htmlEditorDescription',
315
+ defaultMessage: 'Use a basic HTML editor to write and format your content. Suitable if you are familiar with HTML.',
316
+ },
317
+ "dragDropEditorTitle": {
318
+ id: 'creatives.containersV2.Email.dragDropEditorTitle',
319
+ defaultMessage: 'Drag & drop editor',
320
+ },
321
+ "dragDropEditorDescription": {
322
+ id: 'creatives.containersV2.Email.dragDropEditorDescription',
323
+ defaultMessage: 'Create your content visually by dragging blocks — no coding needed. Great for quick, easy designs.',
324
+ },
325
+ "uploadZipTitle": {
326
+ id: 'creatives.containersV2.Email.uploadZipTitle',
327
+ defaultMessage: 'Upload zip file',
328
+ },
329
+ "uploadZipDescription": {
330
+ id: 'creatives.containersV2.Email.uploadZipDescription',
331
+ defaultMessage: 'Upload a ZIP containing your custom HTML, images, and assets. Ideal if your content is already built.',
332
+ },
333
+ "nextButton": {
334
+ id: 'creatives.containersV2.Email.nextButton',
335
+ defaultMessage: 'Next',
336
+ },
305
337
  });
@@ -11,6 +11,7 @@ import * as types from './constants';
11
11
  const initialState = fromJS({
12
12
  createTemplateInProgress: false,
13
13
  createResponse: {},
14
+ isBeeEnabled: false,
14
15
  });
15
16
 
16
17
  function emailReducer(state = initialState, action) {
@@ -107,6 +108,15 @@ function emailReducer(state = initialState, action) {
107
108
  return state
108
109
  .set('fetchingCmsData', false)
109
110
  .set('fetchingCmsDataFailed', true);
111
+ case types.GET_CMS_ACCOUNTS_REQUEST:
112
+ return state
113
+ .set('isBeeEnabled', false); // default to false
114
+ case types.GET_CMS_ACCOUNTS_SUCCESS:
115
+ return state
116
+ .set('isBeeEnabled', action.isBeeEnabled);
117
+ case types.GET_CMS_ACCOUNTS_FAILURE:
118
+ return state
119
+ .set('isBeeEnabled', false);
110
120
  case types.CLEAR_EMAIL_CRUD_RESPONSE_REQUEST:
111
121
  return state
112
122
  .set('createResponse', fromJS({}));
@@ -139,7 +149,8 @@ function emailReducer(state = initialState, action) {
139
149
  .set('CmsSettings', fromJS({}))
140
150
  .set('fetchingCmsData', false)
141
151
  .set('duplicateResponse', fromJS({}))
142
- .set('cmsData', '');
152
+ .set('cmsData', '')
153
+ .set('isBeeEnabled', false);
143
154
  case types.TRANSFORM_EMAIL_TEMPLATE_REQUEST:
144
155
  return state.set("createTemplateInProgress", true);
145
156
  default:
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  call, put, takeLatest, takeEvery, all,
3
3
  } from 'redux-saga/effects';
4
+ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
4
5
  import * as Api from '../../services/api';
5
6
  import * as types from './constants';
6
7
  import { transformEmailTemplates, storeS3FileSizeDetails } from '../../utils/cdnTransformation';
@@ -9,15 +10,47 @@ export function* createTemplate(template) {
9
10
  let errorMsg;
10
11
  try {
11
12
  const result = yield call(Api.createEmailTemplate, template);
12
- if (!result.success || result.status.code === 500) {
13
- errorMsg = result.message;
13
+ // Check if the API call failed (non-2xx status codes or explicit failure)
14
+ const statusCode = result.status?.code || result.status;
15
+ const isError = !result.success || statusCode >= 400;
16
+
17
+ if (isError) {
18
+ // Extract error message from various possible locations in the response
19
+ errorMsg = result.message
20
+ || result.errorMessage
21
+ || result.error?.message
22
+ || (result.response && result.response.message)
23
+ || (result.status && result.status.message)
24
+ || (result.data && result.data.message)
25
+ || `API Error: ${statusCode || 'Unknown error'}`;
26
+ // Show error notification directly from saga
27
+ CapNotification.error({
28
+ key: 'createTemplateError',
29
+ message: errorMsg,
30
+ });
14
31
  yield put({ type: types.CREATE_TEMPLATE_FAILURE, errorMsg });
32
+ // Call callback with error so component can handle it (if needed)
33
+ if (template.onCreateTemplateComplete) {
34
+ try {
35
+ // Call the callback directly (it's a regular function, not a generator)
36
+ template.onCreateTemplateComplete({ error: true, message: errorMsg, statusCode });
37
+ } catch (callbackError) {
38
+ console.error('[Email Saga] Error executing callback:', callbackError);
39
+ }
40
+ }
15
41
  } else {
16
- yield put({ type: types.CREATE_TEMPLATE_SUCCESS, data: result.response, statusCode: result.status ? result.status.code : '', errorMsg });
42
+ yield put({
43
+ type: types.CREATE_TEMPLATE_SUCCESS, data: result.response, statusCode: result.status ? result.status.code : '', errorMsg,
44
+ });
17
45
  yield template.onCreateTemplateComplete(result.response);
18
46
  }
19
47
  } catch (error) {
48
+ errorMsg = error.message || error.toString() || 'An unexpected error occurred';
20
49
  yield put({ type: types.CREATE_TEMPLATE_FAILURE, error, errorMsg });
50
+ // Call callback with error so component can handle it
51
+ if (template.onCreateTemplateComplete) {
52
+ yield template.onCreateTemplateComplete({ error: true, message: errorMsg });
53
+ }
21
54
  }
22
55
  }
23
56
 
@@ -26,14 +59,14 @@ export function* transformEmailTemplate({template, callback}) {
26
59
  const result = yield call(transformEmailTemplates, template);
27
60
  yield callback(result);
28
61
  } catch (error) {
29
- yield callback(template);
62
+ yield callback(template);
30
63
  }
31
64
  }
32
65
 
33
66
  export function* duplicateTemplate(payload) {
34
67
  let errorMsg;
35
68
  let result;
36
- let { id, channel, callback } = payload;
69
+ const { id, channel, callback } = payload;
37
70
  try {
38
71
  result = yield call(Api.duplicateTemplate, {id, channel} );
39
72
  if (result.status.code === 500) {
@@ -42,7 +75,9 @@ export function* duplicateTemplate(payload) {
42
75
  if (callback) {
43
76
  callback(result?.response);
44
77
  }
45
- yield put({ type: types.DUPLICATE_TEMPLATE_SUCCESS, data: result?.response, statusCode: result?.status?.code, errorMsg });
78
+ yield put({
79
+ type: types.DUPLICATE_TEMPLATE_SUCCESS, data: result?.response, statusCode: result?.status?.code, errorMsg,
80
+ });
46
81
  } catch (error) {
47
82
  yield put({ type: types.DUPLICATE_TEMPLATE_FAILURE, error, errorMsg });
48
83
  }
@@ -67,7 +102,9 @@ export function* getAllAssets(assetType, queryParams) {
67
102
  }
68
103
  }
69
104
 
70
- export function* getCmsSetting({cmsType, projectId, cmsMode, langId, isEdmSupport, isBEEAppEnable}) {
105
+ export function* getCmsSetting({
106
+ cmsType, projectId, cmsMode, langId, isEdmSupport, isBEEAppEnable,
107
+ }) {
71
108
  try {
72
109
  const result = yield call(Api.getCmsTemplateSettingsV2, cmsType, projectId, cmsMode, langId, isEdmSupport, isBEEAppEnable);
73
110
 
@@ -87,6 +124,17 @@ export function* getCmsData({cmsType, projectId, langId}) {
87
124
  }
88
125
  }
89
126
 
127
+ export function* getCmsAccounts({cmsType}) {
128
+ try {
129
+ const result = yield call(Api.getCmsAccounts, cmsType);
130
+ const { cmsAccounts } = result.data?.response || {};
131
+ const isBeeEnabled = cmsAccounts?.type === cmsType;
132
+ yield put({ type: types.GET_CMS_ACCOUNTS_SUCCESS, isBeeEnabled });
133
+ } catch (error) {
134
+ yield put({ type: types.GET_CMS_ACCOUNTS_FAILURE, error });
135
+ }
136
+ }
137
+
90
138
  export function* uploadAsset(file, assetType, fileParams) {
91
139
  try {
92
140
  const result = yield call(Api.uploadFile, file, assetType, fileParams);
@@ -123,6 +171,10 @@ function* watchGetCmsData() {
123
171
  yield takeEvery(types.GET_CMS_EDITOR_DATA_REQUEST, getCmsData);
124
172
  }
125
173
 
174
+ function* watchGetCmsAccounts() {
175
+ yield takeEvery(types.GET_CMS_ACCOUNTS_REQUEST, getCmsAccounts);
176
+ }
177
+
126
178
  function* watchUploadAsset() {
127
179
  yield takeLatest(types.UPLOAD_ASSET_REQUEST, uploadAsset);
128
180
  }
@@ -139,6 +191,7 @@ export default [
139
191
  watchGetAllAssets,
140
192
  watchGetCmsSetting,
141
193
  watchGetCmsData,
194
+ watchGetCmsAccounts,
142
195
  watchUploadAsset,
143
196
  watchDuplicateTemplate,
144
197
  ];
@@ -151,6 +204,7 @@ export function* v2EmailSagas() {
151
204
  watchGetAllAssets(),
152
205
  watchGetCmsSetting(),
153
206
  watchGetCmsData(),
207
+ watchGetCmsAccounts(),
154
208
  watchUploadAsset(),
155
209
  ]);
156
210
  }
@@ -4,6 +4,7 @@ exports[` 1`] = `
4
4
  Immutable.Map {
5
5
  "createTemplateInProgress": false,
6
6
  "createResponse": Immutable.Map {},
7
+ "isBeeEnabled": false,
7
8
  }
8
9
  `;
9
10
 
@@ -11,5 +12,6 @@ exports[` 2`] = `
11
12
  Immutable.Map {
12
13
  "createTemplateInProgress": true,
13
14
  "createResponse": Immutable.Map {},
15
+ "isBeeEnabled": false,
14
16
  }
15
17
  `;
@@ -15,4 +15,50 @@ describe('emailReducer', () => {
15
15
  };
16
16
  expect(emailReducer(undefined, action)).toMatchSnapshot();
17
17
  });
18
+
19
+ it.concurrent('it handles GET_CMS_ACCOUNTS_REQUEST action (line 111-113)', () => {
20
+ const initialState = fromJS({
21
+ isBeeEnabled: true, // Start with true to verify it gets set to false
22
+ });
23
+ const action = {
24
+ type: types.GET_CMS_ACCOUNTS_REQUEST,
25
+ };
26
+ const result = emailReducer(initialState, action);
27
+ expect(result.get('isBeeEnabled')).toBe(false);
28
+ });
29
+
30
+ it.concurrent('it handles GET_CMS_ACCOUNTS_SUCCESS action (line 114-116)', () => {
31
+ const initialState = fromJS({
32
+ isBeeEnabled: false,
33
+ });
34
+ const action = {
35
+ type: types.GET_CMS_ACCOUNTS_SUCCESS,
36
+ isBeeEnabled: true,
37
+ };
38
+ const result = emailReducer(initialState, action);
39
+ expect(result.get('isBeeEnabled')).toBe(true);
40
+ });
41
+
42
+ it.concurrent('it handles GET_CMS_ACCOUNTS_SUCCESS action with false (line 114-116)', () => {
43
+ const initialState = fromJS({
44
+ isBeeEnabled: true,
45
+ });
46
+ const action = {
47
+ type: types.GET_CMS_ACCOUNTS_SUCCESS,
48
+ isBeeEnabled: false,
49
+ };
50
+ const result = emailReducer(initialState, action);
51
+ expect(result.get('isBeeEnabled')).toBe(false);
52
+ });
53
+
54
+ it.concurrent('it handles GET_CMS_ACCOUNTS_FAILURE action (line 117-119)', () => {
55
+ const initialState = fromJS({
56
+ isBeeEnabled: true, // Start with true to verify it gets set to false
57
+ });
58
+ const action = {
59
+ type: types.GET_CMS_ACCOUNTS_FAILURE,
60
+ };
61
+ const result = emailReducer(initialState, action);
62
+ expect(result.get('isBeeEnabled')).toBe(false);
63
+ });
18
64
  });