@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.
- package/constants/unified.js +1 -3
- package/initialState.js +2 -0
- package/package.json +1 -1
- package/utils/common.js +8 -5
- package/utils/commonUtils.js +85 -4
- package/utils/tagValidations.js +223 -83
- package/utils/tests/commonUtil.test.js +124 -147
- package/utils/tests/tagValidations.test.js +358 -441
- package/v2Components/ErrorInfoNote/index.js +5 -2
- package/v2Components/FormBuilder/index.js +201 -132
- package/v2Components/FormBuilder/messages.js +8 -0
- package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +2 -1
- package/v2Containers/Cap/mockData.js +14 -0
- package/v2Containers/Cap/reducer.js +55 -3
- package/v2Containers/Cap/tests/reducer.test.js +102 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +1 -5
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -13
- package/v2Containers/CreativesContainer/index.js +15 -30
- package/v2Containers/Email/index.js +5 -1
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +70 -23
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +137 -29
- package/v2Containers/FTP/index.js +51 -2
- package/v2Containers/FTP/messages.js +4 -0
- package/v2Containers/InApp/index.js +104 -4
- package/v2Containers/InApp/tests/index.test.js +6 -17
- package/v2Containers/InappAdvance/index.js +108 -4
- package/v2Containers/InappAdvance/tests/index.test.js +0 -2
- package/v2Containers/Line/Container/Text/index.js +1 -0
- package/v2Containers/MobilePush/Create/index.js +19 -42
- package/v2Containers/MobilePush/Edit/index.js +19 -42
- package/v2Containers/MobilePushNew/index.js +32 -12
- package/v2Containers/MobilepushWrapper/index.js +1 -3
- package/v2Containers/Rcs/index.js +37 -12
- package/v2Containers/Sms/Create/index.js +3 -39
- package/v2Containers/Sms/Create/messages.js +0 -4
- package/v2Containers/Sms/Edit/index.js +3 -35
- package/v2Containers/Sms/commonMethods.js +6 -3
- package/v2Containers/SmsTrai/Edit/index.js +47 -11
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/SmsWrapper/index.js +0 -2
- package/v2Containers/Viber/index.js +1 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/utils/validation.js +2 -17
- package/v2Containers/WebPush/Create/utils/validation.test.js +24 -59
- package/v2Containers/Whatsapp/index.js +17 -9
- package/v2Containers/Zalo/index.js +11 -3
- 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
|
-
//
|
|
1093
|
-
|
|
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,
|
|
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 (
|
|
33
|
-
setTimeout
|
|
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
|
-
//
|
|
819
|
-
|
|
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
|
-
|
|
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',
|
|
@@ -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
|
|
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
|
-
|
|
99
|
-
|
|
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
|
|
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]
|
|
718
|
-
const isIosInvalid = Object.values(errorData[1]
|
|
719
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
134
|
-
|
|
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
|
|
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]
|
|
745
|
-
const isIosInvalid = Object.values(errorData[1]
|
|
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
|
-
|
|
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,
|