@capillarytech/creatives-library 8.0.292-alpha.1 → 8.0.292-alpha.11

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 (55) hide show
  1. package/constants/unified.js +1 -3
  2. package/initialState.js +2 -0
  3. package/package.json +1 -1
  4. package/utils/common.js +8 -5
  5. package/utils/commonUtils.js +85 -4
  6. package/utils/tagValidations.js +223 -83
  7. package/utils/tests/commonUtil.test.js +124 -147
  8. package/utils/tests/tagValidations.test.js +358 -441
  9. package/v2Components/ErrorInfoNote/index.js +5 -2
  10. package/v2Components/FormBuilder/index.js +203 -137
  11. package/v2Components/FormBuilder/messages.js +8 -0
  12. package/v2Components/HtmlEditor/HTMLEditor.js +11 -2
  13. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  14. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
  15. package/v2Components/HtmlEditor/_htmlEditor.scss +5 -1
  16. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +10 -10
  17. package/v2Components/HtmlEditor/components/DeviceToggle/FLOW_AND_CLICK_BEHAVIOUR.md +70 -0
  18. package/v2Components/HtmlEditor/hooks/useInAppContent.js +15 -8
  19. package/v2Containers/Cap/mockData.js +14 -0
  20. package/v2Containers/Cap/reducer.js +55 -3
  21. package/v2Containers/Cap/tests/reducer.test.js +102 -0
  22. package/v2Containers/CreativesContainer/SlideBoxContent.js +2 -5
  23. package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -13
  24. package/v2Containers/CreativesContainer/index.js +10 -47
  25. package/v2Containers/Email/index.js +5 -1
  26. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +70 -23
  27. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +137 -29
  28. package/v2Containers/FTP/index.js +51 -2
  29. package/v2Containers/FTP/messages.js +4 -0
  30. package/v2Containers/InApp/index.js +142 -51
  31. package/v2Containers/InApp/tests/index.test.js +6 -17
  32. package/v2Containers/InappAdvance/index.js +120 -10
  33. package/v2Containers/InappAdvance/tests/index.test.js +2 -3
  34. package/v2Containers/Line/Container/Text/index.js +1 -0
  35. package/v2Containers/MobilePush/Create/index.js +19 -45
  36. package/v2Containers/MobilePush/Edit/index.js +20 -47
  37. package/v2Containers/MobilePushNew/index.js +32 -12
  38. package/v2Containers/MobilepushWrapper/index.js +1 -3
  39. package/v2Containers/Rcs/index.js +37 -12
  40. package/v2Containers/Sms/Create/index.js +3 -39
  41. package/v2Containers/Sms/Create/messages.js +0 -4
  42. package/v2Containers/Sms/Edit/index.js +3 -35
  43. package/v2Containers/Sms/commonMethods.js +6 -3
  44. package/v2Containers/SmsTrai/Edit/index.js +47 -11
  45. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  46. package/v2Containers/SmsWrapper/index.js +0 -2
  47. package/v2Containers/Viber/index.js +1 -0
  48. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
  49. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
  50. package/v2Containers/WebPush/Create/index.js +2 -2
  51. package/v2Containers/WebPush/Create/utils/validation.js +8 -17
  52. package/v2Containers/WebPush/Create/utils/validation.test.js +24 -44
  53. package/v2Containers/Whatsapp/index.js +17 -9
  54. package/v2Containers/Zalo/index.js +11 -3
  55. package/v2Containers/Sms/tests/commonMethods.test.js +0 -122
@@ -38,8 +38,7 @@ import v2MobilePushCreateReducer from './reducer';
38
38
  import { v2MobilePushWatchDuplicateTemplateSaga } from './sagas';
39
39
  import { EXTERNAL_LINK_LOWERCASE } from './constants';
40
40
  import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
41
- import { checkForPersonalizationTokens, validateMobilePushContent } from '../../../utils/commonUtils';
42
- import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
41
+ import { checkForPersonalizationTokens } from '../../../utils/commonUtils';
43
42
 
