@capillarytech/creatives-library 8.0.274 → 8.0.276-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.274",
4
+ "version": "8.0.276-alpha.0",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -8,6 +8,7 @@ import messages from './messages';
8
8
  import ErrorInfoNote from '../../v2Components/ErrorInfoNote';
9
9
  import { PREVIEW } from './constants';
10
10
  import { EMAIL_CREATE_MODES } from '../EmailWrapper/constants';
11
+ import { hasSupportCKEditor } from '../../utils/common';
11
12
 
12
13
  function getFullModeSaveBtn(slidBoxContent, isCreatingTemplate) {
13
14
  if (isCreatingTemplate) {
@@ -72,8 +73,10 @@ function SlideBoxFooter(props) {
72
73
  const isBEEEditor = selectedEmailCreateMode === EMAIL_CREATE_MODES.DRAG_DROP
73
74
  || (emailCreateMode === EMAIL_CREATE_MODES.EDITOR && !isHTMLEditorMode)
74
75
  || (isEditMode && !isHtmlEditorValidationStateActive);
76
+ const isSupportCKEditor = hasSupportCKEditor();
75
77
  // Only check validation for HTML Editor mode, not for BEE/DragDrop editor
76
- const shouldCheckValidation = isEmailChannel && htmlEditorValidationState && isHTMLEditorMode && !isBEEEditor;
78
+ // In upload mode the legacy Email (CK) component does not report validation state, so do not disable buttons
79
+ const shouldCheckValidation = isEmailChannel && htmlEditorValidationState && isHTMLEditorMode && !isBEEEditor && !isSupportCKEditor;
77
80
  const isContentEmpty = shouldCheckValidation ? (htmlEditorValidationState?.isContentEmpty ?? true) : false;
78
81
  // Check if validation has completed
79
82
  const validationComplete = shouldCheckValidation ? (htmlEditorValidationState?.validationComplete ?? false) : true;
@@ -123,7 +126,7 @@ function SlideBoxFooter(props) {
123
126
  const isBEEEditorMode = isBEEEditorModeInEdit || isBEEEditorModeInCreate;
124
127
  const hasBEEEditorErrors = isEmailChannel && isBEEEditorMode && (hasStandardErrors || hasLiquidErrors) && (!htmlEditorValidationState || !htmlEditorHasErrors);
125
128
 
126
- const shouldShowErrorInfoNote = hasBEEEditorErrors;
129
+ const shouldShowErrorInfoNote = hasBEEEditorErrors || isSupportCKEditor;
127
130
  return (
128
131
  <div className="template-footer-width">
129
132
  {shouldShowErrorInfoNote && (
@@ -235,7 +235,12 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
235
235
  this.props.actions.getCmsSetting(BEE_PLUGIN, dragDropId, 'create', undefined, isBEESupport, isBEEAppEnable);
236
236
  }
237
237
  }
238
- this.setState({ content: (this.props.Templates.selectedEmailLayout ? this.props.Templates.selectedEmailLayout : ''), formData});
238
+ const hasUploadedContent = !this.props.params?.id && this.props.Templates?.selectedEmailLayout && !_.isEmpty(formData);
239
+ this.setState({
240
+ content: (this.props.Templates.selectedEmailLayout ? this.props.Templates.selectedEmailLayout : ''),
241
+ formData,
242
+ ...(hasUploadedContent ? { loadingStatus: 2 } : {}),
243
+ });
239
244
 
240
245
  // setTimeout(() => {
241
246
  // // this.getFormData();
@@ -286,6 +291,12 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
286
291
  }
287
292
  }
288
293
  }
294
+
295
+ // When SUPPORT_CK_EDITOR: after zip/HTML upload we show with selectedEmailLayout. Ensure spinner stops
296
+ // even if formData was empty on first mount (e.g. currentOrgDetails not yet available).
297
+ if (!this.props.params?.id && this.props.Templates?.selectedEmailLayout) {
298
+ this.setState((prev) => ({ loadingStatus: Math.max(prev.loadingStatus, 2) }));
299
+ }
289
300
  }
290
301
 
291
302
 
@@ -313,11 +324,10 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
313
324
  // }
314
325
  const {isFullMode, isGetFormData} = nextProps;
315
326
  if (isFullMode && isGetFormData && !_.isEqual(isGetFormData, this.props.isGetFormData) && !this.state.isDragDrop) {
316
- //only for ck editor
317
- //when create button is clicked in full mode
318
- // Don't start validation if we're in Test & Preview mode
327
+ // Only for CK editor: when Done is clicked in full mode, trigger the same chain as library
328
+ // so that FormBuilder validation and saveFormData run reliably (getFormData -> saveValidationData -> startValidation).
319
329
  if (!nextProps.isTestAndPreviewMode) {
320
- this.startValidation(true);
330
+ this.getFormData();
321
331
  }
322
332
  }
323
333
  if (this.state.languageDataSet && nextProps.Templates.selectedEmailLayout && nextProps.Templates.selectedEmailLayout !== '' && !_.isEqual(this.props.Templates.selectedEmailLayout, nextProps.Templates.selectedEmailLayout )) {
@@ -1561,7 +1571,12 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
1561
1571
  newFormData[0] = baseData;
1562
1572
  newFormData[0].base = true;
1563
1573
  }
1564
- this.props.templatesActions.resetUploadData();
1574
+ // Don't clear selectedEmailLayout when we're in create mode with uploaded content -
1575
+ // EmailWrapper uses it to keep isShowEmailCreate true. Clearing it would unmount Email and show wrong UI.
1576
+ const isCreateWithUpload = !props.params?.id && props.Templates?.selectedEmailLayout;
1577
+ if (!isCreateWithUpload) {
1578
+ this.props.templatesActions.resetUploadData();
1579
+ }
1565
1580
  return newFormData;
1566
1581
  }
