@capillarytech/creatives-library 8.0.213 → 8.0.214-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/HOW_BEE_EDITOR_WORKS.md +375 -0
  2. package/constants/unified.js +1 -0
  3. package/package.json +1 -1
  4. package/services/api.js +5 -0
  5. package/utils/common.js +6 -1
  6. package/v2Components/CapTagList/index.js +2 -1
  7. package/v2Components/CapTagListWithInput/index.js +5 -1
  8. package/v2Components/CapTagListWithInput/messages.js +1 -1
  9. package/v2Components/ErrorInfoNote/style.scss +1 -1
  10. package/v2Components/HtmlEditor/HTMLEditor.js +86 -14
  11. package/v2Components/HtmlEditor/_htmlEditor.scss +4 -4
  12. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  13. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +107 -96
  14. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +68 -92
  15. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  16. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  17. package/v2Containers/CreativesContainer/SlideBoxContent.js +85 -35
  18. package/v2Containers/CreativesContainer/SlideBoxFooter.js +9 -3
  19. package/v2Containers/CreativesContainer/index.js +107 -35
  20. package/v2Containers/CreativesContainer/messages.js +4 -0
  21. package/v2Containers/Email/actions.js +7 -0
  22. package/v2Containers/Email/constants.js +5 -1
  23. package/v2Containers/Email/index.js +13 -0
  24. package/v2Containers/Email/messages.js +32 -0
  25. package/v2Containers/Email/reducer.js +12 -1
  26. package/v2Containers/Email/sagas.js +17 -0
  27. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1005 -0
  28. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +193 -7
  29. package/v2Containers/EmailWrapper/constants.js +2 -0
  30. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +470 -71
  31. package/v2Containers/EmailWrapper/index.js +102 -23
  32. package/v2Containers/EmailWrapper/messages.js +61 -1
  33. package/v2Containers/EmailWrapper/tests/EmailHTMLEditor.test.js +177 -0
  34. package/v2Containers/EmailWrapper/tests/EmailHTMLEditorValidation.test.js +90 -0
  35. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +49 -49
  36. package/v2Containers/TagList/index.js +2 -0
  37. package/v2Containers/Templates/index.js +5 -0
@@ -55,11 +55,11 @@ describe('useEmailWrapper', () => {
55
55
  mockStopTimer.mockClear();
56
56
 
57
57
  isEmpty.mockImplementation(val => {
58
- if (val === null || val === undefined) return true;
59
- if (typeof val === 'object') return Object.keys(val).length === 0;
60
- if (typeof val === 'string') return val.trim().length === 0;
61
- return !val;
62
- });
58
+ if (val === null || val === undefined) return true;
59
+ if (typeof val === 'object') return Object.keys(val).length === 0;
60
+ if (typeof val === 'string') return val.trim().length === 0;
61
+ return !val;
62
+ });
63
63
 
