@capillarytech/creatives-library 8.0.304 → 8.0.305-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 +4 -1
- package/initialState.js +0 -2
- package/package.json +1 -1
- package/utils/common.js +12 -9
- package/utils/commonUtils.js +36 -93
- package/utils/tagValidations.js +83 -223
- package/utils/tests/commonUtil.test.js +147 -124
- package/utils/tests/tagValidations.test.js +441 -358
- package/v2Components/CapDeviceContent/index.js +10 -7
- 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/BeePopupEditor/index.js +9 -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 +40 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -5
- package/v2Containers/CreativesContainer/constants.js +6 -0
- package/v2Containers/CreativesContainer/index.js +47 -7
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +69 -1
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +121 -4
- 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/__tests__/InAppHTMLEditor.test.js +110 -155
- package/v2Containers/InApp/index.js +216 -120
- package/v2Containers/InApp/tests/index.test.js +17 -6
- package/v2Containers/InApp/tests/mockData.js +1 -1
- package/v2Containers/InappAdvance/index.js +6 -110
- package/v2Containers/InappAdvance/tests/index.test.js +2 -0
- package/v2Containers/Line/Container/Text/index.js +0 -1
- package/v2Containers/MobilePush/Create/index.js +59 -19
- package/v2Containers/MobilePush/Edit/index.js +48 -20
- 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/TemplatesV2/index.js +28 -13
- 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 -8
- package/v2Containers/WebPush/Create/utils/validation.test.js +44 -24
- package/v2Containers/Whatsapp/index.js +9 -17
- package/v2Containers/Zalo/index.js +3 -11
|
@@ -14,6 +14,8 @@ import CapRow from "@capillarytech/cap-ui-library/CapRow";
|
|
|
14
14
|
import CapColumn from "@capillarytech/cap-ui-library/CapColumn";
|
|
15
15
|
import CapButton from "@capillarytech/cap-ui-library/CapButton";
|
|
16
16
|
import CapNotification from "@capillarytech/cap-ui-library/CapNotification";
|
|
17
|
+
import CapTab from "@capillarytech/cap-ui-library/CapTab";
|
|
18
|
+
import CapInput from "@capillarytech/cap-ui-library/CapInput";
|
|
17
19
|
import { makeSelectInApp, makeSelectAccount, makeSelectGetTemplateDetailsInProgress } from "./selectors";
|
|
18
20
|
import * as globalActions from '../Cap/actions';
|
|
19
21
|
import {
|
|
@@ -31,7 +33,6 @@ import creativesMessages from '../CreativesContainer/messages';
|
|
|
31
33
|
import withCreatives from "../../hoc/withCreatives";
|
|
32
34
|
import UnifiedPreview from "../../v2Components/CommonTestAndPreview/UnifiedPreview";
|
|
33
35
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
34
|
-
import { validateTags } from "../../utils/tagValidations";
|
|
35
36
|
import injectReducer from '../../utils/injectReducer';
|
|
36
37
|
import v2InAppReducer from './reducer';
|
|
37
38
|
import { v2InAppSagas } from './sagas';
|
|
@@ -51,19 +52,21 @@ import {
|
|
|
51
52
|
LAYOUT_RADIO_OPTIONS,
|
|
52
53
|
IOS_CAPITAL,
|
|
53
54
|
} from "./constants";
|
|
54
|
-
import { INAPP, SMS } from "../CreativesContainer/constants";
|
|
55
|
+
import { GENERIC, INAPP, SMS } from "../CreativesContainer/constants";
|
|
56
|
+
import { AI_CONTENT_BOT_DISABLED } from "../../constants/unified";
|
|
55
57
|
import {
|
|
56
58
|
ALL, TAG, EMBEDDED, DEFAULT, FULL, LIBRARY,
|
|
57
59
|
} from "../Whatsapp/constants";
|
|
58
60
|
import { getCdnUrl } from "../../utils/cdnTransformation";
|
|
59
61
|
import { getCtaObject, hasAnyErrors, getSingleTab } from "./utils";
|
|
60
62
|
import { validateInAppContent } from "../../utils/commonUtils";
|
|
61
|
-
import { hasLiquidSupportFeature } from "../../utils/common";
|
|
63
|
+
import { hasLiquidSupportFeature, hasNewEditorFlowInAppEnabled } from "../../utils/common";
|
|
62
64
|
import formBuilderMessages from "../../v2Components/FormBuilder/messages";
|
|
63
65
|
import HTMLEditor from "../../v2Components/HtmlEditor";
|
|
64
66
|
import { HTML_EDITOR_VARIANTS } from "../../v2Components/HtmlEditor/constants";
|
|
65
67
|
import { INAPP_EDITOR_TYPES } from "../InAppWrapper/constants";
|
|
66
68
|
import InappAdvanced from "../InappAdvance/index";
|
|
69
|
+
import CapDeviceContent from "../../v2Components/CapDeviceContent";
|
|
67
70
|
import { ErrorInfoNote } from "../../v2Components/ErrorInfoNote";
|
|
68
71
|
|
|
69
72
|
let editContent = {};
|
|
@@ -74,6 +77,7 @@ export const InApp = (props) => {
|
|
|
74
77
|
actions,
|
|
75
78
|
globalActions,
|
|
76
79
|
isFullMode,
|
|
80
|
+
isLoyaltyModule,
|
|
77
81
|
onCreateComplete,
|
|
78
82
|
params,
|
|
79
83
|
templateData = {},
|
|
@@ -878,7 +882,7 @@ export const InApp = (props) => {
|
|
|
878
882
|
// Use 'html editor template' as title for HTML editor to differentiate from BEE editor
|
|
879
883
|
title: isHTMLTemplate ? 'html editor template' : titleAndroid,
|
|
880
884
|
message: androidMessage,
|
|
881
|
-
bodyType:
|
|
885
|
+
bodyType: bodyTypeForBackend,
|
|
882
886
|
expandableDetails: {
|
|
883
887
|
style: androidExpandableStyle,
|
|
884
888
|
message: androidMessage,
|
|
@@ -1079,39 +1083,55 @@ export const InApp = (props) => {
|
|
|
1079
1083
|
};
|
|
1080
1084
|
|
|
1081
1085
|
// Validation middleware for tag validation (both liquid and non-liquid flow)
|
|
1086
|
+
// Liquid validation (extractTags) runs only in library mode
|
|
1082
1087
|
const validationMiddleWare = async () => {
|
|
1083
|
-
//
|
|
1088
|
+
// Normalize validator bucket keys to component state keys (ANDROID, IOS_CAPITAL, GENERIC)
|
|
1089
|
+
// so we don't merge e.g. 'android'/'ios'/'generic' with ANDROID/IOS/GENERIC and get duplicate/stale keys
|
|
1090
|
+
const normalizeErrorBuckets = (errors) => {
|
|
1091
|
+
const normalized = {
|
|
1092
|
+
[ANDROID]: [],
|
|
1093
|
+
[IOS_CAPITAL]: [],
|
|
1094
|
+
[GENERIC]: [],
|
|
1095
|
+
};
|
|
1096
|
+
if (!errors || typeof errors !== 'object') return normalized;
|
|
1097
|
+
const keyMap = {
|
|
1098
|
+
ANDROID,
|
|
1099
|
+
android: ANDROID,
|
|
1100
|
+
[IOS_CAPITAL]: IOS_CAPITAL,
|
|
1101
|
+
[IOS]: IOS_CAPITAL,
|
|
1102
|
+
ios: IOS_CAPITAL,
|
|
1103
|
+
GENERIC,
|
|
1104
|
+
generic: GENERIC,
|
|
1105
|
+
};
|
|
1106
|
+
for (const [key, value] of Object.entries(errors)) {
|
|
1107
|
+
const targetKey = keyMap[key];
|
|
1108
|
+
if (targetKey != null && Array.isArray(value)) {
|
|
1109
|
+
normalized[targetKey] = value;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return normalized;
|
|
1113
|
+
};
|
|
1084
1114
|
const onError = ({ standardErrors, liquidErrors }) => {
|
|
1085
1115
|
setErrorMessage((prev) => ({
|
|
1086
|
-
STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...standardErrors },
|
|
1087
|
-
LIQUID_ERROR_MSG: { ...prev.LIQUID_ERROR_MSG, ...liquidErrors },
|
|
1116
|
+
STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...normalizeErrorBuckets(standardErrors) },
|
|
1117
|
+
LIQUID_ERROR_MSG: { ...prev.LIQUID_ERROR_MSG, ...normalizeErrorBuckets(liquidErrors) },
|
|
1088
1118
|
}));
|
|
1089
1119
|
};
|
|
1090
1120
|
const onSuccess = () => {
|
|
1091
|
-
// Proceed with submission when validation is successful
|
|
1092
1121
|
onDoneCallback();
|
|
1093
1122
|
};
|
|
1094
1123
|
|
|
1095
|
-
//
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
// For liquid flow, use validateInAppContent
|
|
1099
|
-
if (isLiquidFlow && hasTags) {
|
|
1100
|
-
// Validate the INAPP content
|
|
1124
|
+
// Library mode: run extractTags validation (always, so we catch liquid errors even when tags not loaded)
|
|
1125
|
+
if (!isFullMode) {
|
|
1101
1126
|
const payload = createPayload();
|
|
1102
1127
|
validateInAppContent(payload, {
|
|
1103
|
-
currentTab: panes === ANDROID ? 1 : 2,
|
|
1128
|
+
currentTab: panes === ANDROID ? 1 : 2,
|
|
1104
1129
|
onError,
|
|
1105
1130
|
onSuccess,
|
|
1106
1131
|
getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
|
|
1107
1132
|
formatMessage,
|
|
1108
1133
|
messages: formBuilderMessages,
|
|
1109
|
-
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
1110
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1111
|
-
isLiquidFlow,
|
|
1112
|
-
forwardedTags: {},
|
|
1113
1134
|
skipTags: (tag) => {
|
|
1114
|
-
// Skip certain tags if needed
|
|
1115
1135
|
const skipRegexes = [
|
|
1116
1136
|
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
1117
1137
|
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
@@ -1119,103 +1139,15 @@ export const InApp = (props) => {
|
|
|
1119
1139
|
/SURVEY.*\.TOKEN/,
|
|
1120
1140
|
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
1121
1141
|
];
|
|
1122
|
-
|
|
1123
1142
|
return skipRegexes.some((regex) => regex.test(tag));
|
|
1124
1143
|
},
|
|
1125
1144
|
singleTab: getSingleTab(accountData),
|
|
1126
1145
|
});
|
|
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
1146
|
} else {
|
|
1212
|
-
// No tags available, skip validation and proceed directly
|
|
1213
1147
|
onSuccess();
|
|
1214
1148
|
}
|
|
1215
1149
|
};
|
|
1216
1150
|
|
|
1217
|
-
const isLiquidFlow = hasLiquidSupportFeature();
|
|
1218
|
-
|
|
1219
1151
|
// Check template data to determine editor type (for render decision)
|
|
1220
1152
|
const templateDetails = isFullMode ? editData?.templateDetails : templateData;
|
|
1221
1153
|
const versions = templateDetails?.versions || {};
|
|
@@ -1240,11 +1172,13 @@ export const InApp = (props) => {
|
|
|
1240
1172
|
&& !isBEEeditor
|
|
1241
1173
|
&& !isBeeFreeTemplate;
|
|
1242
1174
|
|
|
1175
|
+
const isNewEditorFlowEnabled = !isLoyaltyModule && hasNewEditorFlowInAppEnabled();
|
|
1176
|
+
|
|
1243
1177
|
// Use state if available, otherwise fall back to direct data check
|
|
1244
|
-
const shouldUseHTMLEditor = isHTMLTemplate || isHTMLTemplateFromData;
|
|
1178
|
+
const shouldUseHTMLEditor = isNewEditorFlowEnabled && (isHTMLTemplate || isHTMLTemplateFromData);
|
|
1245
1179
|
|
|
1246
1180
|
// Only route to Bee editor if it's explicitly a Bee editor AND not an HTML template
|
|
1247
|
-
const shouldUseBeeEditor = (isBEEeditor || isBeeFreeTemplate) && !shouldUseHTMLEditor;
|
|
1181
|
+
const shouldUseBeeEditor = isNewEditorFlowEnabled && (isBEEeditor || isBeeFreeTemplate) && !shouldUseHTMLEditor;
|
|
1248
1182
|
|
|
1249
1183
|
// Early returns to avoid nested ternary
|
|
1250
1184
|
if (isEditInApp && getTemplateDetailsInProgress) {
|
|
@@ -1280,10 +1214,157 @@ export const InApp = (props) => {
|
|
|
1280
1214
|
);
|
|
1281
1215
|
}
|
|
1282
1216
|
|
|
1217
|
+
// ── Old-flow helpers (used by CapDeviceContent when flag is disabled) ──────
|
|
1218
|
+
const isAiContentBotDisabled = currentOrgDetails?.accessibleFeatures?.includes(AI_CONTENT_BOT_DISABLED);
|
|
1219
|
+
|
|
1220
|
+
const templateDescErrorHandler = (value) => {
|
|
1221
|
+
const { unsupportedTags, isBraceError } = validateTags({
|
|
1222
|
+
content: value,
|
|
1223
|
+
tagsParam: tags,
|
|
1224
|
+
injectedTagsParams: injectedTags,
|
|
1225
|
+
location,
|
|
1226
|
+
tagModule: getDefaultTags,
|
|
1227
|
+
isFullMode,
|
|
1228
|
+
}) || {};
|
|
1229
|
+
if (unsupportedTags?.length > 0) {
|
|
1230
|
+
return formatMessage(globalMessages.unsupportedTagsValidationError, { unsupportedTags });
|
|
1231
|
+
}
|
|
1232
|
+
if (isBraceError) {
|
|
1233
|
+
return formatMessage(globalMessages.braceValidationError);
|
|
1234
|
+
}
|
|
1235
|
+
return '';
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1238
|
+
const onCopyTitleAndContent = () => {
|
|
1239
|
+
if (panes === ANDROID) {
|
|
1240
|
+
setTitleAndroid(titleIos);
|
|
1241
|
+
setTemplateMessageAndroid(templateMessageIos);
|
|
1242
|
+
setInAppImageSrcAndroid(inAppImageSrcIos);
|
|
1243
|
+
} else {
|
|
1244
|
+
setTitleIos(titleAndroid);
|
|
1245
|
+
setTemplateMessageIos(templateMessageAndroid);
|
|
1246
|
+
setInAppImageSrcIos(inAppImageSrcAndroid);
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
const onTagSelect = (value, index) => {
|
|
1251
|
+
const tag = `{{${value}}}`;
|
|
1252
|
+
if (panes === ANDROID) {
|
|
1253
|
+
if (index === 0) setTitleAndroid((prev) => prev + tag);
|
|
1254
|
+
else setTemplateMessageAndroid((prev) => prev + tag);
|
|
1255
|
+
} else if (index === 0) {
|
|
1256
|
+
setTitleIos((prev) => prev + tag);
|
|
1257
|
+
} else {
|
|
1258
|
+
setTemplateMessageIos((prev) => prev + tag);
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
|
|
1262
|
+
// Device support flags (same derivation as InappAdvance)
|
|
1263
|
+
const isAndroidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
|
|
1264
|
+
const isIosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
|
|
1265
|
+
|
|
1266
|
+
// CapDeviceContent tab panes (old flow)
|
|
1267
|
+
const DEVICE_PANES = [
|
|
1268
|
+
{
|
|
1269
|
+
content: (
|
|
1270
|
+
<CapDeviceContent
|
|
1271
|
+
panes={ANDROID}
|
|
1272
|
+
actions={actions}
|
|
1273
|
+
editData={editData}
|
|
1274
|
+
isFullMode={isFullMode}
|
|
1275
|
+
inAppImageSrc={inAppImageSrcAndroid}
|
|
1276
|
+
setInAppImageSrc={setInAppImageSrcAndroid}
|
|
1277
|
+
isEditFlow={isEditFlow}
|
|
1278
|
+
ctaData={ctaDataAndroid}
|
|
1279
|
+
setCtaData={setCtaDataAndroid}
|
|
1280
|
+
buttonType={buttonTypeAndroid}
|
|
1281
|
+
setButtonType={setButtonTypeAndroid}
|
|
1282
|
+
templateMediaType={templateMediaType}
|
|
1283
|
+
setTemplateMediaType={setTemplateMediaType}
|
|
1284
|
+
title={titleAndroid}
|
|
1285
|
+
setTitle={setTitleAndroid}
|
|
1286
|
+
templateMessageError={templateMessageErrorAndroid}
|
|
1287
|
+
templateMessage={templateMessageAndroid}
|
|
1288
|
+
setTemplateMessage={setTemplateMessageAndroid}
|
|
1289
|
+
setTemplateMessageError={setTemplateMessageErrorAndroid}
|
|
1290
|
+
addActionLink={addActionLinkAndroid}
|
|
1291
|
+
setAddActionLink={setAddActionLinkAndroid}
|
|
1292
|
+
deepLink={deepLink}
|
|
1293
|
+
deepLinkValue={deepLinkValueAndroid}
|
|
1294
|
+
setDeepLinkValue={setDeepLinkValueAndroid}
|
|
1295
|
+
onCopyTitleAndContent={onCopyTitleAndContent}
|
|
1296
|
+
isOtherDeviceSupported={isIosSupported}
|
|
1297
|
+
tags={tags}
|
|
1298
|
+
onTagSelect={onTagSelect}
|
|
1299
|
+
handleOnTagsContextChange={handleOnTagsContextChange}
|
|
1300
|
+
templateDescErrorHandler={templateDescErrorHandler}
|
|
1301
|
+
templateTitleError={templateTitleErrorAndroid}
|
|
1302
|
+
setTemplateTitleError={setTemplateTitleErrorAndroid}
|
|
1303
|
+
isAiContentBotDisabled={isAiContentBotDisabled}
|
|
1304
|
+
injectedTags={injectedTags}
|
|
1305
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1306
|
+
location={location}
|
|
1307
|
+
/>
|
|
1308
|
+
),
|
|
1309
|
+
tab: <FormattedMessage {...messages.Android} />,
|
|
1310
|
+
key: ANDROID,
|
|
1311
|
+
isSupported: isAndroidSupported,
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
content: (
|
|
1315
|
+
<CapDeviceContent
|
|
1316
|
+
panes={IOS}
|
|
1317
|
+
actions={actions}
|
|
1318
|
+
editData={editData}
|
|
1319
|
+
isFullMode={isFullMode}
|
|
1320
|
+
inAppImageSrc={inAppImageSrcIos}
|
|
1321
|
+
setInAppImageSrc={setInAppImageSrcIos}
|
|
1322
|
+
isEditFlow={isEditFlow}
|
|
1323
|
+
ctaData={ctaDataIos}
|
|
1324
|
+
setCtaData={setCtaDataIos}
|
|
1325
|
+
buttonType={buttonTypeIos}
|
|
1326
|
+
setButtonType={setButtonTypeIos}
|
|
1327
|
+
templateMediaType={templateMediaType}
|
|
1328
|
+
setTemplateMediaType={setTemplateMediaType}
|
|
1329
|
+
title={titleIos}
|
|
1330
|
+
setTitle={setTitleIos}
|
|
1331
|
+
templateMessageError={templateMessageErrorIos}
|
|
1332
|
+
templateMessage={templateMessageIos}
|
|
1333
|
+
setTemplateMessage={setTemplateMessageIos}
|
|
1334
|
+
setTemplateMessageError={setTemplateMessageErrorIos}
|
|
1335
|
+
addActionLink={addActionLinkIos}
|
|
1336
|
+
setAddActionLink={setAddActionLinkIos}
|
|
1337
|
+
deepLink={deepLink}
|
|
1338
|
+
deepLinkValue={deepLinkValueIos}
|
|
1339
|
+
setDeepLinkValue={setDeepLinkValueIos}
|
|
1340
|
+
onCopyTitleAndContent={onCopyTitleAndContent}
|
|
1341
|
+
isOtherDeviceSupported={isAndroidSupported}
|
|
1342
|
+
tags={tags}
|
|
1343
|
+
onTagSelect={onTagSelect}
|
|
1344
|
+
handleOnTagsContextChange={handleOnTagsContextChange}
|
|
1345
|
+
templateDescErrorHandler={templateDescErrorHandler}
|
|
1346
|
+
templateTitleError={templateTitleErrorIos}
|
|
1347
|
+
setTemplateTitleError={setTemplateTitleErrorIos}
|
|
1348
|
+
isAiContentBotDisabled={isAiContentBotDisabled}
|
|
1349
|
+
injectedTags={injectedTags}
|
|
1350
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1351
|
+
location={location}
|
|
1352
|
+
/>
|
|
1353
|
+
),
|
|
1354
|
+
tab: <FormattedMessage {...messages.Ios} />,
|
|
1355
|
+
key: IOS,
|
|
1356
|
+
isSupported: isIosSupported,
|
|
1357
|
+
},
|
|
1358
|
+
];
|
|
1359
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1360
|
+
|
|
1361
|
+
// Calculate column span: HTML editor = 18, everything else = 24
|
|
1362
|
+
const editorColumnSpan = shouldUseHTMLEditor ? 18 : 24;
|
|
1363
|
+
|
|
1283
1364
|
return (
|
|
1284
1365
|
<CapSpin spinning={spin || fetchingLiquidValidation} tip={fetchingLiquidValidation ? <FormattedMessage {...formBuilderMessages.liquidSpinText} /> : ""}>
|
|
1285
1366
|
<CapRow className="cap-inapp-creatives">
|
|
1286
|
-
<CapColumn span={
|
|
1367
|
+
<CapColumn span={editorColumnSpan}>
|
|
1287
1368
|
{/* Creative layout type */}
|
|
1288
1369
|
{shouldUseHTMLEditor && (
|
|
1289
1370
|
<>
|
|
@@ -1304,7 +1385,7 @@ export const InApp = (props) => {
|
|
|
1304
1385
|
/>
|
|
1305
1386
|
</>
|
|
1306
1387
|
)}
|
|
1307
|
-
{shouldUseHTMLEditor
|
|
1388
|
+
{shouldUseHTMLEditor && (
|
|
1308
1389
|
<HTMLEditor
|
|
1309
1390
|
key={`inapp-html-editor-v${htmlEditorContentVersion}`}
|
|
1310
1391
|
variant={HTML_EDITOR_VARIANTS.INAPP}
|
|
@@ -1320,19 +1401,16 @@ export const InApp = (props) => {
|
|
|
1320
1401
|
location={location}
|
|
1321
1402
|
selectedOfferDetails={selectedOfferDetails}
|
|
1322
1403
|
onTagSelect={() => {
|
|
1323
|
-
// Tag insertion
|
|
1324
|
-
// Content updates will be propagated via onContentChange callback
|
|
1404
|
+
// Tag insertion handled by HTMLEditor's CodeEditorPane at cursor position
|
|
1325
1405
|
}}
|
|
1326
|
-
// Don't pass globalActions to prevent duplicate API calls
|
|
1327
|
-
// HTMLEditor will use onContextChange instead
|
|
1328
1406
|
onContextChange={handleOnTagsContextChange}
|
|
1329
|
-
// Pass validation errors to HTMLEditor for display
|
|
1330
1407
|
errors={errorMessage}
|
|
1331
1408
|
isFullMode={isFullMode}
|
|
1332
1409
|
data-test="inapp-html-editor"
|
|
1333
1410
|
style={{ width: '138%' }}
|
|
1334
1411
|
/>
|
|
1335
|
-
)
|
|
1412
|
+
)}
|
|
1413
|
+
{!shouldUseHTMLEditor && isNewEditorFlowEnabled && (
|
|
1336
1414
|
<InappAdvanced
|
|
1337
1415
|
getFormData={getFormData}
|
|
1338
1416
|
setIsLoadingContent={setIsLoadingContent}
|
|
@@ -1357,11 +1435,29 @@ export const InApp = (props) => {
|
|
|
1357
1435
|
onCreateComplete={onCreateComplete}
|
|
1358
1436
|
/>
|
|
1359
1437
|
)}
|
|
1438
|
+
{!shouldUseHTMLEditor && !isNewEditorFlowEnabled && (
|
|
1439
|
+
<>
|
|
1440
|
+
<CapInput
|
|
1441
|
+
label={<FormattedMessage {...messages.creativeName} />}
|
|
1442
|
+
onChange={({ target: { value } }) => setTempName(value)}
|
|
1443
|
+
value={tempName}
|
|
1444
|
+
labelPosition="top"
|
|
1445
|
+
size="default"
|
|
1446
|
+
/>
|
|
1447
|
+
<CapTab
|
|
1448
|
+
panes={DEVICE_PANES.filter((devicePane) => devicePane?.isSupported === true)}
|
|
1449
|
+
onChange={(value) => setPanes(value)}
|
|
1450
|
+
activeKey={panes}
|
|
1451
|
+
defaultActiveKey={panes}
|
|
1452
|
+
className="inapp-template-device-tab"
|
|
1453
|
+
/>
|
|
1454
|
+
</>
|
|
1455
|
+
)}
|
|
1360
1456
|
</CapColumn>
|
|
1361
1457
|
</CapRow>
|
|
1362
|
-
{/* Footer with Done/Update button -
|
|
1458
|
+
{/* Footer with Done/Update button - show for HTML editor and old CapDeviceContent flow */}
|
|
1363
1459
|
{
|
|
1364
|
-
shouldUseHTMLEditor && (
|
|
1460
|
+
(shouldUseHTMLEditor || !isNewEditorFlowEnabled) && (
|
|
1365
1461
|
<>
|
|
1366
1462
|
{hasAnyErrors(errorMessage) && (
|
|
1367
1463
|
<ErrorInfoNote
|
|
@@ -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
|
-
|
|
823
|
-
|
|
824
|
-
if (isLiquidFlow && hasTags) {
|
|
825
|
-
validateInAppContent(payload, {
|
|
826
|
-
currentTab: panes === ANDROID ? 1 : 2, // Convert ANDROID/IOS to tab numbers
|
|
818
|
+
// Liquid validation (extractTags) only in library mode
|
|
819
|
+
if (!isFullMode) {
|
|
820
|
+
await validateInAppContent(payload, {
|
|
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,98 +832,12 @@ 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
|
-
|
|
936
|
-
onSuccess();
|
|
840
|
+
await onSuccess();
|
|
937
841
|
}
|
|
938
842
|
};
|
|
939
843
|
|
|
@@ -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"
|