1567
1582
 
@@ -2902,6 +2917,25 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
2902
2917
  if (!isLoading) {
2903
2918
  isLoading = this.state.loadingStatus < 2;
2904
2919
  }
2920
+
2921
+ // When SUPPORT_CK_EDITOR: after zip/HTML upload we show Email component with selectedEmailLayout.
2922
+ // Don't show spinner once we have uploaded content in formData (create mode, no params.id).
2923
+ if (isLoading && !this.props.params?.id && this.props.Templates?.selectedEmailLayout) {
2924
+ const content = this.state.formData?.[0]?.['template-content']
2925
+ || _.get(this.state.formData, 'base.template-content')
2926
+ || (this.state.formData?.base && this.state.formData.base[this.state.formData.base?.activeTab]?.['template-content']);
2927
+ if (content) {
2928
+ isLoading = false;
2929
+ }
2930
+ if (typeof window !== 'undefined' && this.state.loadingStatus < 2) {
2931
+ console.log('[UPLOAD-DEBUG] Email isEmailLoading (upload path)', {
2932
+ loadingStatus: this.state.loadingStatus,
2933
+ hasContentInFormData: !!content,
2934
+ isLoadingResult: isLoading,
2935
+ hasSelectedEmailLayout: !!this.props.Templates?.selectedEmailLayout,
2936
+ });
2937
+ }
2938
+ }
2905
2939
  // if (isLoading) {
2906
2940
  // this.props.creativesContainerActions.hideCreativesContanerLoader();
2907
2941
  // }
