@capillarytech/creatives-library 8.0.307 → 8.0.308
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 -5
- package/initialState.js +2 -0
- package/package.json +1 -1
- package/utils/common.js +8 -5
- package/utils/commonUtils.js +93 -36
- 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 +203 -137
- 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/constants.js +0 -6
- package/v2Containers/CreativesContainer/index.js +7 -47
- package/v2Containers/Email/index.js +5 -1
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +70 -23
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +120 -20
- package/v2Containers/FTP/index.js +51 -2
- package/v2Containers/FTP/messages.js +4 -0
- package/v2Containers/InApp/index.js +122 -35
- package/v2Containers/InApp/tests/index.test.js +6 -17
- package/v2Containers/InappAdvance/index.js +112 -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 -59
- package/v2Containers/MobilePush/Edit/index.js +20 -48
- 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/TemplatesV2/index.js +13 -28
- 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 +8 -17
- package/v2Containers/WebPush/Create/utils/validation.test.js +24 -44
- package/v2Containers/Whatsapp/index.js +17 -9
- package/v2Containers/Zalo/index.js +11 -3
- package/v2Containers/Sms/tests/commonMethods.test.js +0 -122
|
@@ -33,6 +33,7 @@ import creativesMessages from '../CreativesContainer/messages';
|
|
|
33
33
|
import withCreatives from "../../hoc/withCreatives";
|
|
34
34
|
import UnifiedPreview from "../../v2Components/CommonTestAndPreview/UnifiedPreview";
|
|
35
35
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
36
|
+
import { validateTags } from "../../utils/tagValidations";
|
|
36
37
|
import injectReducer from '../../utils/injectReducer';
|
|
37
38
|
import v2InAppReducer from './reducer';
|
|
38
39
|
import { v2InAppSagas } from './sagas';
|
|
@@ -52,7 +53,7 @@ import {
|
|
|
52
53
|
LAYOUT_RADIO_OPTIONS,
|
|
53
54
|
IOS_CAPITAL,
|
|
54
55
|
} from "./constants";
|
|
55
|
-
import {
|
|
56
|
+
import { INAPP, SMS } from "../CreativesContainer/constants";
|
|
56
57
|
import { AI_CONTENT_BOT_DISABLED } from "../../constants/unified";
|
|
57
58
|
import {
|
|
58
59
|
ALL, TAG, EMBEDDED, DEFAULT, FULL, LIBRARY,
|
|
@@ -60,8 +61,7 @@ import {
|
|
|
60
61
|
import { getCdnUrl } from "../../utils/cdnTransformation";
|
|
61
62
|
import { getCtaObject, hasAnyErrors, getSingleTab } from "./utils";
|
|
62
63
|
import { validateInAppContent } from "../../utils/commonUtils";
|
|
63
|
-
import {
|
|
64
|
-
import { hasNewEditorFlowInAppEnabled } from "../../utils/common";
|
|
64
|
+
import { hasLiquidSupportFeature, hasNewEditorFlowInAppEnabled } from "../../utils/common";
|
|
65
65
|
import formBuilderMessages from "../../v2Components/FormBuilder/messages";
|
|
66
66
|
import HTMLEditor from "../../v2Components/HtmlEditor";
|
|
67
67
|
import { HTML_EDITOR_VARIANTS } from "../../v2Components/HtmlEditor/constants";
|
|
@@ -1084,55 +1084,39 @@ export const InApp = (props) => {
|
|
|
1084
1084
|
};
|
|
1085
1085
|
|
|
1086
1086
|
// Validation middleware for tag validation (both liquid and non-liquid flow)
|
|
1087
|
-
// Liquid validation (extractTags) runs only in library mode
|
|
1088
1087
|
const validationMiddleWare = async () => {
|
|
1089
|
-
//
|
|
1090
|
-
// so we don't merge e.g. 'android'/'ios'/'generic' with ANDROID/IOS/GENERIC and get duplicate/stale keys
|
|
1091
|
-
const normalizeErrorBuckets = (errors) => {
|
|
1092
|
-
const normalized = {
|
|
1093
|
-
[ANDROID]: [],
|
|
1094
|
-
[IOS_CAPITAL]: [],
|
|
1095
|
-
[GENERIC]: [],
|
|
1096
|
-
};
|
|
1097
|
-
if (!errors || typeof errors !== 'object') return normalized;
|
|
1098
|
-
const keyMap = {
|
|
1099
|
-
ANDROID,
|
|
1100
|
-
android: ANDROID,
|
|
1101
|
-
[IOS_CAPITAL]: IOS_CAPITAL,
|
|
1102
|
-
[IOS]: IOS_CAPITAL,
|
|
1103
|
-
ios: IOS_CAPITAL,
|
|
1104
|
-
GENERIC,
|
|
1105
|
-
generic: GENERIC,
|
|
1106
|
-
};
|
|
1107
|
-
for (const [key, value] of Object.entries(errors)) {
|
|
1108
|
-
const targetKey = keyMap[key];
|
|
1109
|
-
if (targetKey != null && Array.isArray(value)) {
|
|
1110
|
-
normalized[targetKey] = value;
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
return normalized;
|
|
1114
|
-
};
|
|
1088
|
+
// Set up callbacks for validation results
|
|
1115
1089
|
const onError = ({ standardErrors, liquidErrors }) => {
|
|
1116
1090
|
setErrorMessage((prev) => ({
|
|
1117
|
-
STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...
|
|
1118
|
-
LIQUID_ERROR_MSG: { ...prev.LIQUID_ERROR_MSG, ...
|
|
1091
|
+
STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...standardErrors },
|
|
1092
|
+
LIQUID_ERROR_MSG: { ...prev.LIQUID_ERROR_MSG, ...liquidErrors },
|
|
1119
1093
|
}));
|
|
1120
1094
|
};
|
|
1121
1095
|
const onSuccess = () => {
|
|
1096
|
+
// Proceed with submission when validation is successful
|
|
1122
1097
|
onDoneCallback();
|
|
1123
1098
|
};
|
|
1124
1099
|
|
|
1125
|
-
//
|
|
1126
|
-
|
|
1100
|
+
// Skip validation if no tags are available (e.g., in tests or when tags haven't loaded)
|
|
1101
|
+
const hasTags = tags && tags.length > 0;
|
|
1102
|
+
|
|
1103
|
+
// For liquid flow, use validateInAppContent
|
|
1104
|
+
if (isLiquidFlow && hasTags) {
|
|
1105
|
+
// Validate the INAPP content
|
|
1127
1106
|
const payload = createPayload();
|
|
1128
1107
|
validateInAppContent(payload, {
|
|
1129
|
-
currentTab: panes === ANDROID ? 1 : 2,
|
|
1108
|
+
currentTab: panes === ANDROID ? 1 : 2, // Convert ANDROID/IOS to tab numbers
|
|
1130
1109
|
onError,
|
|
1131
1110
|
onSuccess,
|
|
1132
1111
|
getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
|
|
1133
1112
|
formatMessage,
|
|
1134
1113
|
messages: formBuilderMessages,
|
|
1114
|
+
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
1115
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1116
|
+
isLiquidFlow,
|
|
1117
|
+
forwardedTags: {},
|
|
1135
1118
|
skipTags: (tag) => {
|
|
1119
|
+
// Skip certain tags if needed
|
|
1136
1120
|
const skipRegexes = [
|
|
1137
1121
|
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
1138
1122
|
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
@@ -1140,15 +1124,103 @@ export const InApp = (props) => {
|
|
|
1140
1124
|
/SURVEY.*\.TOKEN/,
|
|
1141
1125
|
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
1142
1126
|
];
|
|
1127
|
+
|
|
1143
1128
|
return skipRegexes.some((regex) => regex.test(tag));
|
|
1144
1129
|
},
|
|
1145
1130
|
singleTab: getSingleTab(accountData),
|
|
1146
1131
|
});
|
|
1132
|
+
} else if (hasTags) {
|
|
1133
|
+
// For non-liquid flow, validate tags using validateTags (only if tags are available)
|
|
1134
|
+
const androidContent = htmlContentAndroid || '';
|
|
1135
|
+
const iosContent = htmlContentIos || '';
|
|
1136
|
+
|
|
1137
|
+
const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
|
|
1138
|
+
const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
|
|
1139
|
+
|
|
1140
|
+
let hasErrors = false;
|
|
1141
|
+
const newErrors = {
|
|
1142
|
+
STANDARD_ERROR_MSG: {
|
|
1143
|
+
ANDROID: [],
|
|
1144
|
+
IOS: [],
|
|
1145
|
+
GENERIC: [],
|
|
1146
|
+
},
|
|
1147
|
+
LIQUID_ERROR_MSG: {
|
|
1148
|
+
ANDROID: [],
|
|
1149
|
+
IOS: [],
|
|
1150
|
+
GENERIC: [],
|
|
1151
|
+
},
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
// Validate Android content
|
|
1155
|
+
if (androidSupported && androidContent && androidContent?.trim() !== '') {
|
|
1156
|
+
const validationResponse = validateTags({
|
|
1157
|
+
content: androidContent,
|
|
1158
|
+
tagsParam: tags,
|
|
1159
|
+
injectedTagsParams: injectedTags || {},
|
|
1160
|
+
location,
|
|
1161
|
+
tagModule: getDefaultTags,
|
|
1162
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1163
|
+
isFullMode,
|
|
1164
|
+
}) || {};
|
|
1165
|
+
|
|
1166
|
+
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
1167
|
+
hasErrors = true;
|
|
1168
|
+
newErrors.LIQUID_ERROR_MSG.ANDROID.push(
|
|
1169
|
+
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1170
|
+
unsupportedTags: validationResponse.unsupportedTags.join(", "),
|
|
1171
|
+
})
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
if (validationResponse?.isBraceError) {
|
|
1175
|
+
hasErrors = true;
|
|
1176
|
+
newErrors.LIQUID_ERROR_MSG.ANDROID.push(
|
|
1177
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Validate iOS content
|
|
1183
|
+
if (iosSupported && iosContent && iosContent?.trim() !== '') {
|
|
1184
|
+
const validationResponse = validateTags({
|
|
1185
|
+
content: iosContent,
|
|
1186
|
+
tagsParam: tags,
|
|
1187
|
+
injectedTagsParams: injectedTags || {},
|
|
1188
|
+
location,
|
|
1189
|
+
tagModule: getDefaultTags,
|
|
1190
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1191
|
+
isFullMode,
|
|
1192
|
+
}) || {};
|
|
1193
|
+
|
|
1194
|
+
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
1195
|
+
hasErrors = true;
|
|
1196
|
+
newErrors.LIQUID_ERROR_MSG.IOS.push(
|
|
1197
|
+
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1198
|
+
unsupportedTags: validationResponse.unsupportedTags.join(", "),
|
|
1199
|
+
})
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
if (validationResponse?.isBraceError) {
|
|
1203
|
+
hasErrors = true;
|
|
1204
|
+
newErrors.LIQUID_ERROR_MSG.IOS.push(
|
|
1205
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
if (hasErrors) {
|
|
1211
|
+
setErrorMessage(newErrors);
|
|
1212
|
+
} else {
|
|
1213
|
+
// No errors, proceed with submission
|
|
1214
|
+
onSuccess();
|
|
1215
|
+
}
|
|
1147
1216
|
} else {
|
|
1217
|
+
// No tags available, skip validation and proceed directly
|
|
1148
1218
|
onSuccess();
|
|
1149
1219
|
}
|
|
1150
1220
|
};
|
|
1151
1221
|
|
|
1222
|
+
const isLiquidFlow = hasLiquidSupportFeature();
|
|
1223
|
+
|
|
1152
1224
|
// Check template data to determine editor type (for render decision)
|
|
1153
1225
|
const templateDetails = isFullMode ? editData?.templateDetails : templateData;
|
|
1154
1226
|
const versions = templateDetails?.versions || {};
|
|
@@ -1445,6 +1517,21 @@ export const InApp = (props) => {
|
|
|
1445
1517
|
labelPosition="top"
|
|
1446
1518
|
size="default"
|
|
1447
1519
|
/>
|
|
1520
|
+
<CapRow>
|
|
1521
|
+
<CapHeading type="h4">
|
|
1522
|
+
<FormattedMessage {...messages.creativeLayout} />
|
|
1523
|
+
</CapHeading>
|
|
1524
|
+
<CapHeading type="h6" className="inapp-creative-layout-desc">
|
|
1525
|
+
<FormattedMessage {...messages.creativeLayoutDesc} />
|
|
1526
|
+
</CapHeading>
|
|
1527
|
+
</CapRow>
|
|
1528
|
+
<CapRadioGroup
|
|
1529
|
+
id="inapp-layout-radio"
|
|
1530
|
+
options={LAYOUT_RADIO_OPTIONS}
|
|
1531
|
+
value={templateLayoutType}
|
|
1532
|
+
onChange={onTemplateLayoutTypeChange}
|
|
1533
|
+
className="inapp-layout-radio"
|
|
1534
|
+
/>
|
|
1448
1535
|
<CapTab
|
|
1449
1536
|
panes={DEVICE_PANES.filter((devicePane) => devicePane?.isSupported === true)}
|
|
1450
1537
|
onChange={(value) => setPanes(value)}
|
|
@@ -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";
|
|
@@ -816,18 +818,30 @@ export const InappAdvanced = (props) => {
|
|
|
816
818
|
const payload = createPayload(latestHtmlValues);
|
|
817
819
|
|
|
818
820
|
// Validate the INAPP content
|
|
821
|
+
const isLiquidFlow = hasLiquidSupportFeature();
|
|
819
822
|
// Skip validation if no tags are available (e.g., in tests or when tags haven't loaded)
|
|
820
823
|
const hasTags = tags && tags.length > 0;
|
|
821
824
|
|
|
822
|
-
|
|
823
|
-
|
|
825
|
+
// When liquid is enabled and isFullMode is true, skip liquid validation and proceed directly
|
|
826
|
+
if (isLiquidFlow && isFullMode) {
|
|
827
|
+
onSuccess();
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (isLiquidFlow && hasTags) {
|
|
832
|
+
validateInAppContent(payload, {
|
|
824
833
|
currentTab: panes === ANDROID ? 1 : 2, // Convert ANDROID/IOS to tab numbers
|
|
825
834
|
onError,
|
|
826
835
|
onSuccess,
|
|
827
836
|
getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
|
|
828
837
|
formatMessage,
|
|
829
838
|
messages: formBuilderMessages,
|
|
839
|
+
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
840
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
841
|
+
isLiquidFlow,
|
|
842
|
+
forwardedTags: {},
|
|
830
843
|
skipTags: (tag) => {
|
|
844
|
+
// Skip certain tags if needed
|
|
831
845
|
const skipRegexes = [
|
|
832
846
|
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
833
847
|
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
@@ -835,12 +849,98 @@ export const InappAdvanced = (props) => {
|
|
|
835
849
|
/SURVEY.*\.TOKEN/,
|
|
836
850
|
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
837
851
|
];
|
|
852
|
+
|
|
838
853
|
return skipRegexes.some((regex) => regex.test(tag));
|
|
839
854
|
},
|
|
840
855
|
singleTab: getSingleTab(accountData),
|
|
841
856
|
});
|
|
857
|
+
} else if (hasTags) {
|
|
858
|
+
// For non-liquid flow, validate tags using validateTags (only if tags are available)
|
|
859
|
+
const androidContent = latestHtmlValues?.android || (androidBeeHtml?.value || (typeof androidBeeHtml === 'string' ? androidBeeHtml : ''));
|
|
860
|
+
const iosContent = latestHtmlValues?.ios || (iosBeeHtml?.value || (typeof iosBeeHtml === 'string' ? iosBeeHtml : ''));
|
|
861
|
+
|
|
862
|
+
const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
|
|
863
|
+
const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
|
|
864
|
+
|
|
865
|
+
let hasErrors = false;
|
|
866
|
+
const newErrors = {
|
|
867
|
+
STANDARD_ERROR_MSG: {
|
|
868
|
+
ANDROID: [],
|
|
869
|
+
IOS: [],
|
|
870
|
+
GENERIC: [],
|
|
871
|
+
},
|
|
872
|
+
LIQUID_ERROR_MSG: {
|
|
873
|
+
ANDROID: [],
|
|
874
|
+
IOS: [],
|
|
875
|
+
GENERIC: [],
|
|
876
|
+
},
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
// Validate Android content
|
|
880
|
+
if (androidSupported && androidContent && androidContent?.trim() !== '') {
|
|
881
|
+
const validationResponse = validateTags({
|
|
882
|
+
content: androidContent,
|
|
883
|
+
tagsParam: tags,
|
|
884
|
+
injectedTagsParams: injectedTags || {},
|
|
885
|
+
location,
|
|
886
|
+
tagModule: getDefaultTags,
|
|
887
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
888
|
+
isFullMode,
|
|
889
|
+
}) || {};
|
|
890
|
+
|
|
891
|
+
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
892
|
+
hasErrors = true;
|
|
893
|
+
newErrors.LIQUID_ERROR_MSG.ANDROID.push(
|
|
894
|
+
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
895
|
+
unsupportedTags: validationResponse.unsupportedTags.join(", "),
|
|
896
|
+
})
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
if (validationResponse?.isBraceError) {
|
|
900
|
+
hasErrors = true;
|
|
901
|
+
newErrors.LIQUID_ERROR_MSG.ANDROID.push(
|
|
902
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Validate iOS content
|
|
908
|
+
if (iosSupported && iosContent && iosContent?.trim() !== '') {
|
|
909
|
+
const validationResponse = validateTags({
|
|
910
|
+
content: iosContent,
|
|
911
|
+
tagsParam: tags,
|
|
912
|
+
injectedTagsParams: injectedTags || {},
|
|
913
|
+
location,
|
|
914
|
+
tagModule: getDefaultTags,
|
|
915
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
916
|
+
isFullMode,
|
|
917
|
+
}) || {};
|
|
918
|
+
|
|
919
|
+
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
920
|
+
hasErrors = true;
|
|
921
|
+
newErrors.LIQUID_ERROR_MSG.IOS.push(
|
|
922
|
+
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
923
|
+
unsupportedTags: validationResponse.unsupportedTags.join(", "),
|
|
924
|
+
})
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
if (validationResponse?.isBraceError) {
|
|
928
|
+
hasErrors = true;
|
|
929
|
+
newErrors.LIQUID_ERROR_MSG.IOS.push(
|
|
930
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (hasErrors) {
|
|
936
|
+
setErrorMessage(newErrors);
|
|
937
|
+
} else {
|
|
938
|
+
// No errors, proceed with submission
|
|
939
|
+
onSuccess();
|
|
940
|
+
}
|
|
842
941
|
} else {
|
|
843
|
-
|
|
942
|
+
// No tags available, skip validation and proceed directly
|
|
943
|
+
onSuccess();
|
|
844
944
|
}
|
|
845
945
|
};
|
|
846
946
|
|
|
@@ -945,7 +1045,15 @@ export const InappAdvanced = (props) => {
|
|
|
945
1045
|
)}
|
|
946
1046
|
<CapButton
|
|
947
1047
|
onClick={async () => {
|
|
948
|
-
|
|
1048
|
+
const isLiquidFlow = hasLiquidSupportFeature();
|
|
1049
|
+
const hasTags = tags && tags?.length > 0;
|
|
1050
|
+
if (isLiquidFlow || hasTags) {
|
|
1051
|
+
// Use validation middleware for tag validation
|
|
1052
|
+
await liquidMiddleWare();
|
|
1053
|
+
} else {
|
|
1054
|
+
// No validation needed, proceed directly
|
|
1055
|
+
await onDoneCallback();
|
|
1056
|
+
}
|
|
949
1057
|
}}
|
|
950
1058
|
disabled={isDisableDone()}
|
|
951
1059
|
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,56 +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 or return payload; always return early
|
|
101
|
-
if (!this.state.isFormValid) {
|
|
102
|
-
if (typeof nextProps.onValidationFail === 'function') {
|
|
103
|
-
nextProps.onValidationFail();
|
|
104
|
-
}
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const hasAllValidationCallbacks =
|
|
108
|
-
typeof nextProps.getLiquidTags === 'function' &&
|
|
109
|
-
typeof nextProps.showLiquidErrorInFooter === 'function' &&
|
|
110
|
-
typeof nextProps.onValidationFail === 'function';
|
|
111
|
-
|
|
112
|
-
if (hasAllValidationCallbacks) {
|
|
113
|
-
const formDataArr = [this.state.formData?.[0], this.state.formData?.[1]];
|
|
114
|
-
validateMobilePushContent(formDataArr, {
|
|
115
|
-
currentTab: this.state.currentTab,
|
|
116
|
-
getLiquidTags: nextProps.getLiquidTags,
|
|
117
|
-
formatMessage: this.props.intl.formatMessage,
|
|
118
|
-
messages: formBuilderMessages,
|
|
119
|
-
onError: (err) => {
|
|
120
|
-
const { standardErrors = [], liquidErrors = [] } = err;
|
|
121
|
-
// _validatePlatformSpecificContent passes { standardErrors: { ANDROID, IOS, generic }, liquidErrors: { ... } }; footer expects arrays
|
|
122
|
-
const toArray = (v) => (Array.isArray(v) ? v : (v && typeof v === 'object' ? [].concat(...Object.values(v)) : []));
|
|
123
|
-
const STANDARD_ERROR_MSG = toArray(standardErrors);
|
|
124
|
-
const LIQUID_ERROR_MSG = toArray(liquidErrors);
|
|
125
|
-
if (typeof nextProps.showLiquidErrorInFooter === 'function') {
|
|
126
|
-
nextProps.showLiquidErrorInFooter(
|
|
127
|
-
{ STANDARD_ERROR_MSG, LIQUID_ERROR_MSG },
|
|
128
|
-
this.state.currentTab
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
// Only trigger onValidationFail when there are actual errors; skip when helper called onError with empty arrays (reset case)
|
|
132
|
-
if ((STANDARD_ERROR_MSG.length > 0 || LIQUID_ERROR_MSG.length > 0) && typeof nextProps.onValidationFail === 'function') {
|
|
133
|
-
nextProps.onValidationFail();
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
onSuccess: () => {
|
|
137
|
-
if (this.state.isFormValid && typeof nextProps.getFormLibraryData === 'function') {
|
|
138
|
-
nextProps.getFormLibraryData(this.getFormData());
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
} else {
|
|
143
|
-
// Fail closed: require full validation callback set; treat any missing callback as validation failure
|
|
144
|
-
if (typeof nextProps.onValidationFail === 'function') {
|
|
145
|
-
nextProps.onValidationFail();
|
|
146
|
-
}
|
|
147
|
-
}
|
|
97
|
+
if (nextProps.isGetFormData && !this.props.isFullMode) {
|
|
98
|
+
nextProps.getFormLibraryData(this.getFormData());
|
|
148
99
|
} else if (nextProps.isGetFormData && this.props.isGetFormData !== nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
|
|
149
100
|
this.startValidation();
|
|
150
101
|
}
|
|
@@ -727,21 +678,31 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
727
678
|
}
|
|
728
679
|
showError = () => {
|
|
729
680
|
const {intl} = this.props;
|
|
730
|
-
const {errorData
|
|
681
|
+
const {errorData} = this.state;
|
|
731
682
|
const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
|
|
732
683
|
if (!isEmpty(this.state.formData) && !this.state.isFormValid) {
|
|
733
684
|
let tab = this.state.currentTab;
|
|
734
|
-
const isAndroidInvalid = Object.values(errorData[0]
|
|
735
|
-
const isIosInvalid = Object.values(errorData[1]
|
|
736
|
-
|
|
685
|
+
const isAndroidInvalid = Object.values(errorData[0]).includes(true);
|
|
686
|
+
const isIosInvalid = Object.values(errorData[1]).includes(true);
|
|
687
|
+
let isTagErrorExist = false;
|
|
737
688
|
if (isAndroidInvalid) {
|
|
738
689
|
tab = 1;
|
|
739
690
|
errorMessage.description = intl.formatMessage(messages.invalidAndroidMessage);
|
|
740
|
-
|
|
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) {
|
|
741
697
|
tab = 2;
|
|
742
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
|
+
}
|
|
743
704
|
}
|
|
744
|
-
if (tab !== this.state.currentTab) {
|
|
705
|
+
if (tab !== this.state.currentTab || isTagErrorExist) {
|
|
745
706
|
CapNotification.error(errorMessage);
|
|
746
707
|
}
|
|
747
708
|
}
|
|
@@ -2068,7 +2029,6 @@ Create.propTypes = {
|
|
|
2068
2029
|
onPreviewContentClicked: PropTypes.func,
|
|
2069
2030
|
onTestContentClicked: PropTypes.func,
|
|
2070
2031
|
eventContextTags: PropTypes.array,
|
|
2071
|
-
getLiquidTags: PropTypes.func,
|
|
2072
2032
|
showLiquidErrorInFooter: PropTypes.func,
|
|
2073
2033
|
showTestAndPreviewSlidebox: PropTypes.bool,
|
|
2074
2034
|
handleTestAndPreview: PropTypes.func,
|