@capillarytech/creatives-library 8.0.290-alpha.4 → 8.0.291

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 +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 +201 -132
  11. package/v2Components/FormBuilder/messages.js +8 -0
  12. package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
  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/components/CodeEditorPane/index.js +2 -1
  16. package/v2Containers/Cap/mockData.js +14 -0
  17. package/v2Containers/Cap/reducer.js +55 -3
  18. package/v2Containers/Cap/tests/reducer.test.js +102 -0
  19. package/v2Containers/CreativesContainer/SlideBoxContent.js +1 -5
  20. package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -13
  21. package/v2Containers/CreativesContainer/index.js +15 -30
  22. package/v2Containers/Email/index.js +5 -1
  23. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +70 -23
  24. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +137 -29
  25. package/v2Containers/FTP/index.js +51 -2
  26. package/v2Containers/FTP/messages.js +4 -0
  27. package/v2Containers/InApp/index.js +104 -4
  28. package/v2Containers/InApp/tests/index.test.js +6 -17
  29. package/v2Containers/InappAdvance/index.js +108 -4
  30. package/v2Containers/InappAdvance/tests/index.test.js +0 -2
  31. package/v2Containers/Line/Container/Text/index.js +1 -0
  32. package/v2Containers/MobilePush/Create/index.js +19 -42
  33. package/v2Containers/MobilePush/Edit/index.js +19 -42
  34. package/v2Containers/MobilePushNew/index.js +32 -12
  35. package/v2Containers/MobilepushWrapper/index.js +1 -3
  36. package/v2Containers/Rcs/index.js +37 -12
  37. package/v2Containers/Sms/Create/index.js +3 -39
  38. package/v2Containers/Sms/Create/messages.js +0 -4
  39. package/v2Containers/Sms/Edit/index.js +3 -35
  40. package/v2Containers/Sms/commonMethods.js +6 -3
  41. package/v2Containers/SmsTrai/Edit/index.js +47 -11
  42. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  43. package/v2Containers/SmsWrapper/index.js +0 -2
  44. package/v2Containers/Viber/index.js +1 -0
  45. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
  46. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
  47. package/v2Containers/WebPush/Create/index.js +2 -2
  48. package/v2Containers/WebPush/Create/utils/validation.js +2 -17
  49. package/v2Containers/WebPush/Create/utils/validation.test.js +24 -59
  50. package/v2Containers/Whatsapp/index.js +17 -9
  51. package/v2Containers/Zalo/index.js +11 -3
  52. package/v2Containers/Sms/tests/commonMethods.test.js +0 -122
