@capillarytech/creatives-library 8.0.291 → 8.0.292-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +3 -1
- package/initialState.js +0 -2
- package/package.json +1 -1
- package/utils/common.js +5 -8
- package/utils/commonUtils.js +4 -85
- package/utils/tagValidations.js +83 -223
- package/utils/tests/commonUtil.test.js +147 -124
- package/utils/tests/tagValidations.test.js +441 -358
- package/v2Components/ErrorInfoNote/index.js +2 -5
- package/v2Components/FormBuilder/index.js +137 -203
- package/v2Components/FormBuilder/messages.js +0 -8
- package/v2Components/HtmlEditor/HTMLEditor.js +0 -5
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +0 -15
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +1 -2
- package/v2Containers/Cap/mockData.js +0 -14
- package/v2Containers/Cap/reducer.js +3 -55
- package/v2Containers/Cap/tests/reducer.test.js +0 -102
- package/v2Containers/CreativesContainer/SlideBoxContent.js +5 -1
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -5
- package/v2Containers/CreativesContainer/index.js +30 -7
- package/v2Containers/Email/index.js +1 -5
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +23 -70
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +29 -137
- package/v2Containers/FTP/index.js +2 -51
- package/v2Containers/FTP/messages.js +0 -4
- package/v2Containers/InApp/index.js +4 -104
- package/v2Containers/InApp/tests/index.test.js +17 -6
- package/v2Containers/InappAdvance/index.js +4 -108
- package/v2Containers/InappAdvance/tests/index.test.js +2 -0
- package/v2Containers/Line/Container/Text/index.js +0 -1
- package/v2Containers/MobilePush/Create/index.js +42 -19
- package/v2Containers/MobilePush/Edit/index.js +42 -19
- package/v2Containers/MobilePushNew/index.js +12 -32
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/index.js +12 -37
- package/v2Containers/Sms/Create/index.js +39 -3
- package/v2Containers/Sms/Create/messages.js +4 -0
- package/v2Containers/Sms/Edit/index.js +35 -3
- package/v2Containers/Sms/commonMethods.js +3 -6
- package/v2Containers/Sms/tests/commonMethods.test.js +122 -0
- package/v2Containers/SmsTrai/Edit/index.js +11 -47
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/SmsWrapper/index.js +2 -0
- package/v2Containers/Viber/index.js +0 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/utils/validation.js +17 -2
- package/v2Containers/WebPush/Create/utils/validation.test.js +59 -24
- package/v2Containers/Whatsapp/index.js +9 -17
- package/v2Containers/Zalo/index.js +3 -11
|
@@ -21,10 +21,6 @@ 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
|
-
},
|
|
28
24
|
selectTag: {
|
|
29
25
|
id: 'creatives.containersV2.FTP.selectTag',
|
|
30
26
|
defaultMessage: 'Select tag',
|
|
@@ -31,7 +31,6 @@ 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";
|
|
35
34
|
import injectReducer from '../../utils/injectReducer';
|
|
36
35
|
import v2InAppReducer from './reducer';
|
|
37
36
|
import { v2InAppSagas } from './sagas';
|
|
@@ -58,7 +57,6 @@ import {
|
|
|
58
57
|
import { getCdnUrl } from "../../utils/cdnTransformation";
|
|
59
58
|
import { getCtaObject, hasAnyErrors, getSingleTab } from "./utils";
|
|
60
59
|
import { validateInAppContent } from "../../utils/commonUtils";
|
|
61
|
-
import { hasLiquidSupportFeature } from "../../utils/common";
|
|
62
60
|
import formBuilderMessages from "../../v2Components/FormBuilder/messages";
|
|
63
61
|
import HTMLEditor from "../../v2Components/HtmlEditor";
|
|
64
62
|
import { HTML_EDITOR_VARIANTS } from "../../v2Components/HtmlEditor/constants";
|
|
@@ -1079,8 +1077,8 @@ export const InApp = (props) => {
|
|
|
1079
1077
|
};
|
|
1080
1078
|
|
|
1081
1079
|
// Validation middleware for tag validation (both liquid and non-liquid flow)
|
|
1080
|
+
// Liquid validation (extractTags) runs only in library mode
|
|
1082
1081
|
const validationMiddleWare = async () => {
|
|
1083
|
-
// Set up callbacks for validation results
|
|
1084
1082
|
const onError = ({ standardErrors, liquidErrors }) => {
|
|
1085
1083
|
setErrorMessage((prev) => ({
|
|
1086
1084
|
STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...standardErrors },
|
|
@@ -1088,30 +1086,20 @@ export const InApp = (props) => {
|
|
|
1088
1086
|
}));
|
|
1089
1087
|
};
|
|
1090
1088
|
const onSuccess = () => {
|
|
1091
|
-
// Proceed with submission when validation is successful
|
|
1092
1089
|
onDoneCallback();
|
|
1093
1090
|
};
|
|
1094
1091
|
|
|
1095
|
-
//
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
// For liquid flow, use validateInAppContent
|
|
1099
|
-
if (isLiquidFlow && hasTags) {
|
|
1100
|
-
// Validate the INAPP content
|
|
1092
|
+
// Library mode: run extractTags validation (always, so we catch liquid errors even when tags not loaded)
|
|
1093
|
+
if (!isFullMode) {
|
|
1101
1094
|
const payload = createPayload();
|
|
1102
1095
|
validateInAppContent(payload, {
|
|
1103
|
-
currentTab: panes === ANDROID ? 1 : 2,
|
|
1096
|
+
currentTab: panes === ANDROID ? 1 : 2,
|
|
1104
1097
|
onError,
|
|
1105
1098
|
onSuccess,
|
|
1106
1099
|
getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
|
|
1107
1100
|
formatMessage,
|
|
1108
1101
|
messages: formBuilderMessages,
|
|
1109
|
-
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
1110
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1111
|
-
isLiquidFlow,
|
|
1112
|
-
forwardedTags: {},
|
|
1113
1102
|
skipTags: (tag) => {
|
|
1114
|
-
// Skip certain tags if needed
|
|
1115
1103
|
const skipRegexes = [
|
|
1116
1104
|
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
1117
1105
|
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
@@ -1119,103 +1107,15 @@ export const InApp = (props) => {
|
|
|
1119
1107
|
/SURVEY.*\.TOKEN/,
|
|
1120
1108
|
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
1121
1109
|
];
|
|
1122
|
-
|
|
1123
1110
|
return skipRegexes.some((regex) => regex.test(tag));
|
|
1124
1111
|
},
|
|
1125
1112
|
singleTab: getSingleTab(accountData),
|
|
1126
1113
|
});
|
|
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
|
-
}
|
|
1211
1114
|
} else {
|
|
1212
|
-
// No tags available, skip validation and proceed directly
|
|
1213
1115
|
onSuccess();
|
|
1214
1116
|
}
|
|
1215
1117
|
};
|
|
1216
1118
|
|
|
1217
|
-
const isLiquidFlow = hasLiquidSupportFeature();
|
|
1218
|
-
|
|
1219
1119
|
// Check template data to determine editor type (for render decision)
|
|
1220
1120
|
const templateDetails = isFullMode ? editData?.templateDetails : templateData;
|
|
1221
1121
|
const versions = templateDetails?.versions || {};
|
|
@@ -21,18 +21,16 @@ 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';
|
|
24
25
|
|
|
25
26
|
const mockActions = {
|
|
26
27
|
getTemplateInfoById: jest.fn(),
|
|
27
28
|
resetEditTemplate: jest.fn(),
|
|
28
|
-
getTemplateDetails: jest.fn((id,
|
|
29
|
+
getTemplateDetails: jest.fn((id, setSpin) => {
|
|
29
30
|
// Simulate successful template details fetch to prevent loading state
|
|
30
31
|
// The callback is setSpin function, call it with false to stop spinner
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
setTimeout(() => {
|
|
34
|
-
callback(false);
|
|
35
|
-
}, 0);
|
|
32
|
+
if (setSpin && typeof setSpin === 'function') {
|
|
33
|
+
setTimeout(() => setSpin(false), 0);
|
|
36
34
|
}
|
|
37
35
|
}),
|
|
38
36
|
editTemplate: jest.fn(),
|
|
@@ -41,6 +39,9 @@ const mockActions = {
|
|
|
41
39
|
};
|
|
42
40
|
const mockGlobalActions = {
|
|
43
41
|
fetchSchemaForEntity: jest.fn(),
|
|
42
|
+
getLiquidTags: jest.fn((content, callback) =>
|
|
43
|
+
callback({ askAiraResponse: { data: [], errors: [] }, isError: false }),
|
|
44
|
+
),
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
jest.mock('../../../v2Containers/TagList/index.js', () => ({
|
|
@@ -66,7 +67,17 @@ const renderComponent = (props) =>
|
|
|
66
67
|
);
|
|
67
68
|
|
|
68
69
|
describe('Test activity inApp container', () => {
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
jest.restoreAllMocks();
|
|
72
|
+
});
|
|
73
|
+
|
|
69
74
|
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
|
+
});
|
|
70
81
|
renderComponent({
|
|
71
82
|
actions: mockActions,
|
|
72
83
|
globalActions: mockGlobalActions,
|
|
@@ -50,9 +50,7 @@ import injectReducer from '../../utils/injectReducer';
|
|
|
50
50
|
import v2InAppReducer from '../InApp/reducer';
|
|
51
51
|
import { v2InAppSagas } from '../InApp/sagas';
|
|
52
52
|
import injectSaga from '../../utils/injectSaga';
|
|
53
|
-
import { validateTags } from "../../utils/tagValidations";
|
|
54
53
|
import { validateInAppContent } from "../../utils/commonUtils";
|
|
55
|
-
import { hasLiquidSupportFeature } from "../../utils/common";
|
|
56
54
|
import formBuilderMessages from "../../v2Components/FormBuilder/messages";
|
|
57
55
|
import { getSingleTab, hasAnyErrors } from "../InApp/utils";
|
|
58
56
|
import ErrorInfoNote from "../../v2Components/ErrorInfoNote";
|
|
@@ -817,24 +815,16 @@ export const InappAdvanced = (props) => {
|
|
|
817
815
|
const latestHtmlValues = await saveAllBeeInstances();
|
|
818
816
|
const payload = createPayload(latestHtmlValues);
|
|
819
817
|
|
|
820
|
-
//
|
|
821
|
-
|
|
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) {
|
|
818
|
+
// Liquid validation (extractTags) only in library mode
|
|
819
|
+
if (!isFullMode) {
|
|
825
820
|
validateInAppContent(payload, {
|
|
826
|
-
currentTab: panes === ANDROID ? 1 : 2,
|
|
821
|
+
currentTab: panes === ANDROID ? 1 : 2,
|
|
827
822
|
onError,
|
|
828
823
|
onSuccess,
|
|
829
824
|
getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
|
|
830
825
|
formatMessage,
|
|
831
826
|
messages: formBuilderMessages,
|
|
832
|
-
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
833
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
834
|
-
isLiquidFlow,
|
|
835
|
-
forwardedTags: {},
|
|
836
827
|
skipTags: (tag) => {
|
|
837
|
-
// Skip certain tags if needed
|
|
838
828
|
const skipRegexes = [
|
|
839
829
|
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
840
830
|
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
@@ -842,97 +832,11 @@ export const InappAdvanced = (props) => {
|
|
|
842
832
|
/SURVEY.*\.TOKEN/,
|
|
843
833
|
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
844
834
|
];
|
|
845
|
-
|
|
846
835
|
return skipRegexes.some((regex) => regex.test(tag));
|
|
847
836
|
},
|
|
848
837
|
singleTab: getSingleTab(accountData),
|
|
849
838
|
});
|
|
850
|
-
} else if (hasTags) {
|
|
851
|
-
// For non-liquid flow, validate tags using validateTags (only if tags are available)
|
|
852
|
-
const androidContent = latestHtmlValues?.android || (androidBeeHtml?.value || (typeof androidBeeHtml === 'string' ? androidBeeHtml : ''));
|
|
853
|
-
const iosContent = latestHtmlValues?.ios || (iosBeeHtml?.value || (typeof iosBeeHtml === 'string' ? iosBeeHtml : ''));
|
|
854
|
-
|
|
855
|
-
const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
|
|
856
|
-
const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
|
|
857
|
-
|
|
858
|
-
let hasErrors = false;
|
|
859
|
-
const newErrors = {
|
|
860
|
-
STANDARD_ERROR_MSG: {
|
|
861
|
-
ANDROID: [],
|
|
862
|
-
IOS: [],
|
|
863
|
-
GENERIC: [],
|
|
864
|
-
},
|
|
865
|
-
LIQUID_ERROR_MSG: {
|
|
866
|
-
ANDROID: [],
|
|
867
|
-
IOS: [],
|
|
868
|
-
GENERIC: [],
|
|
869
|
-
},
|
|
870
|
-
};
|
|
871
|
-
|
|
872
|
-
// Validate Android content
|
|
873
|
-
if (androidSupported && androidContent && androidContent?.trim() !== '') {
|
|
874
|
-
const validationResponse = validateTags({
|
|
875
|
-
content: androidContent,
|
|
876
|
-
tagsParam: tags,
|
|
877
|
-
injectedTagsParams: injectedTags || {},
|
|
878
|
-
location,
|
|
879
|
-
tagModule: getDefaultTags,
|
|
880
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
881
|
-
isFullMode,
|
|
882
|
-
}) || {};
|
|
883
|
-
|
|
884
|
-
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
885
|
-
hasErrors = true;
|
|
886
|
-
newErrors.LIQUID_ERROR_MSG.ANDROID.push(
|
|
887
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
888
|
-
unsupportedTags: validationResponse.unsupportedTags.join(", "),
|
|
889
|
-
})
|
|
890
|
-
);
|
|
891
|
-
}
|
|
892
|
-
if (validationResponse?.isBraceError) {
|
|
893
|
-
hasErrors = true;
|
|
894
|
-
newErrors.LIQUID_ERROR_MSG.ANDROID.push(
|
|
895
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)
|
|
896
|
-
);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
// Validate iOS content
|
|
901
|
-
if (iosSupported && iosContent && iosContent?.trim() !== '') {
|
|
902
|
-
const validationResponse = validateTags({
|
|
903
|
-
content: iosContent,
|
|
904
|
-
tagsParam: tags,
|
|
905
|
-
injectedTagsParams: injectedTags || {},
|
|
906
|
-
location,
|
|
907
|
-
tagModule: getDefaultTags,
|
|
908
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
909
|
-
isFullMode,
|
|
910
|
-
}) || {};
|
|
911
|
-
|
|
912
|
-
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
913
|
-
hasErrors = true;
|
|
914
|
-
newErrors.LIQUID_ERROR_MSG.IOS.push(
|
|
915
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
916
|
-
unsupportedTags: validationResponse.unsupportedTags.join(", "),
|
|
917
|
-
})
|
|
918
|
-
);
|
|
919
|
-
}
|
|
920
|
-
if (validationResponse?.isBraceError) {
|
|
921
|
-
hasErrors = true;
|
|
922
|
-
newErrors.LIQUID_ERROR_MSG.IOS.push(
|
|
923
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)
|
|
924
|
-
);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
if (hasErrors) {
|
|
929
|
-
setErrorMessage(newErrors);
|
|
930
|
-
} else {
|
|
931
|
-
// No errors, proceed with submission
|
|
932
|
-
onSuccess();
|
|
933
|
-
}
|
|
934
839
|
} else {
|
|
935
|
-
// No tags available, skip validation and proceed directly
|
|
936
840
|
onSuccess();
|
|
937
841
|
}
|
|
938
842
|
};
|
|
@@ -1038,15 +942,7 @@ export const InappAdvanced = (props) => {
|
|
|
1038
942
|
)}
|
|
1039
943
|
<CapButton
|
|
1040
944
|
onClick={async () => {
|
|
1041
|
-
|
|
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
|
-
}
|
|
945
|
+
await liquidMiddleWare();
|
|
1050
946
|
}}
|
|
1051
947
|
disabled={isDisableDone()}
|
|
1052
948
|
className="inapp-create-btn"
|
|
@@ -61,6 +61,7 @@ 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 })),
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
defaultProps = {
|
|
@@ -409,6 +410,7 @@ describe('InappAdvanced Component', () => {
|
|
|
409
410
|
actions: mockActions,
|
|
410
411
|
globalActions: {
|
|
411
412
|
fetchSchemaForEntity: jest.fn(),
|
|
413
|
+
getLiquidTags: jest.fn((content, callback) => callback({ askAiraResponse: { data: [] }, isError: false })),
|
|
412
414
|
},
|
|
413
415
|
location: {
|
|
414
416
|
pathname: '/inapp/create',
|
|
@@ -38,7 +38,8 @@ 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 } from '../../../utils/commonUtils';
|
|
41
|
+
import { checkForPersonalizationTokens, validateMobilePushContent } from '../../../utils/commonUtils';
|
|
42
|
+
import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
|
|
42
43
|
|
|
43
44
|
const PrefixWrapper = styled.div`
|
|
44
45
|
margin-right: 16px;
|
|
@@ -94,8 +95,39 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
94
95
|
this.props.globalActions.fetchSchemaForEntity(query);
|
|
95
96
|
};
|
|
96
97
|
componentWillReceiveProps = (nextProps) => {
|
|
97
|
-
|
|
98
|
-
|
|
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
|
+
}
|
|
99
131
|
} else if (nextProps.isGetFormData && this.props.isGetFormData !== nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
|
|
100
132
|
this.startValidation();
|
|
101
133
|
}
|
|
@@ -678,31 +710,21 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
678
710
|
}
|
|
679
711
|
showError = () => {
|
|
680
712
|
const {intl} = this.props;
|
|
681
|
-
const {errorData} = this.state;
|
|
713
|
+
const {errorData, schema} = this.state;
|
|
682
714
|
const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
|
|
683
715
|
if (!isEmpty(this.state.formData) && !this.state.isFormValid) {
|
|
684
716
|
let tab = this.state.currentTab;
|
|
685
|
-
const isAndroidInvalid = Object.values(errorData[0]).includes(true);
|
|
686
|
-
const isIosInvalid = Object.values(errorData[1]).includes(true);
|
|
687
|
-
|
|
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;
|
|
688
720
|
if (isAndroidInvalid) {
|
|
689
721
|
tab = 1;
|
|
690
722
|
errorMessage.description = intl.formatMessage(messages.invalidAndroidMessage);
|
|
691
|
-
|
|
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) {
|
|
723
|
+
} else if (isIosInvalid && isIosTabVisible) {
|
|
697
724
|
tab = 2;
|
|
698
725
|
errorMessage.description = intl.formatMessage(messages.invalidIosMessage);
|
|
699
|
-
const invalidTags = errorData[1]['invalid-tags'];
|
|
700
|
-
if (!isEmpty(invalidTags)) {
|
|
701
|
-
isTagErrorExist = true;
|
|
702
|
-
errorMessage.description = `${intl.formatMessage(messages.invalidIosMessage)} ${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
|
|
703
|
-
}
|
|
704
726
|
}
|
|
705
|
-
if (tab !== this.state.currentTab
|
|
727
|
+
if (tab !== this.state.currentTab) {
|
|
706
728
|
CapNotification.error(errorMessage);
|
|
707
729
|
}
|
|
708
730
|
}
|
|
@@ -2029,6 +2051,7 @@ Create.propTypes = {
|
|
|
2029
2051
|
onPreviewContentClicked: PropTypes.func,
|
|
2030
2052
|
onTestContentClicked: PropTypes.func,
|
|
2031
2053
|
eventContextTags: PropTypes.array,
|
|
2054
|
+
getLiquidTags: PropTypes.func,
|
|
2032
2055
|
showLiquidErrorInFooter: PropTypes.func,
|
|
2033
2056
|
showTestAndPreviewSlidebox: PropTypes.bool,
|
|
2034
2057
|
handleTestAndPreview: PropTypes.func,
|
|
@@ -39,7 +39,8 @@ 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 } from '../../../utils/commonUtils';
|
|
42
|
+
import { checkForPersonalizationTokens, isEmbeddedEditOrPreview, validateMobilePushContent } from '../../../utils/commonUtils';
|
|
43
|
+
import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
|
|
43
44
|
import { EMBEDDED } from '../../Whatsapp/constants';
|
|
44
45
|
import { OUTBOUND } from '../../../v2Components/FormBuilder/constants';
|
|
45
46
|
import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
|
|
@@ -129,8 +130,39 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
129
130
|
this.hasFetchedInitialTagsRef = false;
|
|
130
131
|
this.lastFetchedTagContextRef = null;
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
}
|
|
134
166
|
} else if (nextProps.isGetFormData && this.props.isGetFormData !== nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
|
|
135
167
|
this.startValidation();
|
|
136
168
|
}
|
|
@@ -705,32 +737,22 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
705
737
|
// eslint-disable-next-line react/sort-comp
|
|
706
738
|
showError = () => {
|
|
707
739
|
const {intl} = this.props;
|
|
708
|
-
const {errorData} = this.state;
|
|
740
|
+
const {errorData, schema} = this.state;
|
|
709
741
|
const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
|
|
710
|
-
let isTagErrorExist = false;
|
|
711
742
|
if (!_.isEmpty(this.state.formData) && !this.state.isFormValid) {
|
|
712
743
|
let tab = this.state.currentTab;
|
|
713
|
-
const isAndroidInvalid = Object.values(errorData[0]).includes(true);
|
|
714
|
-
const isIosInvalid = Object.values(errorData[1]).includes(true);
|
|
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;
|
|
715
747
|
if (isAndroidInvalid) {
|
|
716
748
|
tab = 1;
|
|
717
749
|
errorMessage.description = intl.formatMessage(messages.invalidAndroidMessage);
|
|
718
|
-
|
|
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) {
|
|
750
|
+
} else if (isIosInvalid && isIosTabVisible) {
|
|
724
751
|
tab = 2;
|
|
725
752
|
errorMessage.description = intl.formatMessage(messages.invalidIosMessage);
|
|
726
|
-
const invalidTags = errorData[1]['invalid-tags'];
|
|
727
|
-
if (!_.isEmpty(invalidTags)) {
|
|
728
|
-
isTagErrorExist = true;
|
|
729
|
-
errorMessage.description = `${intl.formatMessage(messages.invalidIosMessage)} ${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
|
|
730
|
-
}
|
|
731
753
|
}
|
|
732
754
|
|
|
733
|
-
if (tab !== this.state.currentTab
|
|
755
|
+
if (tab !== this.state.currentTab) {
|
|
734
756
|
CapNotification.error(errorMessage);
|
|
735
757
|
}
|
|
736
758
|
}
|
|
@@ -2312,6 +2334,7 @@ Edit.propTypes = {
|
|
|
2312
2334
|
onTestContentClicked: PropTypes.func,
|
|
2313
2335
|
creativesMode: PropTypes.string,
|
|
2314
2336
|
eventContextTags: PropTypes.array,
|
|
2337
|
+
getLiquidTags: PropTypes.func,
|
|
2315
2338
|
showLiquidErrorInFooter: PropTypes.func,
|
|
2316
2339
|
showTestAndPreviewSlidebox: PropTypes.bool,
|
|
2317
2340
|
handleTestAndPreview: PropTypes.func,
|