@capillarytech/creatives-library 8.0.286 → 8.0.287-alpha.1

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 (49) 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 +2 -83
  6. package/utils/tagValidations.js +84 -222
  7. package/utils/tests/commonUtil.test.js +147 -118
  8. package/utils/tests/tagValidations.test.js +280 -358
  9. package/v2Components/ErrorInfoNote/index.js +2 -5
  10. package/v2Components/FormBuilder/index.js +68 -160
  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/index.js +4 -4
  20. package/v2Containers/Email/index.js +1 -5
  21. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +10 -62
  22. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +12 -115
  23. package/v2Containers/FTP/index.js +2 -51
  24. package/v2Containers/FTP/messages.js +0 -4
  25. package/v2Containers/InApp/index.js +1 -96
  26. package/v2Containers/InApp/tests/index.test.js +17 -6
  27. package/v2Containers/InappAdvance/index.js +2 -103
  28. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -24
  29. package/v2Containers/Line/Container/Text/index.js +0 -1
  30. package/v2Containers/MobilePushNew/index.js +2 -33
  31. package/v2Containers/Rcs/index.js +12 -37
  32. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +4 -18
  33. package/v2Containers/Sms/Create/index.js +30 -2
  34. package/v2Containers/Sms/Create/messages.js +4 -0
  35. package/v2Containers/Sms/Edit/index.js +28 -2
  36. package/v2Containers/Sms/commonMethods.js +7 -2
  37. package/v2Containers/SmsTrai/Create/index.scss +1 -1
  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/Viber/index.js +0 -1
  41. package/v2Containers/Viber/index.scss +1 -1
  42. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
  43. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
  44. package/v2Containers/WebPush/Create/index.js +2 -2
  45. package/v2Containers/WebPush/Create/utils/validation.js +18 -9
  46. package/v2Containers/WebPush/Create/utils/validation.test.js +0 -24
  47. package/v2Containers/Whatsapp/index.js +9 -17
  48. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +248 -624
  49. 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"
@@ -3117,7 +3117,6 @@ new message content.",
3117
3117
  },
3118
3118
  ]
3119
3119
  }
3120
- showTruncatedTooltip={false}
3121
3120
  size="large"
3122
3121
  style={
3123
3122
  Object {
@@ -3136,7 +3135,6 @@ new message content.",
3136
3135
  />
3137
3136
  }
3138
3137
  onChange={[Function]}
3139
- onDropdownVisibleChange={[Function]}
3140
3138
  removeIcon={
3141
3139
  <CapIcon
3142
3140
  size="s"
@@ -3201,7 +3199,6 @@ new message content.",
3201
3199
  onBlur={[Function]}
3202
3200
  onChange={[Function]}
3203
3201
  onDeselect={[Function]}
3204
- onDropdownVisibleChange={[Function]}
3205
3202
  onFocus={[Function]}
3206
3203
  onInputKeyDown={[Function]}
3207
3204
  onSearch={[Function]}
@@ -3440,11 +3437,7 @@ new message content.",
3440
3437
  }
3441
3438
  }
3442
3439
  title=""
3443
- >
3444
- <div
3445
- className="cap-select-option-tooltip"
3446
- />
3447
- </div>
3440
+ />
3448
3441
  </div>
3449
3442
  <span
3450
3443
  className="ant-select-arrow"
@@ -6947,7 +6940,6 @@ new message content.",
6947
6940
  },
6948
6941
  ]
6949
6942
  }
6950
- showTruncatedTooltip={false}
6951
6943
  size="large"
6952
6944
  style={
6953
6945
  Object {
@@ -6966,7 +6958,6 @@ new message content.",
6966
6958
  />
6967
6959
  }
6968
6960
  onChange={[Function]}
6969
- onDropdownVisibleChange={[Function]}
6970
6961
  removeIcon={
6971
6962
  <CapIcon
6972
6963
  size="s"
@@ -7031,7 +7022,6 @@ new message content.",
7031
7022
  onBlur={[Function]}
7032
7023
  onChange={[Function]}
7033
7024
  onDeselect={[Function]}
7034
- onDropdownVisibleChange={[Function]}
7035
7025
  onFocus={[Function]}
7036
7026
  onInputKeyDown={[Function]}
7037
7027
  onSearch={[Function]}
@@ -7270,11 +7260,7 @@ new message content.",
7270
7260
  }
7271
7261
  }
7272
7262
  title=""