44
43
  const PrefixWrapper = styled.div`
45
44
  margin-right: 16px;
@@ -95,42 +94,8 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
95
94
  this.props.globalActions.fetchSchemaForEntity(query);
96
95
  };
97
96
  componentWillReceiveProps = (nextProps) => {
98
- // Library mode: on Done click (transition to isGetFormData), run extractTags only when no existing validation errors (braces, personalization, etc.)
99
- if (nextProps.isGetFormData && !this.props.isGetFormData && !nextProps.isFullMode) {
100
- // If form already has validation errors (braces, personalization tags, etc.), do not call extractTags; just fail
101
- if (!this.state.isFormValid && nextProps.onValidationFail) {
102
- nextProps.onValidationFail();
103
- return;
104
- }
105
- if (nextProps.getLiquidTags && nextProps.showLiquidErrorInFooter && nextProps.onValidationFail) {
106
- const formDataArr = [this.state.formData?.[0], this.state.formData?.[1]];
107
- validateMobilePushContent(formDataArr, {
108
- currentTab: this.state.currentTab,
109
- getLiquidTags: nextProps.getLiquidTags,
110
- formatMessage: this.props.intl.formatMessage,
111
- messages: formBuilderMessages,
112
- onError: (err) => {
113
- const { standardErrors = [], liquidErrors = [] } = err;
114
- // _validatePlatformSpecificContent passes { standardErrors: { ANDROID, IOS, generic }, liquidErrors: { ... } }; footer expects arrays
115
- const toArray = (v) => (Array.isArray(v) ? v : (v && typeof v === 'object' ? [].concat(...Object.values(v)) : []));
116
- const STANDARD_ERROR_MSG = toArray(standardErrors);
117
- const LIQUID_ERROR_MSG = toArray(liquidErrors);
118
- nextProps.showLiquidErrorInFooter(
119
- { STANDARD_ERROR_MSG, LIQUID_ERROR_MSG },
120
- this.state.currentTab
121
- );
122
- // Only trigger onValidationFail when there are actual errors; skip when helper called onError with empty arrays (reset case)
123
- if (STANDARD_ERROR_MSG.length > 0 || LIQUID_ERROR_MSG.length > 0) {
124
- nextProps.onValidationFail();
125
- }
126
- },
127
- onSuccess: () => {
128
- nextProps.getFormLibraryData(this.getFormData());
129
- },
130
- });
131
- } else {
132
- nextProps.getFormLibraryData(this.getFormData());
133
- }
97
+ if (nextProps.isGetFormData && !this.props.isFullMode) {
98
+ nextProps.getFormLibraryData(this.getFormData());
134
99
  } else if (nextProps.isGetFormData && this.props.isGetFormData !== nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
135
100
  this.startValidation();
136
101
  }
@@ -713,21 +678,31 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
713
678
  }
714
679
  showError = () => {
715
680
  const {intl} = this.props;
716
- const {errorData, schema} = this.state;
681
+ const {errorData} = this.state;
717
682
  const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
718
683
  if (!isEmpty(this.state.formData) && !this.state.isFormValid) {
719
684
  let tab = this.state.currentTab;
720
- const isAndroidInvalid = Object.values(errorData[0] || {}).includes(true);
721
- const isIosInvalid = Object.values(errorData[1] || {}).includes(true);
722
- const isIosTabVisible = get(schema, 'containers[0].panes[1].isSupported', true) !== false;
685
+ const isAndroidInvalid = Object.values(errorData[0]).includes(true);
686
+ const isIosInvalid = Object.values(errorData[1]).includes(true);
687
+ let isTagErrorExist = false;
723
688
  if (isAndroidInvalid) {
724
689
  tab = 1;
725
690
  errorMessage.description = intl.formatMessage(messages.invalidAndroidMessage);
726
- } else if (isIosInvalid && isIosTabVisible) {
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) {
727
697
  tab = 2;
728
698
  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
+ }
729
704
  }
730
- if (tab !== this.state.currentTab) {
705
+ if (tab !== this.state.currentTab || isTagErrorExist) {
731
706
  CapNotification.error(errorMessage);
732
707
  }
733
708
  }
@@ -2054,7 +2029,6 @@ Create.propTypes = {
2054
2029
  onPreviewContentClicked: PropTypes.func,
2055
2030
  onTestContentClicked: PropTypes.func,
2056
2031
  eventContextTags: PropTypes.array,
2057
- getLiquidTags: PropTypes.func,
2058
2032
  showLiquidErrorInFooter: PropTypes.func,
2059
2033
  showTestAndPreviewSlidebox: PropTypes.bool,
2060
2034
  handleTestAndPreview: PropTypes.func,
@@ -39,8 +39,7 @@ import { v2MobilePushEditSagas } from './sagas';
39
39
  import v2MobilePushEditReducer from './reducer';
40
40
  import * as globalActions from '../../Cap/actions';
41
41
  import { MAPP_SDK } from './constants';
42
- import { checkForPersonalizationTokens, isEmbeddedEditOrPreview, validateMobilePushContent } from '../../../utils/commonUtils';
43
- import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
42
+ import { checkForPersonalizationTokens, isEmbeddedEditOrPreview } from '../../../utils/commonUtils';
44
43
  import { EMBEDDED } from '../../Whatsapp/constants';
45
44
  import { OUTBOUND } from '../../../v2Components/FormBuilder/constants';
46
45
  import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
@@ -130,43 +129,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
130
129
  this.hasFetchedInitialTagsRef = false;
131
130
  this.lastFetchedTagContextRef = null;
132
131
  }
133
- // Library mode: on Done click (transition to isGetFormData), run extractTags only when no existing validation errors (braces, personalization, etc.)
134
- if (nextProps.isGetFormData && !this.props.isGetFormData && !nextProps.isFullMode) {
135
- // If form already has validation errors (braces, personalization tags, etc.), do not call extractTags; just fail
136
- if (!this.state.isFormValid && nextProps.onValidationFail) {
137
- nextProps.onValidationFail();
138
- return;
139
- }
140
- if (nextProps.getLiquidTags && nextProps.showLiquidErrorInFooter && nextProps.onValidationFail) {
141
- const formDataArr = [this.state.formData?.[0], this.state.formData?.[1]];
142
- validateMobilePushContent(formDataArr, {
143
- currentTab: this.state.currentTab,
144
- getLiquidTags: nextProps.getLiquidTags,
145
- formatMessage: this.props.intl.formatMessage,
146
- messages: formBuilderMessages,
147
- onError: (err) => {
148
- const { standardErrors = [], liquidErrors = [] } = err;
149
- // _validatePlatformSpecificContent passes { standardErrors: { ANDROID, IOS, generic }, liquidErrors: { ... } }; footer expects arrays
150
- const toArray = (v) => (Array.isArray(v) ? v : (v && typeof v === 'object' ? [].concat(...Object.values(v)) : []));
151
- const STANDARD_ERROR_MSG = toArray(standardErrors);
152
- const LIQUID_ERROR_MSG = toArray(liquidErrors);
153
- nextProps.showLiquidErrorInFooter(
154
- { STANDARD_ERROR_MSG, LIQUID_ERROR_MSG },
155
- this.state.currentTab
156
- );
157
- // Only treat as validation failure when there are actual errors; skip when helper called onError with empty reset payload
158
- const hasErrors = STANDARD_ERROR_MSG.length > 0 || LIQUID_ERROR_MSG.length > 0;
159
- if (hasErrors && nextProps.onValidationFail) {
160
- nextProps.onValidationFail();
161
- }
162
- },
163
- onSuccess: () => {
164
- nextProps.getFormLibraryData(this.getFormData());
165
- },
166
- });
167
- } else {
168
- nextProps.getFormLibraryData(this.getFormData());
169
- }
132
+ if (nextProps.isGetFormData && !this.props.isFullMode) {
133
+ nextProps.getFormLibraryData(this.getFormData());
170
134
  } else if (nextProps.isGetFormData && this.props.isGetFormData !== nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
171
135
  this.startValidation();
172
136
  }
@@ -741,22 +705,32 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
741
705
  // eslint-disable-next-line react/sort-comp
742
706
  showError = () => {
743
707
  const {intl} = this.props;
744
- const {errorData, schema} = this.state;
708
+ const {errorData} = this.state;
745
709
  const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
710
+ let isTagErrorExist = false;
746
711
  if (!_.isEmpty(this.state.formData) && !this.state.isFormValid) {
747
712
  let tab = this.state.currentTab;
748
- const isAndroidInvalid = Object.values(errorData[0] || {}).includes(true);
749
- const isIosInvalid = Object.values(errorData[1] || {}).includes(true);
750
- const isIosTabVisible = get(schema, 'containers[0].panes[1].isSupported', true) !== false;
713
+ const isAndroidInvalid = Object.values(errorData[0]).includes(true);
714
+ const isIosInvalid = Object.values(errorData[1]).includes(true);
751
715
  if (isAndroidInvalid) {
752
716
  tab = 1;
753
717
  errorMessage.description = intl.formatMessage(messages.invalidAndroidMessage);
754
- } else if (isIosInvalid && isIosTabVisible) {
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) {
755
724
  tab = 2;
756
725
  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
+ }
757
731
  }
758
732
 
759
- if (tab !== this.state.currentTab) {
733
+ if (tab !== this.state.currentTab || isTagErrorExist) {
760
734
  CapNotification.error(errorMessage);
761
735
  }
762
736
  }
@@ -2333,12 +2307,11 @@ Edit.propTypes = {
2333
2307
  getFormLibraryData: PropTypes.func,
2334
2308
  isGetFormData: PropTypes.bool,
2335
2309
  Create: PropTypes.object,
2336
- onValidationFail: PropTypes.func,
2310
+ onValidationFail: PropTypes.bool,
2337
2311
  onPreviewContentClicked: PropTypes.func,
2338
2312
  onTestContentClicked: PropTypes.func,
2339
2313
  creativesMode: PropTypes.string,
2340
2314
  eventContextTags: PropTypes.array,
2341
- getLiquidTags: PropTypes.func,
2342
2315
  showLiquidErrorInFooter: PropTypes.func,
2343
2316
  showTestAndPreviewSlidebox: PropTypes.bool,
2344
2317
  handleTestAndPreview: PropTypes.func,
@@ -77,6 +77,7 @@ 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";
80
81
  import formBuilderMessages from "../../v2Components/FormBuilder/messages";
81
82
  import { validateMobilePushContent } from "../../utils/commonUtils";
82
83
  import { getSingleTab } from "../InApp/utils";
@@ -802,9 +803,10 @@ const MobilePushNew = ({
802
803
  (value) => {
803
804
  let errorTemplateDescMessage = "";
804
805
 
805
- const { isBraceError } = validateTags({
806
+ const { unsupportedTags, isBraceError } = validateTags({
806
807
  content: value,
807
808
  tagsParam: tags,
809
+ injectedTagsParams: injectedTags,
808
810
  location,
809
811
  tagModule: getDefaultTags,
810
812
  isFullMode,
@@ -814,6 +816,14 @@ const MobilePushNew = ({
814
816
  messages.emptyTemplateDescErrorMessage
815
817
  );
816
818
  }
819
+ if (unsupportedTags?.length > 0) {
820
+ errorTemplateDescMessage = formatMessage(
821
+ globalMessages.unsupportedTagsValidationError,
822
+ {
823
+ unsupportedTags,
824
+ }
825
+ );
826
+ }
817
827
  if (isBraceError) {
818
828
  errorTemplateDescMessage = formatMessage(
819
829
  globalMessages.unbalanacedCurlyBraces
@@ -2649,6 +2659,20 @@ const MobilePushNew = ({
2649
2659
  getLiquidTags: globalActionsProps.getLiquidTags,
2650
2660
  formatMessage,
2651
2661
  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
+ },
2652
2676
  singleTab: getSingleTab(accountData),
2653
2677
  });
2654
2678
  }, [
@@ -2657,9 +2681,12 @@ const MobilePushNew = ({
2657
2681
  activeTab,
2658
2682
  globalActionsProps,
2659
2683
  formatMessage,
2684
+ metaEntities,
2660
2685
  accountData,
2661
2686
  ]);
2662
2687
 
2688
+ const isLiquidFlow = hasLiquidSupportFeature();
2689
+
2663
2690
  useEffect(() => {
2664
2691
  // Always map to { label } for both platforms
2665
2692
  const newButtons = Array.isArray(ctaData)
@@ -2906,22 +2933,16 @@ const MobilePushNew = ({
2906
2933
  setShowTestAndPreviewSlidebox(false);
2907
2934
  }, []);
2908
2935
 
2909
- // Add useEffect to handle isGetFormData prop changes (e.g. Done clicked in footer)
2910
- // In library mode: run liquid validation (extractTags) first; on success liquidMiddleWare calls handleSave
2911
- // In full mode: call handleSave directly
2936
+ // Add useEffect to handle isGetFormData prop changes
2912
2937
  useEffect(() => {
2913
2938
  if (isGetFormData) {
2914
- if (!isFullMode) {
2915
- liquidMiddleWare();
2916
- } else {
2917
- handleSave();
2918
- }
2939
+ handleSave();
2919
2940
  // Reset the flag to prevent infinite loop
2920
2941
  if (onValidationFail) {
2921
2942
  onValidationFail();
2922
2943
  }
2923
2944
  }
2924
- }, [isGetFormData, handleSave, onValidationFail, isFullMode, liquidMiddleWare]);
2945
+ }, [isGetFormData, handleSave, onValidationFail]);
2925
2946
 
2926
2947
  // Add message event listener to handle parent communication (like old MobilePush components)
2927
2948
  useEffect(() => {
@@ -3062,8 +3083,7 @@ const MobilePushNew = ({
3062
3083
  <CapButton
3063
3084
  type="primary"
3064
3085
  onClick={() => {
3065
- // Liquid validation (extractTags) only in library mode
3066
- if (!isFullMode) {
3086
+ if (isLiquidFlow) {
3067
3087
  liquidMiddleWare();
3068
3088
  } else {
3069
3089
  handleSave();
@@ -72,7 +72,7 @@ export class MobilepushWrapper extends React.Component { // eslint-disable-line
72
72
  }
73
73
 
74
74
  render() {
75
- const {mobilePushCreateMode, step, getFormData, getLiquidTags, setIsLoadingContent, isGetFormData, query, isFullMode, showTemplateName, type, onValidationFail, onPreviewContentClicked, onTestContentClicked, templateData, eventContextTags = [], showTestAndPreviewSlidebox, handleTestAndPreview, handleCloseTestAndPreview, restrictPersonalization, isAnonymousType, onPersonalizationTokensChange} = this.props;
75
+ const {mobilePushCreateMode, step, getFormData, setIsLoadingContent, isGetFormData, query, isFullMode, showTemplateName, type, onValidationFail, onPreviewContentClicked, onTestContentClicked, templateData, eventContextTags = [], showTestAndPreviewSlidebox, handleTestAndPreview, handleCloseTestAndPreview, restrictPersonalization, isAnonymousType, onPersonalizationTokensChange} = this.props;
76
76
  const {templateName} = this.state;
77
77
  const isShowMobilepushCreate = !isEmpty(mobilePushCreateMode);
78
78
  return (
@@ -102,7 +102,6 @@ export class MobilepushWrapper extends React.Component { // eslint-disable-line
102
102
  <div>
103
103
  {isShowMobilepushCreate && <MobilepushCreate
104
104
  getFormLibraryData={getFormData}
105
- getLiquidTags={getLiquidTags}
106
105
  setIsLoadingContent={setIsLoadingContent}
107
106
  defaultData={{"template-name": templateName}}
108
107
  location={{
@@ -144,7 +143,6 @@ MobilepushWrapper.propTypes = {
144
143
  mobilePushCreateMode: PropTypes.string,
145
144
  step: PropTypes.string,
146
145
  getFormData: PropTypes.string,
147
- getLiquidTags: PropTypes.func,
148
146
  setIsLoadingContent: PropTypes.func,
149
147
  onEnterTemplateName: PropTypes.func,
150
148
  onRemoveTemplateName: PropTypes.func,
@@ -394,15 +394,23 @@ export const Rcs = (props) => {
394
394
  const validationResponse =
395
395
  validateTags({
396
396
  content: contentForValidation,
397
- tagsParam: tags,
398
- location,
399
- tagModule: getDefaultTags,
400
- isFullMode,
401
- }) || {};
402
- const errorMsg =
403
- (validationResponse?.isBraceError &&
404
- formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
405
- false;
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;
406
414
  if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
407
415
  if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
408
416
  };
@@ -827,9 +835,10 @@ export const Rcs = (props) => {
827
835
 
828
836
  const templateDescErrorHandler = (value) => {
829
837
  let errorMessage = false;
830
- const { isBraceError } = validateTags({
838
+ const { unsupportedTags, isBraceError } = validateTags({
831
839
  content: value,
832
840
  tagsParam: tags,
841
+ injectedTagsParams: injectedTags,
833
842
  location,
834
843
  tagModule: getDefaultTags,
835
844
  isFullMode,
@@ -859,10 +868,26 @@ export const Rcs = (props) => {
859
868
  };
860
869
 
861
870
  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
+ }) || {};
862
880
  if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
863
- return formatMessage(messages.fallbackMsgLenError);
881
+ errorMessage = formatMessage(messages.fallbackMsgLenError);
882
+ } else if (unsupportedTags?.length > 0) {
883
+ errorMessage = formatMessage(
884
+ globalMessages.unsupportedTagsValidationError,
885
+ {
886
+ unsupportedTags,
887
+ },
888
+ );
864
889
  }
865
- return false;
890
+ return errorMessage;
866
891
  };
867
892
 
868
893
  // Check for forbidden characters: square brackets [] and single curly braces {}
@@ -51,7 +51,6 @@ 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,
55
54
  };
56
55
  this.saveFormData = this.saveFormData.bind(this);
57
56
  this.onFormDataChange = this.onFormDataChange.bind(this);
@@ -141,9 +140,8 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
141
140
  }
142
141
 
143
142
  componentWillReceiveProps(nextProps) {
144
- // Only trigger on actual Done click (isGetFormData false -> true). Prevents auto-submit after user fixes brace error.
145
- if (!nextProps.isFullMode && nextProps.isGetFormData && !this.props.isGetFormData) {
146
- this.setState({ startValidation: true, pendingGetFormData: true });
143
+ if (nextProps.location.query.module === 'library' && nextProps.isGetFormData) {
144
+ nextProps.getFormSubscriptionData(this.getFormData());
147
145
  } else if (nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
148
146
  this.startValidation();
149
147
  }
@@ -173,9 +171,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
173
171
  }
174
172
 
175
173
  componentWillUnmount() {
176
- if (this.pendingGetFormDataTimeout) {
177
- clearTimeout(this.pendingGetFormDataTimeout);
178
- }
179
174
  if (this.props.setIsLoadingContent) {
180
175
  this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
181
176
  }
@@ -204,10 +199,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
204
199
  if (currentTab) {
205
200
  this.setState({currentTab});
206
201
  }
207
- // Clear footer validation errors on input change so they refresh on next validation
208
- if (this.props.showLiquidErrorInFooter) {
209
- this.props.showLiquidErrorInFooter({ STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] });
210
- }
211
202
  }
212
203
 
213
204
  onTagSelect(data, currentTab) {
@@ -358,25 +349,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
358
349
  }
359
350
 
360
351
  setFormValidity(isFormValid, errorData) {
361
- this.setState({ isFormValid, errorData }, () => {
362
- if (this.state.pendingGetFormData && !isFormValid) {
363
- this.setState({ pendingGetFormData: false, startValidation: false });
364
- // Reset parent's Done state so next Done click is a fresh attempt
365
- if (this.props.onValidationFail) {
366
- this.props.onValidationFail();
367
- }
368
- return;
369
- }
370
- // In library mode with SMS, submit only when FormBuilder calls onSubmit (after liquid validation).
371
- if (this.state.pendingGetFormData && this.props.getFormSubscriptionData && this.props.isFullMode) {
372
- if (this.pendingGetFormDataTimeout) {
373
- clearTimeout(this.pendingGetFormDataTimeout);
374
- this.pendingGetFormDataTimeout = null;
375
- }
376
- this.props.getFormSubscriptionData(this.getFormData());
377
- this.setState({ pendingGetFormData: false, startValidation: false });
378
- }
379
- });
352
+ this.setState({isFormValid, errorData});
380
353
  }
381
354
 
382
355
  injectMessages(elem) {
@@ -997,15 +970,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
997
970
  }
998
971
 
999
972
  saveFormData() {
1000
- // In library mode: FormBuilder calls onSubmit only after liquid validation succeeds.
1001
- // Submit to parent here so the slidebox can close with valid data.
1002
- if (!this.props.isFullMode) {
1003
- if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
1004
- this.props.getFormSubscriptionData(this.getFormData());
1005
- this.setState({ pendingGetFormData: false, startValidation: false });
1006
- }
1007
- return;
1008
- }
1009
973
  //Logic to save in db etc
1010
974
  const formData = _.cloneDeep(this.state.formData);
1011
975
  const obj = {};
@@ -90,10 +90,6 @@ 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
- },
97
93
  "smsTemplateCreatedSuccess": {
98
94
  id: 'creatives.containersV2.Create.smsTemplateCreatedSuccess',
99
95
  defaultMessage: 'SMS Template Created Successfully',
@@ -52,7 +52,6 @@ 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,
56
55
  };
57
56
  this.saveFormData = this.saveFormData.bind(this);
58
57
  this.onFormDataChange = this.onFormDataChange.bind(this);
@@ -135,8 +134,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
135
134
  }
136
135
 
137
136
  componentWillReceiveProps(nextProps) {
138
- if (!nextProps.isFullMode && nextProps.isGetFormData && !this.props.isGetFormData) {
139
- this.setState({ startValidation: true, pendingGetFormData: true });
137
+ if (nextProps.location.query.module === 'library' && nextProps.isGetFormData) {
138
+ nextProps.getFormSubscriptionData(this.getFormData());
140
139
  }
141
140
  if ( nextProps.location.query.module === 'library' && nextProps.subscriptionTemplateDetails && nextProps.subscriptionTemplateDetails.name && _.isEmpty(this.state.editData) && !_.isEmpty(this.state.schema)) {
142
141
  this.setEditState(nextProps.subscriptionTemplateDetails);
@@ -190,9 +189,6 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
190
189
  }
191
190
 
192
191
  componentWillUnmount() {
193
- if (this.pendingGetFormDataTimeout) {
194
- clearTimeout(this.pendingGetFormDataTimeout);
195
- }
196
192
  if (this.props.setIsLoadingContent) {
197
193
  this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
198
194
  }
@@ -218,10 +214,6 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
218
214
  if (currentTab) {
219
215
  this.setState({currentTab});
220
216
  }
221
- // Clear footer validation errors on input change so they refresh on next validation
222
- if (this.props.showLiquidErrorInFooter) {
223
- this.props.showLiquidErrorInFooter({ STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] });
224
- }
225
217
  }
226
218
 
227
219
  onVersionNameChange() {
@@ -325,23 +317,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
325
317
  }
326
318
 
327
319
  setFormValidity(isFormValid, errorData) {
328
- this.setState({ isFormValid, errorData }, () => {
329
- if (this.state.pendingGetFormData && !isFormValid) {
330
- this.setState({ pendingGetFormData: false, startValidation: false });
331
- if (this.props.onValidationFail) {
332
- this.props.onValidationFail();
333
- }
334
- return;
335
- }
336
- if (this.state.pendingGetFormData && this.props.getFormSubscriptionData && this.props.isFullMode) {
337
- if (this.pendingGetFormDataTimeout) {
338
- clearTimeout(this.pendingGetFormDataTimeout);
339
- this.pendingGetFormDataTimeout = null;
340
- }
341
- this.props.getFormSubscriptionData(this.getFormData());
342
- this.setState({ pendingGetFormData: false, startValidation: false });
343
- }
344
- });
320
+ this.setState({isFormValid, errorData});
345
321
  }
346
322
 
347
323
  getFormData(e, value) {
@@ -947,14 +923,6 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
947
923
  this.setState({startValidation: false});
948
924
  }
949
925
  saveFormData() {
950
- // In library mode: FormBuilder calls onSubmit only after liquid validation succeeds.
951
- if (!this.props.isFullMode) {
952
- if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
953
- this.props.getFormSubscriptionData(this.getFormData());
954
- this.setState({ pendingGetFormData: false, startValidation: false });
955
- }
956
- return;
957
- }
958
926
  //Logic to save in db etc
959
927
  //saveFormData gets called only when validation result is true
960
928
 
@@ -1,14 +1,17 @@
1
1
  import isEmpty from 'lodash/isEmpty';
2
- import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
2
+ import { CapNotification } from '@capillarytech/cap-ui-library';
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 err0 = errorData[0] || {};
10
- const isSmsInvalid = Object.values(err0).includes(true);
9
+ const isSmsInvalid = Object.values(errorData[0]).includes(true);
11
10
  if (isSmsInvalid) {
11
+ const invalidTags = errorData[0]['invalid-tags'];
12
+ if (!isEmpty(invalidTags)) {
13
+ errorMessage.description = `${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
14
+ }
12
15
  CapNotification.error(errorMessage);
13
16
  }
14
17
  }