@capillarytech/creatives-library 9.0.70-alpha.sprint-cap-213712.13 → 9.0.70

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 (39) hide show
  1. package/package.json +1 -1
  2. package/v2Components/FormBuilder/Classic.js +3 -0
  3. package/v2Containers/CreativesContainer/SlideBoxContent.js +39 -30
  4. package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
  5. package/v2Containers/CreativesContainer/constants.js +1 -0
  6. package/v2Containers/CreativesContainer/index.js +26 -1
  7. package/v2Containers/CreativesContainer/messages.js +4 -0
  8. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +40 -12
  9. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -0
  10. package/v2Containers/CreativesContainer/tests/switchToEditAfterCreate.test.js +152 -0
  11. package/v2Containers/Email/index.js +41 -6
  12. package/v2Containers/Email/tests/index.test.js +123 -0
  13. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +32 -12
  14. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +34 -0
  15. package/v2Containers/InApp/index.js +19 -23
  16. package/v2Containers/InApp/tests/index.test.js +52 -9
  17. package/v2Containers/InappAdvance/index.js +19 -24
  18. package/v2Containers/InappAdvance/tests/index.test.js +39 -15
  19. package/v2Containers/Line/Container/index.js +18 -8
  20. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +129 -108
  21. package/v2Containers/Line/Container/tests/index.test.js +68 -0
  22. package/v2Containers/MobilePush/Create/index.js +24 -5
  23. package/v2Containers/MobilePush/Create/test/stayOpenOnSave.test.js +80 -0
  24. package/v2Containers/MobilePush/Edit/index.js +15 -9
  25. package/v2Containers/MobilePush/Edit/test/stayOpenOnSave.test.js +60 -0
  26. package/v2Containers/MobilePushNew/index.js +41 -7
  27. package/v2Containers/MobilePushNew/tests/index.test.js +61 -0
  28. package/v2Containers/Sms/Create/index.js +32 -14
  29. package/v2Containers/Sms/Create/test/stayOpenOnSave.test.js +92 -0
  30. package/v2Containers/Sms/Edit/index.js +22 -14
  31. package/v2Containers/Sms/Edit/tests/index.test.js +66 -0
  32. package/v2Containers/Templates/index.js +17 -4
  33. package/v2Containers/Viber/index.js +15 -6
  34. package/v2Containers/Viber/tests/index.test.js +97 -28
  35. package/v2Containers/WebPush/Create/components/FormActions.js +1 -1
  36. package/v2Containers/WebPush/Create/components/FormActions.test.js +3 -3
  37. package/v2Containers/WebPush/Create/index.js +32 -6
  38. package/v2Containers/WebPush/Create/messages.js +0 -4
  39. package/v2Containers/WebPush/Create/tests/stayOpenOnSave.test.js +268 -0
@@ -1,6 +1,7 @@
1
1
  import React, { useState, useEffect, useRef } from "react";
2
2
  import isEmpty from 'lodash/isEmpty';
3
3
  import get from 'lodash/get';
4
+ import isPlainObject from 'lodash/isPlainObject';
4
5
  import { bindActionCreators } from "redux";
5
6
  import { createStructuredSelector } from "reselect";
6
7
  import { injectIntl, FormattedMessage } from "react-intl";