@@ -21,6 +21,10 @@ export default defineMessages({
21
21
  id: 'creatives.containersV2.FTP.addColumn',
22
22
  defaultMessage: 'Add column',
23
23
  },
24
+ unsupportedTagsValidationError: {
25
+ id: 'creatives.containersV2.FTP.unsupportedTagsValidationError',
26
+ defaultMessage: 'Unsupported tags: {unsupportedTags}. Please remove them from this message.',
27
+ },
24
28
  selectTag: {
25
29
  id: 'creatives.containersV2.FTP.selectTag',
26
30
  defaultMessage: 'Select tag',
@@ -31,6 +31,7 @@ import creativesMessages from '../CreativesContainer/messages';
31
31
  import withCreatives from "../../hoc/withCreatives";
32
32
  import UnifiedPreview from "../../v2Components/CommonTestAndPreview/UnifiedPreview";
33
33
  import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
34
+ import { validateTags } from "../../utils/tagValidations";
34
35
  import injectReducer from '../../utils/injectReducer';
35
36
  import v2InAppReducer from './reducer';
36
37
  import { v2InAppSagas } from './sagas';
@@ -57,6 +58,7 @@ import {
57
58
  import { getCdnUrl } from "../../utils/cdnTransformation";
58
59
  import { getCtaObject, hasAnyErrors, getSingleTab } from "./utils";
59
60
  import { validateInAppContent } from "../../utils/commonUtils";
61
+ import { hasLiquidSupportFeature } from "../../utils/common";
60
62
  import formBuilderMessages from "../../v2Components/FormBuilder/messages";
61
63
  import HTMLEditor from "../../v2Components/HtmlEditor";
62
64
  import { HTML_EDITOR_VARIANTS } from "../../v2Components/HtmlEditor/constants";
@@ -1077,8 +1079,8 @@ export const InApp = (props) => {
1077
1079
  };
1078
1080
 
1079
1081
  // Validation middleware for tag validation (both liquid and non-liquid flow)
1080
- // Liquid validation (extractTags) runs only in library mode
1081
1082
  const validationMiddleWare = async () => {
1083
+ // Set up callbacks for validation results
1082
1084
  const onError = ({ standardErrors, liquidErrors }) => {
1083
1085
  setErrorMessage((prev) => ({
1084
1086
  STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...standardErrors },
@@ -1086,20 +1088,30 @@ export const InApp = (props) => {
1086
1088
  }));
1087
1089
  };
1088
1090
  const onSuccess = () => {
1091
+ // Proceed with submission when validation is successful
1089
1092
  onDoneCallback();
1090
1093
  };
1091
1094
 
1092
- // Library mode: run extractTags validation (always, so we catch liquid errors even when tags not loaded)
1093
- if (!isFullMode) {
1095
+ // Skip validation if no tags are available (e.g., in tests or when tags haven't loaded)
1096
+ const hasTags = tags && tags.length > 0;
1097
+
1098
+ // For liquid flow, use validateInAppContent
1099
+ if (isLiquidFlow && hasTags) {
1100
+ // Validate the INAPP content
1094
1101
  const payload = createPayload();
1095
1102
  validateInAppContent(payload, {
1096
- currentTab: panes === ANDROID ? 1 : 2,
1103
+ currentTab: panes === ANDROID ? 1 : 2, // Convert ANDROID/IOS to tab numbers
1097
1104
  onError,
1098
1105
  onSuccess,
1099
1106
  getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
1100
1107
  formatMessage,
1101
1108
  messages: formBuilderMessages,
1109
+ tagLookupMap: metaEntities?.tagLookupMap || {},
1110
+ eventContextTags: metaEntities?.eventContextTags || [],
1111
+ isLiquidFlow,
1112
+ forwardedTags: {},
1102
1113
  skipTags: (tag) => {
1114
+ // Skip certain tags if needed
1103
1115
  const skipRegexes = [
1104
1116
  /dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
1105
1117
  /unsubscribe\(#[a-zA-Z\d]{6}\)/,
@@ -1107,15 +1119,103 @@ export const InApp = (props) => {
1107
1119
  /SURVEY.*\.TOKEN/,
1108
1120
  /^[A-Za-z].*\([a-zA-Z\d]*\)/,
1109
1121
  ];
1122
+
1110
1123
  return skipRegexes.some((regex) => regex.test(tag));
1111
1124
  },
1112
1125
  singleTab: getSingleTab(accountData),
1113
1126
  });
1127
+ } else if (hasTags) {
1128
+ // For non-liquid flow, validate tags using validateTags (only if tags are available)
1129
+ const androidContent = htmlContentAndroid || '';
1130
+ const iosContent = htmlContentIos || '';
1131
+
1132
+ const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
1133
+ const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
1134
+
1135
+ let hasErrors = false;
1136
+ const newErrors = {
1137
+ STANDARD_ERROR_MSG: {
1138
+ ANDROID: [],
1139
+ IOS: [],
1140
+ GENERIC: [],
1141
+ },
1142
+ LIQUID_ERROR_MSG: {
1143
+ ANDROID: [],
1144
+ IOS: [],
1145
+ GENERIC: [],
1146
+ },
1147
+ };
1148
+
1149
+ // Validate Android content
1150
+ if (androidSupported && androidContent && androidContent?.trim() !== '') {
1151
+ const validationResponse = validateTags({
1152
+ content: androidContent,
1153
+ tagsParam: tags,
1154
+ injectedTagsParams: injectedTags || {},
1155
+ location,
1156
+ tagModule: getDefaultTags,
1157
+ eventContextTags: metaEntities?.eventContextTags || [],
1158
+ isFullMode,
1159
+ }) || {};
1160
+
1161
+ if (validationResponse?.unsupportedTags?.length > 0) {
1162
+ hasErrors = true;
1163
+ newErrors.LIQUID_ERROR_MSG.ANDROID.push(
1164
+ formatMessage(globalMessages.unsupportedTagsValidationError, {
1165
+ unsupportedTags: validationResponse.unsupportedTags.join(", "),
1166
+ })
1167
+ );
1168
+ }
1169
+ if (validationResponse?.isBraceError) {
1170
+ hasErrors = true;
1171
+ newErrors.LIQUID_ERROR_MSG.ANDROID.push(
1172
+ formatMessage(globalMessages.unbalanacedCurlyBraces)
1173
+ );
1174
+ }
1175
+ }
1176
+
1177
+ // Validate iOS content
1178
+ if (iosSupported && iosContent && iosContent?.trim() !== '') {
1179
+ const validationResponse = validateTags({
1180
+ content: iosContent,
1181
+ tagsParam: tags,
1182
+ injectedTagsParams: injectedTags || {},
1183
+ location,
1184
+ tagModule: getDefaultTags,
1185
+ eventContextTags: metaEntities?.eventContextTags || [],
1186
+ isFullMode,
1187
+ }) || {};
1188
+
1189
+ if (validationResponse?.unsupportedTags?.length > 0) {
1190
+ hasErrors = true;
1191
+ newErrors.LIQUID_ERROR_MSG.IOS.push(
1192
+ formatMessage(globalMessages.unsupportedTagsValidationError, {
1193
+ unsupportedTags: validationResponse.unsupportedTags.join(", "),
1194
+ })
1195
+ );
1196
+ }
1197
+ if (validationResponse?.isBraceError) {
1198
+ hasErrors = true;
1199
+ newErrors.LIQUID_ERROR_MSG.IOS.push(
1200
+ formatMessage(globalMessages.unbalanacedCurlyBraces)
1201
+ );
1202
+ }
1203
+ }
1204
+
1205
+ if (hasErrors) {
1206
+ setErrorMessage(newErrors);
1207
+ } else {
1208
+ // No errors, proceed with submission
1209
+ onSuccess();
1210
+ }
1114
1211
  } else {
1212
+ // No tags available, skip validation and proceed directly
1115
1213
  onSuccess();
1116
1214
  }
1117
1215
  };
1118
1216
 
1217
+ const isLiquidFlow = hasLiquidSupportFeature();
1218
+
1119
1219
  // Check template data to determine editor type (for render decision)
1120
1220
  const templateDetails = isFullMode ? editData?.templateDetails : templateData;
1121
1221
  const versions = templateDetails?.versions || {};
@@ -21,16 +21,18 @@ import { getCtaObject } from '../utils';
21
21
  jest.mock('redux-auth-wrapper/history4/redirect', () => ({
22
22
  connectedRouterRedirect: jest.fn((config) => (Component) => Component),
23
23
  }));
24
- import * as commonUtils from '../../../utils/commonUtils';
25
24
 
26
25
  const mockActions = {
27
26
  getTemplateInfoById: jest.fn(),
28
27
  resetEditTemplate: jest.fn(),
29
- getTemplateDetails: jest.fn((id, setSpin) => {
28
+ getTemplateDetails: jest.fn((id, callback) => {
30
29
  // Simulate successful template details fetch to prevent loading state
31
30
  // The callback is setSpin function, call it with false to stop spinner
32
- if (setSpin && typeof setSpin === 'function') {
33
- setTimeout(() => setSpin(false), 0);
31
+ if (callback && typeof callback === 'function') {
32
+ // Use setTimeout to ensure it's called after render
33
+ setTimeout(() => {
34
+ callback(false);
35
+ }, 0);
34
36
  }
35
37
  }),
36
38
  editTemplate: jest.fn(),
@@ -39,9 +41,6 @@ const mockActions = {
39
41
  };
40
42
  const mockGlobalActions = {
41
43
  fetchSchemaForEntity: jest.fn(),
42
- getLiquidTags: jest.fn((content, callback) =>
43
- callback({ askAiraResponse: { data: [], errors: [] }, isError: false }),
44
- ),
45
44
  };
46
45
 
47
46
  jest.mock('../../../v2Containers/TagList/index.js', () => ({
@@ -67,17 +66,7 @@ const renderComponent = (props) =>
67
66
  );
68
67
 
69
68
  describe('Test activity inApp container', () => {
70
- afterEach(() => {
71
- jest.restoreAllMocks();
72
- });
73
-
74
69
  it('test case for inApp template update flow', async () => {
75
- jest
76
- .spyOn(commonUtils, 'validateInAppContent')
77
- .mockImplementation((payload, options) => {
78
- options.onSuccess();
79
- return Promise.resolve(true);
80
- });
81
70
  renderComponent({
82
71
  actions: mockActions,
83
72
  globalActions: mockGlobalActions,
@@ -50,7 +50,9 @@ 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";
53
54
  import { validateInAppContent } from "../../utils/commonUtils";
55
+ import { hasLiquidSupportFeature } from "../../utils/common";
54
56
  import formBuilderMessages from "../../v2Components/FormBuilder/messages";
55
57
  import { getSingleTab, hasAnyErrors } from "../InApp/utils";
56
58
  import ErrorInfoNote from "../../v2Components/ErrorInfoNote";
@@ -815,16 +817,24 @@ export const InappAdvanced = (props) => {
815
817
  const latestHtmlValues = await saveAllBeeInstances();
816
818
  const payload = createPayload(latestHtmlValues);
817
819
 
818
- // Liquid validation (extractTags) only in library mode
819
- if (!isFullMode) {
820
+ // Validate the INAPP content
821
+ const isLiquidFlow = hasLiquidSupportFeature();
822
+ // Skip validation if no tags are available (e.g., in tests or when tags haven't loaded)
823
+ const hasTags = tags && tags.length > 0;
824
+ if (isLiquidFlow && hasTags) {
820
825
  validateInAppContent(payload, {
821
- currentTab: panes === ANDROID ? 1 : 2,
826
+ currentTab: panes === ANDROID ? 1 : 2, // Convert ANDROID/IOS to tab numbers
822
827
  onError,
823
828
  onSuccess,
824
829
  getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
825
830
  formatMessage,
826
831
  messages: formBuilderMessages,
832
+ tagLookupMap: metaEntities?.tagLookupMap || {},
833
+ eventContextTags: metaEntities?.eventContextTags || [],
834
+ isLiquidFlow,
835
+ forwardedTags: {},
827
836
  skipTags: (tag) => {
837
+ // Skip certain tags if needed
828
838
  const skipRegexes = [
829
839
  /dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
830
840
  /unsubscribe\(#[a-zA-Z\d]{6}\)/,
@@ -832,11 +842,97 @@ export const InappAdvanced = (props) => {
832
842
  /SURVEY.*\.TOKEN/,
833
843
  /^[A-Za-z].*\([a-zA-Z\d]*\)/,
834
844
  ];
845
+
835
846
  return skipRegexes.some((regex) => regex.test(tag));
836
847
  },
837
848
  singleTab: getSingleTab(accountData),
838
849
  });
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
+ }
839
934
  } else {
935
+ // No tags available, skip validation and proceed directly
840
936
  onSuccess();
841
937
  }
842
938
  };
@@ -942,7 +1038,15 @@ export const InappAdvanced = (props) => {
942
1038
  )}
943
1039
  <CapButton
944
1040
  onClick={async () => {
945
- await liquidMiddleWare();
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
+ }
946
1050
  }}
947
1051
  disabled={isDisableDone()}
948
1052
  className="inapp-create-btn"
@@ -61,7 +61,6 @@ describe('InappAdvanced Component', () => {
61
61
 
62
62
  const mockGlobalActions = {
63
63
  fetchSchemaForEntity: jest.fn(),
64
- getLiquidTags: jest.fn((content, callback) => callback({ askAiraResponse: { data: [] }, isError: false })),
65
64
  };
66
65
 
67
66
  defaultProps = {
@@ -410,7 +409,6 @@ describe('InappAdvanced Component', () => {
410
409
  actions: mockActions,
411
410
  globalActions: {
412
411
  fetchSchemaForEntity: jest.fn(),
413
- getLiquidTags: jest.fn((content, callback) => callback({ askAiraResponse: { data: [] }, isError: false })),
414
412
  },
415
413
  location: {
416
414
  pathname: '/inapp/create',
@@ -137,6 +137,7 @@ export const LineText = ({
137
137
  const { valid, isBraceError } = validateTags({
138
138
  content: value,
139
139
  tagsParam: tags,
140
+ injectedTagsParams: injectedTags,
140
141
  location,
141
142
  tagModule: 'outbound',
142
143
  isFullMode,
@@ -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,39 +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
- nextProps.onValidationFail();
123
- },
124
- onSuccess: () => {
125
- nextProps.getFormLibraryData(this.getFormData());
126
- },
127
- });
128
- } else {
129
- nextProps.getFormLibraryData(this.getFormData());
130
- }
97
+ if (nextProps.isGetFormData && !this.props.isFullMode) {
98
+ nextProps.getFormLibraryData(this.getFormData());
131
99
  } else if (nextProps.isGetFormData && this.props.isGetFormData !== nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
132
100
  this.startValidation();
133
101
  }
@@ -710,21 +678,31 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
710
678
  }
711
679
  showError = () => {
712
680
  const {intl} = this.props;
713
- const {errorData, schema} = this.state;
681
+ const {errorData} = this.state;
714
682
  const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
715
683
  if (!isEmpty(this.state.formData) && !this.state.isFormValid) {
716
684
  let tab = this.state.currentTab;
717
- const isAndroidInvalid = Object.values(errorData[0] || {}).includes(true);
718
- const isIosInvalid = Object.values(errorData[1] || {}).includes(true);
719
- 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;
720
688
  if (isAndroidInvalid) {
721
689
  tab = 1;
722
690
  errorMessage.description = intl.formatMessage(messages.invalidAndroidMessage);
723
- } 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) {
724
697
  tab = 2;
725
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
+ }
726
704
  }
727
- if (tab !== this.state.currentTab) {
705
+ if (tab !== this.state.currentTab || isTagErrorExist) {
728
706
  CapNotification.error(errorMessage);
729
707
  }
730
708
  }
@@ -2051,7 +2029,6 @@ Create.propTypes = {
2051
2029
  onPreviewContentClicked: PropTypes.func,
2052
2030
  onTestContentClicked: PropTypes.func,
2053
2031
  eventContextTags: PropTypes.array,
2054
- getLiquidTags: PropTypes.func,
2055
2032
  showLiquidErrorInFooter: PropTypes.func,
2056
2033
  showTestAndPreviewSlidebox: PropTypes.bool,
2057
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,39 +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
- nextProps.onValidationFail();
158
- },
159
- onSuccess: () => {
160
- nextProps.getFormLibraryData(this.getFormData());
161
- },
162
- });
163
- } else {
164
- nextProps.getFormLibraryData(this.getFormData());
165
- }
132
+ if (nextProps.isGetFormData && !this.props.isFullMode) {
133
+ nextProps.getFormLibraryData(this.getFormData());
166
134
  } else if (nextProps.isGetFormData && this.props.isGetFormData !== nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
167
135
  this.startValidation();
168
136
  }
@@ -737,22 +705,32 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
737
705
  // eslint-disable-next-line react/sort-comp
738
706
  showError = () => {
739
707
  const {intl} = this.props;
740
- const {errorData, schema} = this.state;
708
+ const {errorData} = this.state;
741
709
  const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
710
+ let isTagErrorExist = false;
742
711
  if (!_.isEmpty(this.state.formData) && !this.state.isFormValid) {
743
712
  let tab = this.state.currentTab;
744
- const isAndroidInvalid = Object.values(errorData[0] || {}).includes(true);
745
- const isIosInvalid = Object.values(errorData[1] || {}).includes(true);
746
- 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);
747
715
  if (isAndroidInvalid) {
748
716
  tab = 1;
749
717
  errorMessage.description = intl.formatMessage(messages.invalidAndroidMessage);
750
- } 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) {
751
724
  tab = 2;
752
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
+ }
753
731
  }
754
732
 
755
- if (tab !== this.state.currentTab) {
733
+ if (tab !== this.state.currentTab || isTagErrorExist) {
756
734
  CapNotification.error(errorMessage);
757
735
  }
758
736
  }
@@ -2334,7 +2312,6 @@ Edit.propTypes = {
2334
2312
  onTestContentClicked: PropTypes.func,
2335
2313
  creativesMode: PropTypes.string,
2336
2314
  eventContextTags: PropTypes.array,
2337
- getLiquidTags: PropTypes.func,
2338
2315
  showLiquidErrorInFooter: PropTypes.func,
2339
2316
  showTestAndPreviewSlidebox: PropTypes.bool,
2340
2317
  handleTestAndPreview: PropTypes.func,