@capillarytech/creatives-library 8.0.288 → 8.0.290-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.
Files changed (52) hide show
  1. package/constants/unified.js +0 -1
  2. package/initialState.js +0 -2
  3. package/package.json +1 -1
  4. package/utils/common.js +5 -8
  5. package/utils/commonUtils.js +4 -85
  6. package/utils/tagValidations.js +84 -222
  7. package/utils/tests/commonUtil.test.js +461 -118
  8. package/utils/tests/tagValidations.test.js +280 -358
  9. package/v2Components/ErrorInfoNote/index.js +2 -5
  10. package/v2Components/FormBuilder/index.js +78 -161
  11. package/v2Components/FormBuilder/messages.js +0 -8
  12. package/v2Components/HtmlEditor/HTMLEditor.js +0 -5
  13. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
  14. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +0 -15
  15. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +1 -2
  16. package/v2Containers/Cap/mockData.js +0 -14
  17. package/v2Containers/Cap/reducer.js +3 -55
  18. package/v2Containers/Cap/tests/reducer.test.js +0 -102
  19. package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -1
  20. package/v2Containers/CreativesContainer/index.js +19 -6
  21. package/v2Containers/Email/index.js +1 -5
  22. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +10 -62
  23. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +12 -115
  24. package/v2Containers/FTP/index.js +2 -51
  25. package/v2Containers/FTP/messages.js +0 -4
  26. package/v2Containers/InApp/index.js +1 -96
  27. package/v2Containers/InApp/tests/index.test.js +17 -6
  28. package/v2Containers/InappAdvance/index.js +2 -103
  29. package/v2Containers/Line/Container/Text/index.js +0 -1
  30. package/v2Containers/MobilePush/Create/index.js +6 -16
  31. package/v2Containers/MobilePush/Edit/index.js +6 -16
  32. package/v2Containers/MobilePushNew/index.js +2 -33
  33. package/v2Containers/Rcs/index.js +12 -37
  34. package/v2Containers/Sms/Create/index.js +31 -3
  35. package/v2Containers/Sms/Create/messages.js +4 -0
  36. package/v2Containers/Sms/Edit/index.js +29 -3
  37. package/v2Containers/Sms/commonMethods.js +6 -6
  38. package/v2Containers/SmsTrai/Edit/index.js +6 -47
  39. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  40. package/v2Containers/Templates/reducer.js +3 -1
  41. package/v2Containers/Templates/tests/reducer.test.js +12 -0
  42. package/v2Containers/Viber/index.js +0 -1
  43. package/v2Containers/WebPush/Create/components/BrandIconSection.test.js +264 -0
  44. package/v2Containers/WebPush/Create/components/__snapshots__/BrandIconSection.test.js.snap +187 -0
  45. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
  46. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
  47. package/v2Containers/WebPush/Create/index.js +2 -2
  48. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +269 -0
  49. package/v2Containers/WebPush/Create/utils/validation.js +17 -2
  50. package/v2Containers/WebPush/Create/utils/validation.test.js +0 -24
  51. package/v2Containers/Whatsapp/index.js +9 -17
  52. package/v2Containers/Zalo/index.js +3 -11
@@ -50,9 +50,7 @@ import injectReducer from '../../utils/injectReducer';
50
50
  import v2InAppReducer from '../InApp/reducer';
51
51
  import { v2InAppSagas } from '../InApp/sagas';
52
52
  import injectSaga from '../../utils/injectSaga';
53
- import { validateTags } from "../../utils/tagValidations";
54
53
  import { validateInAppContent } from "../../utils/commonUtils";
55
- import { hasLiquidSupportFeature } from "../../utils/common";
56
54
  import formBuilderMessages from "../../v2Components/FormBuilder/messages";
57
55
  import { getSingleTab, hasAnyErrors } from "../InApp/utils";
58
56
  import ErrorInfoNote from "../../v2Components/ErrorInfoNote";