@@ -198,12 +198,13 @@ const EmailWrapperView = ({
198
198
  const isEditModeForEditor = hasParamsIdForEditor;
199
199
  const isBEEFromProps = emailProps?.editor === 'BEE' && emailProps?.selectedEditorMode === null;
200
200
  const isDragDropFromCreateMode = emailCreateMode === EMAIL_CREATE_MODES.DRAG_DROP;
201
- const isExplicitlyBEEEditor = isBEEFromProps || isDragDropFromCreateMode;
201
+ const isExplicitlyBEEEditor = isBEEFromProps || isDragDropFromCreateMode || (emailProps?.editor === "BEE" && !isFullMode);
202
202
  let isHTMLEditorMode = false;
203
203
 
204
204
  if (supportCKEditor) {
205
- // Legacy flow: use HTML editor for UPLOAD so zip/HTML content (EmailLayout) is shown; use Email component for EDITOR
206
- isHTMLEditorMode = emailCreateMode === EMAIL_CREATE_MODES.UPLOAD;
205
+ // UPLOAD mode should use Email component (CKEditor), NOT HTML editor
206
+ // This ensures consistency between create and edit flows when SUPPORT_CK_EDITOR is enabled
207
+ isHTMLEditorMode = false; // Always use Email component (CKEditor) in legacy flow
207
208
  } else if (isEditModeForEditor) {
208
209
  isHTMLEditorMode = !isExplicitlyBEEEditor;
209
210
  } else {
@@ -517,4 +517,141 @@ describe('EmailWrapperView', () => {
517
517
  // Should handle gracefully if ref methods don't exist
518
518
  });
519
519
  });
520
+
521
+ describe('Issue 1: Beefree template should open in Beefree editor in Edit mode', () => {
522
+ beforeEach(() => {
523
+ const { hasSupportCKEditor } = require('../../../../utils/common');
524
+ hasSupportCKEditor.mockReturnValue(false); // New flow
525
+ });
526
+
527
+ it('should render Email component (not HTML editor) when editing Beefree template', () => {
528
+ const emailPropsWithBEE = {
529
+ ...defaultProps.emailProps,
530
+ editor: 'BEE',
531
+ selectedEditorMode: null,
532
+ };
533
+
534
+ renderWithIntl({
535
+ step: STEPS.CREATE_TEMPLATE_CONTENT,
536
+ isShowEmailCreate: true,
537
+ emailProps: emailPropsWithBEE,
538
+ emailCreateMode: EMAIL_CREATE_MODES.DRAG_DROP,
539
+ params: { id: 'beefree-template-1' },
540
+ location: { query: {}, pathname: '/email/edit/beefree-template-1' },
541
+ });
542
+
543
+ // Should render Email component (BEE editor), not HTML editor
544
+ expect(screen.queryByTestId('email-html-editor')).not.toBeInTheDocument();
545
+ // Email component would be rendered (we're using EmailWithoutSaga which doesn't have a testid)
546
+ });
547
+
548
+ it('should render HTML editor when editing non-Beefree template', () => {
549
+ const emailPropsWithHTML = {
550
+ ...defaultProps.emailProps,
551
+ editor: 'HTML',
552
+ selectedEditorMode: EMAIL_CREATE_MODES.HTML_EDITOR,
553
+ };
554
+
555
+ renderWithIntl({
556
+ step: STEPS.CREATE_TEMPLATE_CONTENT,
557
+ isShowEmailCreate: true,
558
+ emailProps: emailPropsWithHTML,
559
+ emailCreateMode: EMAIL_CREATE_MODES.HTML_EDITOR,
560
+ params: { id: 'html-template-1' },
561
+ location: { query: {}, pathname: '/email/edit/html-template-1' },
562
+ });
563
+
564
+ // Should render HTML editor for non-Beefree template
565
+ expect(screen.getByTestId('email-html-editor')).toBeInTheDocument();
566
+ });
567
+
568
+ it('should render Email component when emailCreateMode is DRAG_DROP in create mode', () => {
569
+ const emailPropsWithBEE = {
570
+ ...defaultProps.emailProps,
571
+ editor: 'BEE',
572
+ selectedEditorMode: null,
573
+ };
574
+
575
+ renderWithIntl({
576
+ step: STEPS.CREATE_TEMPLATE_CONTENT,
577
+ isShowEmailCreate: true,
578
+ emailProps: emailPropsWithBEE,
579
+ emailCreateMode: EMAIL_CREATE_MODES.DRAG_DROP,
580
+ params: {},
581
+ location: { query: {}, pathname: '/email/create' },
582
+ });
583
+
584
+ // Should render Email component (BEE editor), not HTML editor
585
+ expect(screen.queryByTestId('email-html-editor')).not.toBeInTheDocument();
586
+ });
587
+ });
588
+
589
+ describe('Issue 2: Upload Zip should open in CKEditor when SUPPORT_CK_EDITOR is enabled', () => {
590
+ beforeEach(() => {
591
+ const { hasSupportCKEditor } = require('../../../../utils/common');
592
+ hasSupportCKEditor.mockReturnValue(true); // Legacy flow
593
+ });
594
+
595
+ it('should render Email component (CKEditor) for UPLOAD mode in create flow', () => {
596
+ const emailPropsForCKEditor = {
597
+ ...defaultProps.emailProps,
598
+ editor: undefined, // Default CKEditor
599
+ selectedEditorMode: null,
600
+ };
601
+
602
+ renderWithIntl({
603
+ step: STEPS.CREATE_TEMPLATE_CONTENT,
604
+ isShowEmailCreate: true,
605
+ emailProps: emailPropsForCKEditor,
606
+ emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
607
+ EmailLayout: { html: '<p>Uploaded content</p>' },
608
+ params: {},
609
+ location: { query: {}, pathname: '/email/create' },
610
+ });
611
+
612
+ // Should render Email component (CKEditor), NOT HTML editor
613
+ expect(screen.queryByTestId('email-html-editor')).not.toBeInTheDocument();
614
+ });
615
+
616
+ it('should render Email component (CKEditor) for UPLOAD mode in edit flow', () => {
617
+ const emailPropsForCKEditor = {
618
+ ...defaultProps.emailProps,
619
+ editor: undefined,
620
+ selectedEditorMode: null,
621
+ };
622
+
623
+ renderWithIntl({
624
+ step: STEPS.CREATE_TEMPLATE_CONTENT,
625
+ isShowEmailCreate: true,
626
+ emailProps: emailPropsForCKEditor,
627
+ emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
628
+ EmailLayout: { html: '<p>Edited uploaded content</p>' },
629
+ params: { id: 'uploaded-template-1' },
630
+ location: { query: {}, pathname: '/email/edit/uploaded-template-1' },
631
+ });
632
+
633
+ // Should render Email component (CKEditor), NOT HTML editor
634
+ expect(screen.queryByTestId('email-html-editor')).not.toBeInTheDocument();
635
+ });
636
+
637
+ it('should NOT render HTML editor for EDITOR mode when SUPPORT_CK_EDITOR is enabled', () => {
638
+ const emailPropsForCKEditor = {
639
+ ...defaultProps.emailProps,
640
+ editor: undefined,
641
+ selectedEditorMode: null,
642
+ };
643
+
644
+ renderWithIntl({
645
+ step: STEPS.CREATE_TEMPLATE_CONTENT,
646
+ isShowEmailCreate: true,
647
+ emailProps: emailPropsForCKEditor,
648
+ emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
649
+ params: {},
650
+ location: { query: {}, pathname: '/email/create' },
651
+ });
652
+
653
+ // Should render Email component (CKEditor), NOT HTML editor
654
+ expect(screen.queryByTestId('email-html-editor')).not.toBeInTheDocument();
655
+ });
656
+ });
520
657
  });