@@ -62,7 +63,6 @@ export const InappAdvanced = (props) => {
62
63
  intl,
63
64
  actions,
64
65
  isFullMode,
65
- onCreateComplete,
66
66
  params,
67
67
  templateData = {},
68
68
  editData = {},
@@ -102,6 +102,9 @@ export const InappAdvanced = (props) => {
102
102
  const [tags, updateTags] = useState([]);
103
103
  //for edit only
104
104
  const [isEditFlow, setEditFlow] = useState(false);
105
+ // Full mode: id of the template created in this session — the form stays as is and later saves update it
106
+ const [savedTemplateId, setSavedTemplateId] = useState('');
107
+ const isUpdate = isEditFlow || !!savedTemplateId;
105
108
  const [errorMessage, setErrorMessage] = useState({
106
109
  STANDARD_ERROR_MSG: {
107
110
  ANDROID: [],
@@ -545,7 +548,7 @@ export const InappAdvanced = (props) => {
545
548
  const actionCallback = ({ errorMessage: errorMsg }) => {
546
549
  if (!errorMsg) {
547
550
  CapNotification.success({
548
- message: isEditFlow ? formatMessage(messages.inAppEditNotification, {
551
+ message: isUpdate ? formatMessage(messages.inAppEditNotification, {
549
552
  name: templateName,
550
553
  }) : formatMessage(messages.inAppCreateNotification, {
551
554
  name: templateName,
@@ -648,10 +651,15 @@ export const InappAdvanced = (props) => {
648
651
  const payload = createPayload(latestHtmlValues);
649
652
  actions.createInAppTemplate(payload, (resp, errorMsg) => {
650
653
  actionCallback({ resp, errorMessage: errorMsg });
654
+ setSpin(false);
651
655
  if (!errorMsg) {
652
- onCreateComplete();
653
- } else {
654
- setSpin(false);
656
+ // Full mode stays open and continues in place as an edit.
657
+ const savedResponse = resp?.templateId;
658
+ const newTemplateId = (isPlainObject(savedResponse) ? savedResponse._id : savedResponse) || '';
659
+ setSavedTemplateId(newTemplateId);
660
+ getFormData({
661
+ _id: newTemplateId, validity: true, keepSlideBoxOpen: true, continuedAsEdit: true,
662
+ });
655
663
  }
656
664
  });
657
665
  };
@@ -664,14 +672,13 @@ export const InappAdvanced = (props) => {
664
672
  actions.editTemplate(
665
673
  {
666
674
  ...payload,
667
- _id: params?.id,
675
+ _id: params?.id || savedTemplateId,
668
676
  },
669
677
  (resp, errorMsg) => {
670
678
  actionCallback({ resp, errorMessage: errorMsg });
679
+ setSpin(false);
671
680
  if (!errorMsg) {
672
- onCreateComplete();
673
- } else {
674
- setSpin(false);
681
+ getFormData({ validity: true, keepSlideBoxOpen: true }); // stays open; host refreshes its list
675
682
  }
676
683
  },
677
684
  );
@@ -846,7 +853,7 @@ export const InappAdvanced = (props) => {
846
853
 
847
854
  const onDoneCallback = async () => {
848
855
  if (isFullMode) {
849
- if (isEditFlow) {
856
+ if (isUpdate) {
850
857
  await onEditInApp();
851
858
  return;
852
859
  }
@@ -950,20 +957,8 @@ export const InappAdvanced = (props) => {
950
957
  disabled={isDisableDone()}
951
958
  className="inapp-create-btn"
952
959
  >
953
- {(() => {
954
- if (isEditFlow) {
955
- return isFullMode ? (
956
- <FormattedMessage {...messages.update} />
957
- ) : (
958
- <FormattedMessage {...globalMessages.done} />
959
- );
960
- }
961
- return isFullMode ? (
962
- <FormattedMessage {...messages.create} />
963
- ) : (
964
- <FormattedMessage {...globalMessages.done} />
965
- );
966
- })()}
960
+ {/* Full mode saves without closing, so the button reads "Save" for create and edit */}
961
+ <FormattedMessage {...(isFullMode ? globalMessages.save : globalMessages.done)} />
967
962
  </CapButton>
968
963
  </>
969
964
  }
@@ -6,7 +6,7 @@ import { configureStore } from '@capillarytech/vulcan-react-sdk/utils';
6
6
  import history from '../../../utils/history';
7
7
  import { initialReducer } from '../../../initialReducer';
8
8
  import { InappAdvanced } from '../index';
9
- import { render, screen, fireEvent, waitFor } from '../../../utils/test-utils';
9
+ import { render, screen, fireEvent, waitFor, act } from '../../../utils/test-utils';
10
10
 
11
11
  // Mock child components that are complex or not under test
12
12
  jest.mock('../../BeePopupEditor', () => ({
@@ -113,7 +113,7 @@ describe('InappAdvanced Component', () => {
113
113
  it('should render without crashing', () => {
114
114
  renderComponent(defaultProps);
115
115
 
116
- expect(screen.getByText('Create')).toBeInTheDocument();
116
+ expect(screen.getByText('Save')).toBeInTheDocument();
117
117
  });
118
118
 
119
119
  it('should call getBeePopupBuilderToken on mount', () => {
@@ -129,7 +129,7 @@ describe('InappAdvanced Component', () => {
129
129
  });
130
130
 
131
131
  // The spinner should be present but we can't easily test for it without mocking
132
- expect(screen.getByText('Create')).toBeInTheDocument();
132
+ expect(screen.getByText('Save')).toBeInTheDocument();
133
133
  });
134
134
  });
135
135
 
@@ -238,16 +238,16 @@ describe('InappAdvanced Component', () => {
238
238
  });
239
239
 
240
240
  describe('Action Buttons', () => {
241
- it('should render Create button in full mode for new templates', () => {
241
+ it('should render Save button in full mode for new templates', () => {
242
242
  renderComponent({
243
243
  ...defaultProps,
244
244
  isFullMode: true,
245
245
  });
246
246
 
247
- expect(screen.getByText('Create')).toBeInTheDocument();
247
+ expect(screen.getByText('Save')).toBeInTheDocument();
248
248
  });
249
249
 
250
- it('should render Update button in edit mode', () => {
250
+ it('should render Save button in edit mode', () => {
251
251
  renderComponent({
252
252
  ...defaultProps,
253
253
  isFullMode: true,
@@ -265,7 +265,7 @@ describe('InappAdvanced Component', () => {
265
265
  },
266
266
  });
267
267
 
268
- expect(screen.getByText('Update')).toBeInTheDocument();
268
+ expect(screen.getByText('Save')).toBeInTheDocument();
269
269
  });
270
270
 
271
271
  it('should render Done button in library mode', () => {
@@ -277,13 +277,13 @@ describe('InappAdvanced Component', () => {
277
277
  expect(screen.getByText('Done')).toBeInTheDocument();
278
278
  });
279
279
 
280
- it('should call appropriate action when Create button is clicked', async () => {
280
+ it('should call appropriate action when Save button is clicked', async () => {
281
281
  renderComponent({
282
282
  ...defaultProps,
283
283
  isFullMode: true,
284
284
  });
285
285
 
286
- const createButton = screen.getByText('Create');
286
+ const createButton = screen.getByText('Save');
287
287
  fireEvent.click(createButton);
288
288
 
289
289
  // Wait for async operations to complete
@@ -325,7 +325,7 @@ describe('InappAdvanced Component', () => {
325
325
  });
326
326
 
327
327
  // Template name should be used in the payload creation
328
- expect(screen.getByText('Create')).toBeInTheDocument();
328
+ expect(screen.getByText('Save')).toBeInTheDocument();
329
329
  });
330
330
 
331
331
  it('should handle setTemplateName callback', () => {
@@ -348,7 +348,7 @@ describe('InappAdvanced Component', () => {
348
348
  });
349
349
 
350
350
  // Component should render without crashing
351
- expect(screen.getByText('Create')).toBeInTheDocument();
351
+ expect(screen.getByText('Save')).toBeInTheDocument();
352
352
  });
353
353
 
354
354
  it('should use account data in payload creation', () => {
@@ -363,7 +363,7 @@ describe('InappAdvanced Component', () => {
363
363
  });
364
364
 
365
365
  // Account data should be used when creating the payload
366
- expect(screen.getByText('Create')).toBeInTheDocument();
366
+ expect(screen.getByText('Save')).toBeInTheDocument();
367
367
  });
368
368
  });
369
369
 
@@ -396,7 +396,7 @@ describe('InappAdvanced Component', () => {
396
396
  });
397
397
 
398
398
  // Should show Update button in edit flow
399
- expect(screen.getByText('Update')).toBeInTheDocument();
399
+ expect(screen.getByText('Save')).toBeInTheDocument();
400
400
  });
401
401
  });
402
402
 
@@ -427,7 +427,7 @@ describe('InappAdvanced Component', () => {
427
427
  beePopupBuilderToken: undefined,
428
428
  });
429
429
 
430
- expect(screen.getByText('Create')).toBeInTheDocument();
430
+ expect(screen.getByText('Save')).toBeInTheDocument();
431
431
  });
432
432
  });
433
433
 
@@ -447,4 +447,28 @@ describe('InappAdvanced Component', () => {
447
447
  expect(container.querySelector('.inapp-footer-lib')).toBeInTheDocument();
448
448
  });
449
449
  });
450
- });
450
+ describe('Full mode keeps the slidebox open after saving', () => {
451
+ it('create: stays open (header switches) and the next save updates the created template', async () => {
452
+ renderComponent({ ...defaultProps, isFullMode: true, params: {} });
453
+
454
+ fireEvent.click(screen.getByText('Save'));
455
+ await waitFor(() => expect(mockActions.createInAppTemplate).toHaveBeenCalledTimes(1), { timeout: 3000 });
456
+ const onCreated = mockActions.createInAppTemplate.mock.calls[0][1];
457
+ act(() => onCreated({ templateId: { _id: 'ia-1' } }));
458
+
459
+ expect(defaultProps.getFormData).toHaveBeenCalledWith({
460
+ _id: 'ia-1', validity: true, keepSlideBoxOpen: true, continuedAsEdit: true,
461
+ });
462
+ expect(defaultProps.onCreateComplete).not.toHaveBeenCalled();
463
+
464
+ fireEvent.click(screen.getByText('Save'));
465
+ await waitFor(() => expect(mockActions.editTemplate).toHaveBeenCalledTimes(1), { timeout: 3000 });
466
+ expect(mockActions.createInAppTemplate).toHaveBeenCalledTimes(1);
467
+ expect(mockActions.editTemplate.mock.calls[0][0]._id).toBe('ia-1');
468
+
469
+ act(() => mockActions.editTemplate.mock.calls[0][1]({ templateId: { _id: 'ia-1' } }));
470
+ expect(defaultProps.getFormData).toHaveBeenLastCalledWith({ validity: true, keepSlideBoxOpen: true });
471
+ expect(defaultProps.onCreateComplete).not.toHaveBeenCalled();
472
+ });
473
+ });
474
+ });
@@ -4,6 +4,7 @@ import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
4
4
  import { createStructuredSelector } from 'reselect';
5
5
  import get from 'lodash/get';
6
6
  import isEmpty from 'lodash/isEmpty';
7
+ import isPlainObject from 'lodash/isPlainObject';
7
8
  import style from './style';
8
9
  import withStyles from '../../../hoc/withStyles';
9
10
  import './_lineCreate.scss';
@@ -22,6 +23,7 @@ import { CREATE, EDIT, TRACK_CREATE_LINE, TRACK_EDIT_LINE } from '../../App/cons
22
23
 
23
24
  import LineWrapper from './Wrapper';
24
25
  import messages from './messages';
26
+ import globalMessages from '../../Cap/messages';
25
27
  import { TEXT, IMAGE, IMAGE_MAP, IMAGE_CAROUSEL, MAX_LINE_WRAPPER_LIMIT, MAX_LINE_WRAPPER_LIMIT_FULL_MODE, DEFAULT_VIDEO_URI, VIDEO, TEMPLATE } from './constants';
26
28
  import { FONT_COLOR_05 } from '@capillarytech/cap-ui-library/styled/variables';
27
29
 
@@ -50,7 +52,6 @@ export const LineContainer = ({
50
52
  Line,
51
53
  className,
52
54
  params,
53
- onCreateComplete,
54
55
  intl: { formatMessage },
55
56
  getFormSubscriptionData,
56
57
  templateData,
@@ -67,6 +68,8 @@ export const LineContainer = ({
67
68
  const initialWrapperContent = [{ type: TEXT, isError: true, isInit: true }];
68
69
  const [lineWrapperContents, updateLineWrapperContent] = useState(initialWrapperContent);
69
70
  const [isEditFlow, checkEditFlow] = useState(false);
71
+ // Full mode: id of the template created in this session — the form stays as is and later saves update it
72
+ const [savedTemplateId, setSavedTemplateId] = useState('');
70
73
  const [isEditLoading, updateEditLoading] = useState(false);
71
74
  const [accountSelectFlow, updateAccountSelectFlow] = useState(false);
72
75
  const [uploadedAssetList, updateUploadedAssetList] = useState({});
@@ -653,7 +656,13 @@ export const LineContainer = ({
653
656
  mode: CREATE,
654
657
  timeTaken,
655
658
  }, formatFSPayload(lineWrapperContents)));
656
- onCreateComplete();
659
+ // Full mode stays open and continues in place as an edit.
660
+ const savedResponse = resp?.templateId;
661
+ const newTemplateId = (isPlainObject(savedResponse) ? savedResponse._id : savedResponse) || '';
662
+ setSavedTemplateId(newTemplateId);
663
+ getFormSubscriptionData({
664
+ _id: newTemplateId, validity: true, keepSlideBoxOpen: true, continuedAsEdit: true,
665
+ });
657
666
  }
658
667
  }
659
668
  );
@@ -701,7 +710,7 @@ export const LineContainer = ({
701
710
  actions.editTemplate(
702
711
  {
703
712
  ...formatSubmitPayload(lineWrapperContents),
704
- _id: params.id,
713
+ _id: params?.id || savedTemplateId,
705
714
  },
706
715
  (resp, errorMessage) => {
707
716
  editCallback(errorMessage);
@@ -713,20 +722,20 @@ export const LineContainer = ({
713
722
  label: getLineType(lineWrapperContents),
714
723
  });
715
724
  gtmPush('creativeDetails', Object.assign({
716
- id: resp.templateId._id,
725
+ id: resp?.templateId?._id || resp?.templateId,
717
726
  channel: LINE,
718
727
  mode: EDIT,
719
728
  timeTaken,
720
729
  }, formatFSPayload(lineWrapperContents)));
721
-
722
- onCreateComplete();
730
+
731
+ getFormSubscriptionData({ validity: true, keepSlideBoxOpen: true }); // stays open; host refreshes its list
723
732
  }
724
733
  });
725
734
  }
726
735
 
727
736
  const onDoneCallback = () => {
728
737
  if (isFullMode) {
729
- if (isEditFlow) {
738
+ if (isEditFlow || savedTemplateId) {
730
739
  return onEditLine;
731
740
  }
732
741
  return onCreateLine;
@@ -845,7 +854,8 @@ export const LineContainer = ({
845
854
  disabled={isDoneDisabled()}
846
855
  className="create-msg"
847
856
  >
848
- <FormattedMessage {...messages.doneButtonLabel} />
857
+ {/* Full mode saves without closing, so the button reads "Save" for create and edit */}
858
+ <FormattedMessage {...(isFullMode ? globalMessages.save : messages.doneButtonLabel)} />
849
859
  </CapButton>
850
860
  {
851
861
  !isFullMode