7273
- >
7274
- <div
7275
- className="cap-select-option-tooltip"
7276
- />
7277
- </div>
7263
+ />
7278
7264
  </div>
7279
7265
  <span
7280
7266
  className="ant-select-arrow"
@@ -10721,7 +10707,6 @@ new message content.",
10721
10707
  },
10722
10708
  ]
10723
10709
  }
10724
- showTruncatedTooltip={false}
10725
10710
  size="large"
10726
10711
  style={
10727
10712
  Object {
@@ -10740,7 +10725,6 @@ new message content.",
10740
10725
  />
10741
10726
  }
10742
10727
  onChange={[Function]}
10743
- onDropdownVisibleChange={[Function]}
10744
10728
  removeIcon={
10745
10729
  <CapIcon
10746
10730
  size="s"
@@ -10805,7 +10789,6 @@ new message content.",
10805
10789
  onBlur={[Function]}
10806
10790
  onChange={[Function]}
10807
10791
  onDeselect={[Function]}
10808
- onDropdownVisibleChange={[Function]}
10809
10792
  onFocus={[Function]}
10810
10793
  onInputKeyDown={[Function]}
10811
10794
  onSearch={[Function]}
@@ -11044,11 +11027,7 @@ new message content.",
11044
11027
  }
11045
11028
  }
11046
11029
  title=""
11047
- >
11048
- <div
11049
- className="cap-select-option-tooltip"
11050
- />
11051
- </div>
11030
+ />
11052
11031
  </div>
11053
11032
  <span
11054
11033
  className="ant-select-arrow"