@@ -643,7 +643,7 @@ const useEmailWrapper = ({
643
643
  // If template was created in BEE AND BEE is enabled → open in BEE editor
644
644
  // Otherwise → open in HTML editor (fallback)
645
645
  // IMPORTANT: When supportCKEditor is false, default to HTML editor unless explicitly BEE
646
- if (isDragDrop && isBeeEnabled) {
646
+ if ((isDragDrop && isBeeEnabled) || (!isFullMode && isBeeEnabled)) {
647
647
  editorType = 'BEE';
648
648
  selectedEditorMode = null; // BEE uses existing flow
649
649
  } else {
@@ -732,8 +732,7 @@ describe('useEmailWrapper', () => {
732
732
 
733
733
  await waitFor(() => {
734
734
  const emailProps = result.current.emailProps;
735
- expect(emailProps.editor).toBe('HTML');
736
- expect(emailProps.selectedEditorMode).toBe(EMAIL_CREATE_MODES.HTML_EDITOR);
735
+ expect(emailProps.editor).toBe('BEE');
737
736
  });
738
737
  });
739
738
 
@@ -762,8 +761,7 @@ describe('useEmailWrapper', () => {
762
761
 
763
762
  await waitFor(() => {
764
763
  const emailProps = result.current.emailProps;
765
- expect(emailProps.editor).toBe('HTML');
766
- expect(emailProps.selectedEditorMode).toBe(EMAIL_CREATE_MODES.HTML_EDITOR);
764
+ expect(emailProps.editor).toBe('BEE');
767
765
  });
768
766
  });
769
767
  });
@@ -114,7 +114,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
114
114
  if (nextProps.iosCtasData && isEmpty(this.state.templateCta)) {
115
115
  this.setState({showIosCtaTable: true});
116
116
  }
117
- if (nextProps.metaEntities && nextProps.metaEntities.layouts && nextProps.metaEntities.layouts.length > 0 && isEmpty(this.state.schema) && isEmpty(this.state.formData)) {
117
+ if (nextProps.metaEntities && nextProps.metaEntities.layouts && nextProps.metaEntities.layouts.length > 0 && isEmpty(this.state.schema)) {
118
118
  const schema = this.props.params.mode === "text" ? nextProps.metaEntities.layouts[0].definition.textSchema : nextProps.metaEntities.layouts[0].definition.imageSchema;
119
119
  const isAndroidSupported = get(this, "props.Templates.selectedWeChatAccount.configs.android") === '1';
120
120
  const isIosSupported = get(this, "props.Templates.selectedWeChatAccount.configs.ios") === '1';
@@ -168,7 +168,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
168
168
  this.props.actions.getMobilepushTemplatesList('mobilepush', params);
169
169
  }
170
170
  if (nextProps.metaEntities && nextProps.metaEntities.layouts && nextProps.metaEntities.layouts.length > 0 && _.isEmpty(this.state.fullSchema) && _.isEmpty(this.state.formData)) {
171
- this.setState({ fullSchema: nextProps.metaEntities.layouts[0].definition, schema: (nextProps.location.query.module === 'loyalty') ? nextProps.metaEntities.layouts[0].definition.textSchema : {} }, () => {
171
+ this.setState({fullSchema: nextProps.metaEntities.layouts[0].definition, schema: (nextProps.location.query.module === 'loyalty') ? nextProps.metaEntities.layouts[0].definition.textSchema : {}}, () => {
172
172
  this.handleEditSchemaOnPropsChange(nextProps, selectedWeChatAccount);
173
173
  const templateId = get(this, "props.params.id");
174
174
  if (nextProps.location.query.module !== 'loyalty' && templateId && templateId !== 'temp') {
@@ -388,11 +388,11 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
388
388
  }
389
389
  });
390
390
  } else {
391
- selectedAccount = accounts?.[0];
391
+ selectedAccount = accounts?.length > 0 ? accounts[0] : {};
392
392
  formData['mobilepush-accounts'] = selectedAccount?.id;
393
393
  }
394
394
  this.props.actions.setWeChatAccount(selectedAccount);
395
- this.setState({ formData, accountsOptions: accounts?.map((acc) => ({ key: acc.id, label: acc.name, value: acc.id })) });
395
+ this.setState({formData, accountsOptions: accounts?.length > 0 ? accounts.map((acc) => ({key: acc.id, label: acc.name, value: acc.id})) : []});
396
396
  };
397
397
 
398
398
  createDefinition = (account) => ({
@@ -2018,9 +2018,6 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
2018
2018
  if (this.props.supportedTags) {
2019
2019
  tags = this.props.supportedTags;
2020
2020
  }
2021
- if (this.props.supportedTags) {
2022
- tags = this.props.supportedTags;
2023
- }
2024
2021
  return (
2025
2022
  <div className="creatives-mobilepush-edit mobilepush-wrapper">
2026
2023
  <CapSpin spinning={spinning}>