64
64
  mockProps = {
65
65
  ...EmailWrapperMockData,
@@ -224,7 +224,7 @@ describe('useEmailWrapper', () => {
224
224
  const mockReader = {
225
225
  onload: null,
226
226
  onerror: null,
227
- readAsText: jest.fn(function() {
227
+ readAsText: jest.fn(function () {
228
228
  this.result = mockHtmlContent;
229
229
  if (this.onload) {
230
230
  this.onload();
@@ -294,7 +294,7 @@ describe('useEmailWrapper', () => {
294
294
  });
295
295
 
296
296
  expect(mockCapNotificationError).toHaveBeenCalledWith(expect.objectContaining({
297
- key: "email-upload-error",
297
+ key: "email-upload-error",
298
298
  }));
299
299
  expect(mockProps.templatesActions.handleZipUpload).not.toHaveBeenCalled();
300
300
  });
@@ -309,14 +309,14 @@ describe('useEmailWrapper', () => {
309
309
  result.current.useFileUpload({ file: null });
310
310
  });
311
311
  expect(mockCapNotificationError).toHaveBeenCalledTimes(1);
312
- expect(mockCapNotificationError).toHaveBeenCalledWith(expect.objectContaining({ key: "email-upload-error"}));
312
+ expect(mockCapNotificationError).toHaveBeenCalledWith(expect.objectContaining({ key: "email-upload-error" }));
313
313
 
314
314
  mockCapNotificationError.mockClear();
315
315
  reactAct(() => {
316
- result.current.useFileUpload({ file: { name: 'test.zip', size: 100 } });
316
+ result.current.useFileUpload({ file: { name: 'test.zip', size: 100 } });
317
317
  });
318
318
  expect(mockCapNotificationError).toHaveBeenCalledTimes(1);
319
- expect(mockCapNotificationError).toHaveBeenCalledWith(expect.objectContaining({ key: "email-upload-error"}));
319
+ expect(mockCapNotificationError).toHaveBeenCalledWith(expect.objectContaining({ key: "email-upload-error" }));
320
320
 
321
321
  expect(mockProps.templatesActions.handleZipUpload).not.toHaveBeenCalled();
322
322
  expect(mockProps.templatesActions.handleHtmlUpload).not.toHaveBeenCalled();
@@ -397,7 +397,7 @@ describe('useEmailWrapper', () => {
397
397
  // selectedCreateMode is initially '' from hook's useState
398
398
  };
399
399
  const { result, rerender } = renderHook((props) => useEmailWrapper(props), {
400
- initialProps: initialProps
400
+ initialProps: initialProps
401
401
  });
402
402
 
403
403
  // 4. Simulate user selecting a template ID via the exposed callback (useEditor)
@@ -448,11 +448,11 @@ describe('useEmailWrapper', () => {
448
448
 
449
449
  it('sets selectedCreateMode in useEffect when in UPLOAD mode and CREATE_TEMPLATE_CONTENT step with EmailLayout', async () => {
450
450
  const initialRenderProps = {
451
- ...mockProps,
452
- step: STEPS.MODE_SELECTION,
453
- emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
454
- EmailLayout: null,
455
- };
451
+ ...mockProps,
452
+ step: STEPS.MODE_SELECTION,
453
+ emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
454
+ EmailLayout: null,
455
+ };
456
456
 
457
457
  const { result, rerender } = renderHook((props) => useEmailWrapper(props), {
458
458
  initialProps: initialRenderProps
@@ -467,11 +467,11 @@ describe('useEmailWrapper', () => {
467
467
 
468
468
  // Reset isEmpty to default before setting the specific mock for the effect
469
469
  isEmpty.mockImplementation((val) => {
470
- if (val === null || val === undefined) return true;
471
- if (typeof val === 'object') return Object.keys(val).length === 0;
472
- if (typeof val === 'string') return val.trim().length === 0;
473
- return !val;
474
- });
470
+ if (val === null || val === undefined) return true;
471
+ if (typeof val === 'object') return Object.keys(val).length === 0;
472
+ if (typeof val === 'string') return val.trim().length === 0;
473
+ return !val;
474
+ });
475
475
  // Mock isEmpty specifically for the EmailLayout check inside the useEffect
476
476
  isEmpty.mockImplementationOnce(() => false); // Mock !isEmpty(EmailLayout) to be true
477
477
 
@@ -479,16 +479,16 @@ describe('useEmailWrapper', () => {
479
479
 
480
480
  // Wait for the effect to run, set selectedCreateMode, and isShowEmailCreate to update
481
481
  await waitFor(() => {
482
- expect(result.current.isShowEmailCreate).toBe(true);
482
+ expect(result.current.isShowEmailCreate).toBe(true);
483
483
  });
484
484
 
485
485
  // Restore default isEmpty behavior after the specific mock is used
486
486
  isEmpty.mockImplementation((val) => {
487
- if (val === null || val === undefined) return true;
488
- if (typeof val === 'object') return Object.keys(val).length === 0;
489
- if (typeof val === 'string') return val.trim().length === 0;
490
- return !val;
491
- });
487
+ if (val === null || val === undefined) return true;
488
+ if (typeof val === 'object') return Object.keys(val).length === 0;
489
+ if (typeof val === 'string') return val.trim().length === 0;
490
+ return !val;
491
+ });
492
492
  isEmpty.mockClear(); // Clear any remaining mock state if needed
493
493
 
494
494
  expect(mockProps.templatesActions.setEdmTemplate).not.toHaveBeenCalled();
@@ -533,11 +533,11 @@ describe('useEmailWrapper', () => {
533
533
 
534
534
  // Reset isEmpty to default before setting the specific mock for the effect
535
535
  isEmpty.mockImplementation((val) => {
536
- if (val === null || val === undefined) return true;
537
- if (typeof val === 'object') return Object.keys(val).length === 0;
538
- if (typeof val === 'string') return val.trim().length === 0;
539
- return !val;
540
- });
536
+ if (val === null || val === undefined) return true;
537
+ if (typeof val === 'object') return Object.keys(val).length === 0;
538
+ if (typeof val === 'string') return val.trim().length === 0;
539
+ return !val;
540
+ });
541
541
  // Mock isEmpty specifically for the EmailLayout check inside the useEffect
542
542
  isEmpty.mockImplementationOnce(() => false); // Mock !isEmpty(EmailLayout) to be true
543
543
 
@@ -545,16 +545,16 @@ describe('useEmailWrapper', () => {
545
545
 
546
546
  // Wait for the effect to run, set selectedCreateMode, and isShowEmailCreate to update
547
547
  await waitFor(() => {
548
- expect(result.current.isShowEmailCreate).toBe(true);
548
+ expect(result.current.isShowEmailCreate).toBe(true);
549
549
  });
550
550
 
551
551
  // Restore default isEmpty behavior after the specific mock is used
552
552
  isEmpty.mockImplementation((val) => {
553
- if (val === null || val === undefined) return true;
554
- if (typeof val === 'object') return Object.keys(val).length === 0;
555
- if (typeof val === 'string') return val.trim().length === 0;
556
- return !val;
557
- });
553
+ if (val === null || val === undefined) return true;
554
+ if (typeof val === 'object') return Object.keys(val).length === 0;
555
+ if (typeof val === 'string') return val.trim().length === 0;
556
+ return !val;
557
+ });
558
558
  isEmpty.mockClear(); // Clear any remaining mock state if needed
559
559
 
560
560
  // --- Case 3: EDITOR mode selected, Template becomes available -> true (via effect) ---
@@ -563,11 +563,11 @@ describe('useEmailWrapper', () => {
563
563
  find.mockReturnValue(mockTemplate); // Ensure find is mocked for this case
564
564
 
565
565
  const editorPropsInitial = {
566
- ...mockProps,
567
- step: STEPS.TEMPLATE_SELECTION,
568
- emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
569
- CmsTemplates: [mockTemplate],
570
- SelectedEdmDefaultTemplate: null,
566
+ ...mockProps,
567
+ step: STEPS.TEMPLATE_SELECTION,
568
+ emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
569
+ CmsTemplates: [mockTemplate],
570
+ SelectedEdmDefaultTemplate: null,
571
571
  };
572
572
  rerender(editorPropsInitial);
573
573
 
@@ -579,12 +579,12 @@ describe('useEmailWrapper', () => {
579
579
  expect(result.current.modeContent).toEqual({ id: templateId });
580
580
 
581
581
  const editorPropsFinal = {
582
- ...mockProps, // Use fresh props base
583
- step: STEPS.CREATE_TEMPLATE_CONTENT, // Move to the create step
584
- emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
585
- CmsTemplates: [mockTemplate],
586
- SelectedEdmDefaultTemplate: null, // Template not yet selected in props
587
- // modeContent state ({id: templateId}) should persist internally from the reactAct call
582
+ ...mockProps, // Use fresh props base
583
+ step: STEPS.CREATE_TEMPLATE_CONTENT, // Move to the create step
584
+ emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
585
+ CmsTemplates: [mockTemplate],
586
+ SelectedEdmDefaultTemplate: null, // Template not yet selected in props
587
+ // modeContent state ({id: templateId}) should persist internally from the reactAct call
588
588
  };
589
589
 
590
590
  // Mock isEmpty specifically for the check within the useEffect triggered by rerender
@@ -372,6 +372,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
372
372
  channel={this.props.channel}
373
373
  disabled={this.props.disabled}
374
374
  fetchingSchemaError={this?.state?.tagsError}
375
+ popoverPlacement={this.props.popoverPlacement}
375
376
  />
376
377
  </div>
377
378
  );
@@ -402,6 +403,7 @@ TagList.propTypes = {
402
403
  disabled: PropTypes.bool,
403
404
  fetchingSchemaError: PropTypes.bool,
404
405
  eventContextTags: PropTypes.array,
406
+ popoverPlacement: PropTypes.string,
405
407
  intl: PropTypes.shape({
406
408
  formatMessage: PropTypes.func.isRequired,
407
409
  locale: PropTypes.string,
@@ -931,6 +931,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
931
931
  const zaloSelectedTemplateData = this.selectTemplate(parseInt(creativesParams._id, 10)) || {};
932
932
  const { name = '' } = zaloSelectedTemplateData;
933
933
  creativesParams.name = name
934
+ } else if (this.state.channel?.toLowerCase() === EMAIL_LOWERCASE) {
935
+ const emailSelectedTemplateData = this.selectTemplate(creativesParams._id) || {};
936
+ const activeTab = get(emailSelectedTemplateData, 'versions.base.activeTab', 'en');
937
+ const isDragDrop = get(emailSelectedTemplateData, `versions.base.${activeTab}.is_drag_drop`, false);
938
+ creativesParams.is_drag_drop = isDragDrop;
934
939
  }
935
940
  }
936
941
  creativesParams.type = this.state.channel.toUpperCase();