@@ -817,11 +815,9 @@ export const InappAdvanced = (props) => {
817
815
  const latestHtmlValues = await saveAllBeeInstances();
818
816
  const payload = createPayload(latestHtmlValues);
819
817
 
820
- // Validate the INAPP content
821
- const isLiquidFlow = hasLiquidSupportFeature();
822
818
  // Skip validation if no tags are available (e.g., in tests or when tags haven't loaded)
823
819
  const hasTags = tags && tags.length > 0;
824
- if (isLiquidFlow && hasTags) {
820
+ if (hasTags) {
825
821
  validateInAppContent(payload, {
826
822
  currentTab: panes === ANDROID ? 1 : 2, // Convert ANDROID/IOS to tab numbers
827
823
  onError,
@@ -829,10 +825,6 @@ export const InappAdvanced = (props) => {
829
825
  getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
830
826
  formatMessage,
831
827
  messages: formBuilderMessages,
832
- tagLookupMap: metaEntities?.tagLookupMap || {},
833
- eventContextTags: metaEntities?.eventContextTags || [],
834
- isLiquidFlow,
835
- forwardedTags: {},
836
828
  skipTags: (tag) => {
837
829
  // Skip certain tags if needed
838
830
  const skipRegexes = [
@@ -847,92 +839,7 @@ export const InappAdvanced = (props) => {
847
839
  },
848
840
  singleTab: getSingleTab(accountData),
849
841
  });
850
- } else if (hasTags) {
851
- // For non-liquid flow, validate tags using validateTags (only if tags are available)
852
- const androidContent = latestHtmlValues?.android || (androidBeeHtml?.value || (typeof androidBeeHtml === 'string' ? androidBeeHtml : ''));
853
- const iosContent = latestHtmlValues?.ios || (iosBeeHtml?.value || (typeof iosBeeHtml === 'string' ? iosBeeHtml : ''));
854
-
855
- const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
856
- const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
857
-
858
- let hasErrors = false;
859
- const newErrors = {
860
- STANDARD_ERROR_MSG: {
861
- ANDROID: [],
862
- IOS: [],
863
- GENERIC: [],
864
- },
865
- LIQUID_ERROR_MSG: {
866
- ANDROID: [],
867
- IOS: [],
868
- GENERIC: [],
869
- },
870
- };
871
-
872
- // Validate Android content
873
- if (androidSupported && androidContent && androidContent?.trim() !== '') {
874
- const validationResponse = validateTags({
875
- content: androidContent,
876
- tagsParam: tags,
877
- injectedTagsParams: injectedTags || {},
878
- location,
879
- tagModule: getDefaultTags,
880
- eventContextTags: metaEntities?.eventContextTags || [],
881
- isFullMode,
882
- }) || {};
883
-
884
- if (validationResponse?.unsupportedTags?.length > 0) {
885
- hasErrors = true;
886
- newErrors.LIQUID_ERROR_MSG.ANDROID.push(
887
- formatMessage(globalMessages.unsupportedTagsValidationError, {
888
- unsupportedTags: validationResponse.unsupportedTags.join(", "),
889
- })
890
- );
891
- }
892
- if (validationResponse?.isBraceError) {
893
- hasErrors = true;
894
- newErrors.LIQUID_ERROR_MSG.ANDROID.push(
895
- formatMessage(globalMessages.unbalanacedCurlyBraces)
896
- );
897
- }
898
- }
899
-
900
- // Validate iOS content
901
- if (iosSupported && iosContent && iosContent?.trim() !== '') {
902
- const validationResponse = validateTags({
903
- content: iosContent,
904
- tagsParam: tags,
905
- injectedTagsParams: injectedTags || {},
906
- location,
907
- tagModule: getDefaultTags,
908
- eventContextTags: metaEntities?.eventContextTags || [],
909
- isFullMode,
910
- }) || {};
911
-
912
- if (validationResponse?.unsupportedTags?.length > 0) {
913
- hasErrors = true;
914
- newErrors.LIQUID_ERROR_MSG.IOS.push(
915
- formatMessage(globalMessages.unsupportedTagsValidationError, {
916
- unsupportedTags: validationResponse.unsupportedTags.join(", "),
917
- })
918
- );
919
- }
920
- if (validationResponse?.isBraceError) {
921
- hasErrors = true;
922
- newErrors.LIQUID_ERROR_MSG.IOS.push(
923
- formatMessage(globalMessages.unbalanacedCurlyBraces)
924
- );
925
- }
926
- }
927
-
928
- if (hasErrors) {
929
- setErrorMessage(newErrors);
930
- } else {
931
- // No errors, proceed with submission
932
- onSuccess();
933
- }
934
842
  } else {
935
- // No tags available, skip validation and proceed directly
936
843
  onSuccess();
937
844
  }
938
845
  };
@@ -1038,15 +945,7 @@ export const InappAdvanced = (props) => {
1038
945
  )}
1039
946
  <CapButton
1040
947
  onClick={async () => {
1041
- const isLiquidFlow = hasLiquidSupportFeature();
1042
- const hasTags = tags && tags?.length > 0;
1043
- if (isLiquidFlow || hasTags) {
1044
- // Use validation middleware for tag validation
1045
- await liquidMiddleWare();
1046
- } else {
1047
- // No validation needed, proceed directly
1048
- await onDoneCallback();
1049
- }
948
+ await liquidMiddleWare();
1050
949
  }}
1051
950
  disabled={isDisableDone()}
1052
951
  className="inapp-create-btn"
@@ -137,7 +137,6 @@ export const LineText = ({
137
137
  const { valid, isBraceError } = validateTags({
138
138
  content: value,
139
139
  tagsParam: tags,
140
- injectedTagsParams: injectedTags,
141
140
  location,
142
141
  tagModule: 'outbound',
143
142
  isFullMode,
@@ -678,31 +678,21 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
678
678
  }
679
679
  showError = () => {
680
680
  const {intl} = this.props;
681
- const {errorData} = this.state;
681
+ const {errorData, schema} = this.state;
682
682
  const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
683
683
  if (!isEmpty(this.state.formData) && !this.state.isFormValid) {
684
684
  let tab = this.state.currentTab;
685
- const isAndroidInvalid = Object.values(errorData[0]).includes(true);
686
- const isIosInvalid = Object.values(errorData[1]).includes(true);
687
- let isTagErrorExist = false;
685
+ const isAndroidInvalid = Object.values(errorData[0] || {}).includes(true);
686
+ const isIosInvalid = Object.values(errorData[1] || {}).includes(true);
687
+ const isIosTabVisible = get(schema, 'containers[0].panes[1].isSupported', true) !== false;
688
688
  if (isAndroidInvalid) {
689
689
  tab = 1;
690
690
  errorMessage.description = intl.formatMessage(messages.invalidAndroidMessage);
691
- const invalidTags = errorData[0]['invalid-tags'];
692
- if (!isEmpty(invalidTags)) {
693
- isTagErrorExist = true;
694
- errorMessage.description = `${intl.formatMessage(messages.invalidAndroidMessage)} ${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
695
- }
696
- } else if (isIosInvalid) {
691
+ } else if (isIosInvalid && isIosTabVisible) {
697
692
  tab = 2;
698
693
  errorMessage.description = intl.formatMessage(messages.invalidIosMessage);
699
- const invalidTags = errorData[1]['invalid-tags'];
700
- if (!isEmpty(invalidTags)) {
701
- isTagErrorExist = true;
702
- errorMessage.description = `${intl.formatMessage(messages.invalidIosMessage)} ${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
703
- }
704
694
  }
705
- if (tab !== this.state.currentTab || isTagErrorExist) {
695
+ if (tab !== this.state.currentTab) {
706
696
  CapNotification.error(errorMessage);
707
697
  }
708
698
  }
@@ -705,32 +705,22 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
705
705
  // eslint-disable-next-line react/sort-comp
706
706
  showError = () => {
707
707
  const {intl} = this.props;
708
- const {errorData} = this.state;
708
+ const {errorData, schema} = this.state;
709
709
  const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
710
- let isTagErrorExist = false;
711
710
  if (!_.isEmpty(this.state.formData) && !this.state.isFormValid) {
712
711
  let tab = this.state.currentTab;
713
- const isAndroidInvalid = Object.values(errorData[0]).includes(true);
714
- const isIosInvalid = Object.values(errorData[1]).includes(true);
712
+ const isAndroidInvalid = Object.values(errorData[0] || {}).includes(true);
713
+ const isIosInvalid = Object.values(errorData[1] || {}).includes(true);
714
+ const isIosTabVisible = get(schema, 'containers[0].panes[1].isSupported', true) !== false;
715
715
  if (isAndroidInvalid) {
716
716
  tab = 1;
717
717
  errorMessage.description = intl.formatMessage(messages.invalidAndroidMessage);
718
- const invalidTags = errorData[0]['invalid-tags'];
719
- if (!_.isEmpty(invalidTags)) {
720
- isTagErrorExist = true;
721
- errorMessage.description = `${intl.formatMessage(messages.invalidAndroidMessage)} ${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
722
- }
723
- } else if (isIosInvalid) {
718
+ } else if (isIosInvalid && isIosTabVisible) {
724
719
  tab = 2;
725
720
  errorMessage.description = intl.formatMessage(messages.invalidIosMessage);
726
- const invalidTags = errorData[1]['invalid-tags'];
727
- if (!_.isEmpty(invalidTags)) {
728
- isTagErrorExist = true;
729
- errorMessage.description = `${intl.formatMessage(messages.invalidIosMessage)} ${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
730
- }
731
721
  }
732
722
 
733
- if (tab !== this.state.currentTab || isTagErrorExist) {
723
+ if (tab !== this.state.currentTab) {
734
724
  CapNotification.error(errorMessage);
735
725
  }
736
726
  }
@@ -77,7 +77,6 @@ import { getContent } from "../MobilePush/commonMethods";
77
77
  import { getMessageObject } from "../../utils/messageUtils";
78
78
  import { gtmPush } from "../../utils/gtmTrackers";
79
79
  import mobilePushReducer from "./reducer";
80
- import { hasLiquidSupportFeature } from "../../utils/common";
81
80
  import formBuilderMessages from "../../v2Components/FormBuilder/messages";
82
81
  import { validateMobilePushContent } from "../../utils/commonUtils";
83
82
  import { getSingleTab } from "../InApp/utils";
@@ -803,10 +802,9 @@ const MobilePushNew = ({
803
802
  (value) => {
804
803
  let errorTemplateDescMessage = "";
805
804
 
806
- const { unsupportedTags, isBraceError } = validateTags({
805
+ const { isBraceError } = validateTags({
807
806
  content: value,
808
807
  tagsParam: tags,
809
- injectedTagsParams: injectedTags,
810
808
  location,
811
809
  tagModule: getDefaultTags,
812
810
  isFullMode,
@@ -816,14 +814,6 @@ const MobilePushNew = ({
816
814
  messages.emptyTemplateDescErrorMessage
817
815
  );
818
816
  }
819
- if (unsupportedTags?.length > 0) {
820
- errorTemplateDescMessage = formatMessage(
821
- globalMessages.unsupportedTagsValidationError,
822
- {
823
- unsupportedTags,
824
- }
825
- );
826
- }
827
817
  if (isBraceError) {
828
818
  errorTemplateDescMessage = formatMessage(
829
819
  globalMessages.unbalanacedCurlyBraces
@@ -2659,20 +2649,6 @@ const MobilePushNew = ({
2659
2649
  getLiquidTags: globalActionsProps.getLiquidTags,
2660
2650
  formatMessage,
2661
2651
  messages: formBuilderMessages,
2662
- tagLookupMap: metaEntities?.tagLookupMap || {},
2663
- eventContextTags: metaEntities?.eventContextTags || [],
2664
- isLiquidFlow: hasLiquidSupportFeature(),
2665
- forwardedTags: {},
2666
- skipTags: (tag) => {
2667
- const skipRegexes = [
2668
- /dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
2669
- /unsubscribe\(#[a-zA-Z\d]{6}\)/,
2670
- /Link_to_[a-zA-z]/,
2671
- /SURVEY.*\.TOKEN/,
2672
- /^[A-Za-z].*\([a-zA-Z\d]*\)/,
2673
- ];
2674
- return skipRegexes.some((regex) => regex.test(tag));
2675
- },
2676
2652
  singleTab: getSingleTab(accountData),
2677
2653
  });
2678
2654
  }, [
@@ -2681,12 +2657,9 @@ const MobilePushNew = ({
2681
2657
  activeTab,
2682
2658
  globalActionsProps,
2683
2659
  formatMessage,
2684
- metaEntities,
2685
2660
  accountData,
2686
2661
  ]);
2687
2662
 
2688
- const isLiquidFlow = hasLiquidSupportFeature();
2689
-
2690
2663
  useEffect(() => {
2691
2664
  // Always map to { label } for both platforms
2692
2665
  const newButtons = Array.isArray(ctaData)
@@ -3083,11 +3056,7 @@ const MobilePushNew = ({
3083
3056
  <CapButton
3084
3057
  type="primary"
3085
3058
  onClick={() => {
3086
- if (isLiquidFlow) {
3087
- liquidMiddleWare();
3088
- } else {
3089
- handleSave();
3090
- }
3059
+ liquidMiddleWare();
3091
3060
  }}
3092
3061
  className="save-button"
3093
3062
  disabled={isSaveDisabled}
@@ -394,23 +394,15 @@ export const Rcs = (props) => {
394
394
  const validationResponse =
395
395
  validateTags({
396
396
  content: contentForValidation,
397
- tagsParam: tags,
398
- injectedTagsParams: injectedTags,
399
- location,
400
- tagModule: getDefaultTags,
401
- eventContextTags,
402
- isFullMode,
403
- }) || {};
404
- const unsupportedTagsLengthCheck =
405
- validationResponse?.unsupportedTags?.length > 0;
406
- const errorMsg =
407
- (unsupportedTagsLengthCheck &&
408
- formatMessage(globalMessages.unsupportedTagsValidationError, {
409
- unsupportedTags: validationResponse.unsupportedTags,
410
- })) ||
411
- (validationResponse.isBraceError &&
412
- formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
413
- false;
397
+ tagsParam: tags,
398
+ location,
399
+ tagModule: getDefaultTags,
400
+ isFullMode,
401
+ }) || {};
402
+ const errorMsg =
403
+ (validationResponse?.isBraceError &&
404
+ formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
405
+ false;
414
406
  if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
415
407
  if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
416
408
  };
@@ -835,10 +827,9 @@ export const Rcs = (props) => {
835
827
 
836
828
  const templateDescErrorHandler = (value) => {
837
829
  let errorMessage = false;
838
- const { unsupportedTags, isBraceError } = validateTags({
830
+ const { isBraceError } = validateTags({
839
831
  content: value,
840
832
  tagsParam: tags,
841
- injectedTagsParams: injectedTags,
842
833
  location,
843
834
  tagModule: getDefaultTags,
844
835
  isFullMode,
@@ -868,26 +859,10 @@ export const Rcs = (props) => {
868
859
  };
869
860
 
870
861
  const fallbackMessageErrorHandler = (value) => {
871
- let errorMessage = false;
872
- const { unsupportedTags } = validateTags({
873
- content: value,
874
- tagsParam: tags,
875
- injectedTagsParams: injectedTags,
876
- location,
877
- tagModule: getDefaultTags,
878
- isFullMode,
879
- }) || {};
880
862
  if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
881
- errorMessage = formatMessage(messages.fallbackMsgLenError);
882
- } else if (unsupportedTags?.length > 0) {
883
- errorMessage = formatMessage(
884
- globalMessages.unsupportedTagsValidationError,
885
- {
886
- unsupportedTags,
887
- },
888
- );
863
+ return formatMessage(messages.fallbackMsgLenError);
889
864
  }
890
- return errorMessage;
865
+ return false;
891
866
  };
892
867
 
893
868
  // Check for forbidden characters: square brackets [] and single curly braces {}
@@ -51,6 +51,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
51
51
  modalContent: {title: "Alert", body: "Do you really want to delete this version?", type: 'confirm', id: 'sms-version-modal'},
52
52
  showTestAndPreviewSlidebox: false,
53
53
  isTestAndPreviewMode: false,
54
+ pendingGetFormData: false,
54
55
  };
55
56
  this.saveFormData = this.saveFormData.bind(this);
56
57
  this.onFormDataChange = this.onFormDataChange.bind(this);
@@ -140,8 +141,16 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
140
141
  }
141
142
 
142
143
  componentWillReceiveProps(nextProps) {
143
- if (nextProps.location.query.module === 'library' && nextProps.isGetFormData) {
144
- nextProps.getFormSubscriptionData(this.getFormData());
144
+ if (!nextProps.isFullMode && nextProps.isGetFormData) {
145
+ // Trigger validation first; response will be sent in setFormValidity callback so pasted content is validated
146
+ this.setState({ startValidation: true, pendingGetFormData: true });
147
+ // Fallback: if FormBuilder never calls onFormValidityChange (e.g. early return), still respond to parent
148
+ this.pendingGetFormDataTimeout = setTimeout(() => {
149
+ if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
150
+ this.props.getFormSubscriptionData(this.getFormData());
151
+ this.setState({ pendingGetFormData: false, startValidation: false });
152
+ }
153
+ }, 300);
145
154
  } else if (nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
146
155
  this.startValidation();
147
156
  }
@@ -171,6 +180,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
171
180
  }
172
181
 
173
182
  componentWillUnmount() {
183
+ if (this.pendingGetFormDataTimeout) {
184
+ clearTimeout(this.pendingGetFormDataTimeout);
185
+ }
174
186
  if (this.props.setIsLoadingContent) {
175
187
  this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
176
188
  }
@@ -349,7 +361,16 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
349
361
  }
350
362
 
351
363
  setFormValidity(isFormValid, errorData) {
352
- this.setState({isFormValid, errorData});
364
+ this.setState({ isFormValid, errorData }, () => {
365
+ if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
366
+ if (this.pendingGetFormDataTimeout) {
367
+ clearTimeout(this.pendingGetFormDataTimeout);
368
+ this.pendingGetFormDataTimeout = null;
369
+ }
370
+ this.props.getFormSubscriptionData(this.getFormData());
371
+ this.setState({ pendingGetFormData: false, startValidation: false });
372
+ }
373
+ });
353
374
  }
354
375
 
355
376
  injectMessages(elem) {
@@ -970,6 +991,13 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
970
991
  }
971
992
 
972
993
  saveFormData() {
994
+ // In library mode the template is submitted via getFormSubscriptionData (triggered by startValidation →
995
+ // onFormValidityChange → setFormValidity). Calling createTemplate API here would set
996
+ // createTemplateInProgress: true in redux and, because the slidebox closes before the API responds,
997
+ // that flag would never be reset – causing the spinner to be stuck on the next open.
998
+ if (!this.props.isFullMode) {
999
+ return;
1000
+ }
973
1001
  //Logic to save in db etc
974
1002
  const formData = _.cloneDeep(this.state.formData);
975
1003
  const obj = {};
@@ -90,6 +90,10 @@ export default defineMessages({
90
90
  id: 'creatives.containersV2.Create.validationError',
91
91
  defaultMessage: 'Validation error',
92
92
  },
93
+ "unbalancedCurlyBraces": {
94
+ id: 'creatives.containersV2.Create.unbalancedCurlyBraces',
95
+ defaultMessage: 'Please close all curly braces in the message.',
96
+ },
93
97
  "smsTemplateCreatedSuccess": {
94
98
  id: 'creatives.containersV2.Create.smsTemplateCreatedSuccess',
95
99
  defaultMessage: 'SMS Template Created Successfully',
@@ -52,6 +52,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
52
52
  modalContent: {title: "Alert", body: "Do you really want to delete this version?", type: 'confirm', id: 'sms-version-modal'},
53
53
  showTestAndPreviewSlidebox: false,
54
54
  isTestAndPreviewMode: false,
55
+ pendingGetFormData: false,
55
56
  };
56
57
  this.saveFormData = this.saveFormData.bind(this);
57
58
  this.onFormDataChange = this.onFormDataChange.bind(this);
@@ -134,8 +135,14 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
134
135
  }
135
136
 
136
137
  componentWillReceiveProps(nextProps) {
137
- if (nextProps.location.query.module === 'library' && nextProps.isGetFormData) {
138
- nextProps.getFormSubscriptionData(this.getFormData());
138
+ if (!nextProps.isFullMode && nextProps.isGetFormData) {
139
+ this.setState({ startValidation: true, pendingGetFormData: true });
140
+ this.pendingGetFormDataTimeout = setTimeout(() => {
141
+ if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
142
+ this.props.getFormSubscriptionData(this.getFormData());
143
+ this.setState({ pendingGetFormData: false, startValidation: false });
144
+ }
145
+ }, 300);
139
146
  }
140
147
  if ( nextProps.location.query.module === 'library' && nextProps.subscriptionTemplateDetails && nextProps.subscriptionTemplateDetails.name && _.isEmpty(this.state.editData) && !_.isEmpty(this.state.schema)) {
141
148
  this.setEditState(nextProps.subscriptionTemplateDetails);
@@ -189,6 +196,9 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
189
196
  }
190
197
 
191
198
  componentWillUnmount() {
199
+ if (this.pendingGetFormDataTimeout) {
200
+ clearTimeout(this.pendingGetFormDataTimeout);
201
+ }
192
202
  if (this.props.setIsLoadingContent) {
193
203
  this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
194
204
  }
@@ -317,7 +327,16 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
317
327
  }
318
328
 
319
329
  setFormValidity(isFormValid, errorData) {
320
- this.setState({isFormValid, errorData});
330
+ this.setState({ isFormValid, errorData }, () => {
331
+ if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
332
+ if (this.pendingGetFormDataTimeout) {
333
+ clearTimeout(this.pendingGetFormDataTimeout);
334
+ this.pendingGetFormDataTimeout = null;
335
+ }
336
+ this.props.getFormSubscriptionData(this.getFormData());
337
+ this.setState({ pendingGetFormData: false, startValidation: false });
338
+ }
339
+ });
321
340
  }
322
341
 
323
342
  getFormData(e, value) {
@@ -923,6 +942,13 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
923
942
  this.setState({startValidation: false});
924
943
  }
925
944
  saveFormData() {
945
+ // In library mode the template is submitted via getFormSubscriptionData, not editTemplate API.
946
+ // Calling editTemplate here would set editTemplateInProgress: true in redux and, because the
947
+ // slidebox closes before the API responds, that flag would never be reset – causing the spinner
948
+ // to be stuck on the next open.
949
+ if (!this.props.isFullMode) {
950
+ return;
951
+ }
926
952
  //Logic to save in db etc
927
953
  //saveFormData gets called only when validation result is true
928
954
 
@@ -1,18 +1,18 @@
1
1
  import isEmpty from 'lodash/isEmpty';
2
- import { CapNotification } from '@capillarytech/cap-ui-library';
2
+ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
3
3
  import messages from './Create/messages';
4
4
  export function showError() {
5
5
  const {intl} = this.props;
6
6
  const {errorData} = this.state;
7
7
  const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
8
8
  if (!isEmpty(this.state.formData) && !this.state.isFormValid) {
9
- const isSmsInvalid = Object.values(errorData[0]).includes(true);
9
+ const err0 = errorData[0] || {};
10
+ const isSmsInvalid = Object.values(err0).includes(true);
11
+ const isBraceError = Boolean(err0['bracket-error']);
10
12
  if (isSmsInvalid) {
11
- const invalidTags = errorData[0]['invalid-tags'];
12
- if (!isEmpty(invalidTags)) {
13
- errorMessage.description = `${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
14
- }
15
13
  CapNotification.error(errorMessage);
14
+ } else if (isBraceError) {
15
+ // Do not trigger toast for this path; footer error is the reliable UX in SMS library flow.
16
16
  }
17
17
  }
18
18
  }