@@ -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,
@@ -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";
@@ -799,10 +798,9 @@ const MobilePushNew = ({
799
798
  (value) => {
800
799
  let errorTemplateDescMessage = "";
801
800
 
802
- const { unsupportedTags, isBraceError } = validateTags({
801
+ const { isBraceError } = validateTags({
803
802
  content: value,
804
803
  tagsParam: tags,
805
- injectedTagsParams: injectedTags,
806
804
  location,
807
805
  tagModule: getDefaultTags,
808
806
  isFullMode,
@@ -812,14 +810,6 @@ const MobilePushNew = ({
812
810
  messages.emptyTemplateDescErrorMessage
813
811
  );
814
812
  }
815
- if (unsupportedTags?.length > 0) {
816
- errorTemplateDescMessage = formatMessage(
817
- globalMessages.unsupportedTagsValidationError,
818
- {
819
- unsupportedTags,
820
- }
821
- );
822
- }
823
813
  if (isBraceError) {
824
814
  errorTemplateDescMessage = formatMessage(
825
815
  globalMessages.unbalanacedCurlyBraces
@@ -2606,20 +2596,6 @@ const MobilePushNew = ({
2606
2596
  getLiquidTags: globalActionsProps.getLiquidTags,
2607
2597
  formatMessage,
2608
2598
  messages: formBuilderMessages,
2609
- tagLookupMap: metaEntities?.tagLookupMap || {},
2610
- eventContextTags: metaEntities?.eventContextTags || [],
2611
- isLiquidFlow: hasLiquidSupportFeature(),
2612
- forwardedTags: {},
2613
- skipTags: (tag) => {
2614
- const skipRegexes = [
2615
- /dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
2616
- /unsubscribe\(#[a-zA-Z\d]{6}\)/,
2617
- /Link_to_[a-zA-z]/,
2618
- /SURVEY.*\.TOKEN/,
2619
- /^[A-Za-z].*\([a-zA-Z\d]*\)/,
2620
- ];
2621
- return skipRegexes.some((regex) => regex.test(tag));
2622
- },
2623
2599
  singleTab: getSingleTab(accountData),
2624
2600
  });
2625
2601
  }, [
@@ -2628,12 +2604,9 @@ const MobilePushNew = ({
2628
2604
  activeTab,
2629
2605
  globalActionsProps,
2630
2606
  formatMessage,
2631
- metaEntities,
2632
2607
  accountData,
2633
2608
  ]);
2634
2609
 
2635
- const isLiquidFlow = hasLiquidSupportFeature();
2636
-
2637
2610
  useEffect(() => {
2638
2611
  // Always map to { label } for both platforms
2639
2612
  const newButtons = Array.isArray(ctaData)
@@ -3030,11 +3003,7 @@ const MobilePushNew = ({
3030
3003
  <CapButton
3031
3004
  type="primary"
3032
3005
  onClick={() => {
3033
- if (isLiquidFlow) {
3034
- liquidMiddleWare();
3035
- } else {
3036
- handleSave();
3037
- }
3006
+ liquidMiddleWare();
3038
3007
  }}
3039
3008
  className="save-button"
3040
3009
  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 {}
@@ -86505,7 +86505,6 @@ new message content.",
86505
86505
  },
86506
86506
  ]
86507
86507
  }
86508
- showTruncatedTooltip={false}
86509
86508
  size="large"
86510
86509
  style={
86511
86510
  Object {
@@ -86525,7 +86524,6 @@ new message content.",
86525
86524
  />
86526
86525
  }
86527
86526
  onChange={[Function]}
86528
- onDropdownVisibleChange={[Function]}
86529
86527
  removeIcon={
86530
86528
  <_default
86531
86529
  size="s"
@@ -86591,7 +86589,6 @@ new message content.",
86591
86589
  onBlur={[Function]}
86592
86590
  onChange={[Function]}
86593
86591
  onDeselect={[Function]}
86594
- onDropdownVisibleChange={[Function]}
86595
86592
  onFocus={[Function]}
86596
86593
  onInputKeyDown={[Function]}
86597
86594
  onSearch={[Function]}
@@ -86830,13 +86827,9 @@ new message content.",
86830
86827
  "opacity": 1,
86831
86828
  }
86832
86829
  }
86833
- title=""
86830
+ title="Vertical Medium"
86834
86831
  >
86835
- <div
86836
- className="cap-select-option-tooltip"
86837
- >
86838
- Vertical Medium
86839
- </div>
86832
+ Vertical Medium
86840
86833
  </div>
86841
86834
  </div>
86842
86835
  <span
@@ -105854,7 +105847,6 @@ new message content.",
105854
105847
  },
105855
105848
  ]
105856
105849
  }
105857
- showTruncatedTooltip={false}
105858
105850
  size="large"
105859
105851
  style={
105860
105852
  Object {
@@ -105874,7 +105866,6 @@ new message content.",
105874
105866
  />
105875
105867
  }
105876
105868
  onChange={[Function]}
105877
- onDropdownVisibleChange={[Function]}
105878
105869
  removeIcon={
105879
105870
  <_default
105880
105871
  size="s"
@@ -105940,7 +105931,6 @@ new message content.",
105940
105931
  onBlur={[Function]}
105941
105932
  onChange={[Function]}
105942
105933
  onDeselect={[Function]}
105943
- onDropdownVisibleChange={[Function]}
105944
105934
  onFocus={[Function]}
105945
105935
  onInputKeyDown={[Function]}
105946
105936
  onSearch={[Function]}
@@ -106179,13 +106169,9 @@ new message content.",
106179
106169
  "opacity": 1,
106180
106170
  }
106181
106171
  }
106182
- title=""
106172
+ title="Vertical Medium"
106183
106173
  >
106184
- <div
106185
- className="cap-select-option-tooltip"
106186
- >
106187
- Vertical Medium
106188
- </div>
106174
+ Vertical Medium
106189
106175
  </div>
106190
106176
  </div>
106191
106177
  <span
@@ -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);
@@ -141,7 +142,15 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
141
142
 
142
143
  componentWillReceiveProps(nextProps) {
143
144
  if (nextProps.location.query.module === 'library' && nextProps.isGetFormData) {
144
- nextProps.getFormSubscriptionData(this.getFormData());
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);
@@ -135,7 +136,13 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
135
136
 
136
137
  componentWillReceiveProps(nextProps) {
137
138
  if (nextProps.location.query.module === 'library' && nextProps.isGetFormData) {
138
- nextProps.getFormSubscriptionData(this.getFormData());
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
 
@@ -6,13 +6,18 @@ export function showError() {
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'];
13
+ const invalidTags = err0['invalid-tags'];
12
14
  if (!isEmpty(invalidTags)) {
13
15
  errorMessage.description = `${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
14
16
  }
15
17
  CapNotification.error(errorMessage);
18
+ } else if (isBraceError) {
19
+ errorMessage.description = intl.formatMessage(messages.unbalancedCurlyBraces);
20
+ CapNotification.error(errorMessage);
16
21
  }
17
22
  }
18
23
  }
@@ -98,5 +98,5 @@
98
98
  }
99
99
 
100
100
  .create-dlt-msg {
101
- margin-left: 100px;
101
+ margin-left: 6.25rem;
102
102
  }