@capillarytech/creatives-library 8.0.290-alpha.4 → 8.0.291
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +1 -3
- package/initialState.js +2 -0
- package/package.json +1 -1
- package/utils/common.js +8 -5
- package/utils/commonUtils.js +85 -4
- package/utils/tagValidations.js +223 -83
- package/utils/tests/commonUtil.test.js +124 -147
- package/utils/tests/tagValidations.test.js +358 -441
- package/v2Components/ErrorInfoNote/index.js +5 -2
- package/v2Components/FormBuilder/index.js +201 -132
- package/v2Components/FormBuilder/messages.js +8 -0
- package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +2 -1
- package/v2Containers/Cap/mockData.js +14 -0
- package/v2Containers/Cap/reducer.js +55 -3
- package/v2Containers/Cap/tests/reducer.test.js +102 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +1 -5
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -13
- package/v2Containers/CreativesContainer/index.js +15 -30
- package/v2Containers/Email/index.js +5 -1
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +70 -23
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +137 -29
- package/v2Containers/FTP/index.js +51 -2
- package/v2Containers/FTP/messages.js +4 -0
- package/v2Containers/InApp/index.js +104 -4
- package/v2Containers/InApp/tests/index.test.js +6 -17
- package/v2Containers/InappAdvance/index.js +108 -4
- package/v2Containers/InappAdvance/tests/index.test.js +0 -2
- package/v2Containers/Line/Container/Text/index.js +1 -0
- package/v2Containers/MobilePush/Create/index.js +19 -42
- package/v2Containers/MobilePush/Edit/index.js +19 -42
- package/v2Containers/MobilePushNew/index.js +32 -12
- package/v2Containers/MobilepushWrapper/index.js +1 -3
- package/v2Containers/Rcs/index.js +37 -12
- package/v2Containers/Sms/Create/index.js +3 -39
- package/v2Containers/Sms/Create/messages.js +0 -4
- package/v2Containers/Sms/Edit/index.js +3 -35
- package/v2Containers/Sms/commonMethods.js +6 -3
- package/v2Containers/SmsTrai/Edit/index.js +47 -11
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/SmsWrapper/index.js +0 -2
- package/v2Containers/Viber/index.js +1 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/utils/validation.js +2 -17
- package/v2Containers/WebPush/Create/utils/validation.test.js +24 -59
- package/v2Containers/Whatsapp/index.js +17 -9
- package/v2Containers/Zalo/index.js +11 -3
- package/v2Containers/Sms/tests/commonMethods.test.js +0 -122
|
@@ -186,6 +186,7 @@ export const ErrorInfoNote = (props) => {
|
|
|
186
186
|
errorMessages,
|
|
187
187
|
onErrorClick,
|
|
188
188
|
onClose,
|
|
189
|
+
isLiquidEnabled = true,
|
|
189
190
|
intl,
|
|
190
191
|
useLegacyDisplay = false, // Use simple list display instead of tabs (for BEE Editor)
|
|
191
192
|
} = props;
|
|
@@ -229,7 +230,7 @@ export const ErrorInfoNote = (props) => {
|
|
|
229
230
|
const standardErrors = Array.isArray(rawStandardErrors) ? rawStandardErrors : [];
|
|
230
231
|
const liquidErrors = Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [];
|
|
231
232
|
const hasStandardErrors = standardErrors.length > 0;
|
|
232
|
-
const hasLiquidErrors = liquidErrors.length > 0;
|
|
233
|
+
const hasLiquidErrors = liquidErrors.length > 0 && isLiquidEnabled;
|
|
233
234
|
|
|
234
235
|
if (!hasStandardErrors && !hasLiquidErrors) {
|
|
235
236
|
return null;
|
|
@@ -356,7 +357,7 @@ export const ErrorInfoNote = (props) => {
|
|
|
356
357
|
className="error-info-note__tabs"
|
|
357
358
|
/>
|
|
358
359
|
<CapRow className="error-info-note__actions">
|
|
359
|
-
{hasLiquidErrors && (
|
|
360
|
+
{hasLiquidErrors && isLiquidEnabled && (
|
|
360
361
|
<CapButton
|
|
361
362
|
type="flat"
|
|
362
363
|
className="error-info-note__liquid-doc"
|
|
@@ -451,6 +452,7 @@ ErrorInfoNote.defaultProps = {
|
|
|
451
452
|
},
|
|
452
453
|
onErrorClick: null,
|
|
453
454
|
onClose: null,
|
|
455
|
+
isLiquidEnabled: true,
|
|
454
456
|
intl: null,
|
|
455
457
|
useLegacyDisplay: false, // Use simple list display for BEE Editor
|
|
456
458
|
};
|
|
@@ -477,6 +479,7 @@ ErrorInfoNote.propTypes = {
|
|
|
477
479
|
}),
|
|
478
480
|
onErrorClick: PropTypes.func,
|
|
479
481
|
onClose: PropTypes.func,
|
|
482
|
+
isLiquidEnabled: PropTypes.bool,
|
|
480
483
|
intl: PropTypes.object,
|
|
481
484
|
useLegacyDisplay: PropTypes.bool, // Use simple list display for BEE Editor
|
|
482
485
|
};
|
|
@@ -50,7 +50,7 @@ import { makeSelectMetaEntities, selectCurrentOrgDetails, selectLiquidStateDetai
|
|
|
50
50
|
import * as actions from "../../v2Containers/Cap/actions";
|
|
51
51
|
import './_formBuilder.scss';
|
|
52
52
|
import {updateCharCount, checkUnicode} from "../../utils/smsCharCountV2";
|
|
53
|
-
import { preprocessHtml,
|
|
53
|
+
import { checkSupport, extractNames, preprocessHtml, validateIfTagClosed, isInsideLiquidBlock} from '../../utils/tagValidations';
|
|
54
54
|
import { containsBase64Images } from '../../utils/content';
|
|
55
55
|
import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS,AI_CONTENT_BOT_DISABLED, EMAIL, LIQUID_SUPPORTED_CHANNELS, INAPP } from '../../v2Containers/CreativesContainer/constants';
|
|
56
56
|
import globalMessages from '../../v2Containers/Cap/messages';
|
|
@@ -60,7 +60,7 @@ import { GET_TRANSLATION_MAPPED } from '../../constants/unified';
|
|
|
60
60
|
import moment from 'moment';
|
|
61
61
|
import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS} from '../../constants/unified';
|
|
62
62
|
import { REQUEST } from '../../v2Containers/Cap/constants'
|
|
63
|
-
import {
|
|
63
|
+
import { hasLiquidSupportFeature, isEmailUnsubscribeTagMandatory } from '../../utils/common';
|
|
64
64
|
import { isUrl } from '../../v2Containers/Line/Container/Wrapper/utils';
|
|
65
65
|
import { bindActionCreators } from 'redux';
|
|
66
66
|
import { getChannelData, hasPersonalizationTags, validateLiquidTemplateContent, validateMobilePushContent } from '../../utils/commonUtils';
|
|
@@ -72,8 +72,10 @@ const {CapRadioGroup} = CapRadio;
|
|
|
72
72
|
|
|
73
73
|
const tagsTypes = {
|
|
74
74
|
MISSING_TAGS: 'missingTags',
|
|
75
|
+
UNSUPPORTED_TAGS: 'unsupportedTags',
|
|
75
76
|
};
|
|
76
77
|
const errorMessageForTags = {
|
|
78
|
+
UNSUPPORTED_TAG_ERROR: 'unsupportedTagsError',
|
|
77
79
|
MISSING_TAG_ERROR: 'missingTagsError',
|
|
78
80
|
GENERIC_VALIDATION_ERROR: 'genericValidationError',
|
|
79
81
|
TAG_BRACKET_COUNT_MISMATCH_ERROR: 'tagBracketCountMismatchError'
|
|
@@ -136,7 +138,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
136
138
|
this.handleSetRadioValue = this.handleSetRadioValue.bind(this);
|
|
137
139
|
this.formElements = [];
|
|
138
140
|
// Check if the liquid flow feature is supported and the channel is in the supported list.
|
|
139
|
-
this.
|
|
141
|
+
this.liquidFlow = this.isLiquidFlowSupported.bind(this);
|
|
140
142
|
this.onSubmitWrapper = this.onSubmitWrapper.bind(this);
|
|
141
143
|
|
|
142
144
|
// Performance optimization: Debounced functions for high-frequency updates
|
|
@@ -328,8 +330,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
328
330
|
return updatedFormData;
|
|
329
331
|
}
|
|
330
332
|
|
|
331
|
-
|
|
332
|
-
return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()));
|
|
333
|
+
isLiquidFlowSupported = () => {
|
|
334
|
+
return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()) && hasLiquidSupportFeature());
|
|
333
335
|
}
|
|
334
336
|
|
|
335
337
|
componentWillMount() {
|
|
@@ -712,9 +714,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
712
714
|
if (channel && channel.toUpperCase() === SMS) {
|
|
713
715
|
for (let count = 0; count < this.state.tabCount; count += 1) {
|
|
714
716
|
if (_.isEmpty(errorData[count])) {
|
|
715
|
-
|
|
716
|
-
// prevents onFormValidityChange from firing, which makes Done appear unresponsive.
|
|
717
|
-
errorData[count] = {};
|
|
717
|
+
return;
|
|
718
718
|
}
|
|
719
719
|
const index = count + 1;
|
|
720
720
|
if (!this.state.formData[count]) {
|
|
@@ -726,19 +726,17 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
726
726
|
|
|
727
727
|
let tagValidationResponse = false;
|
|
728
728
|
if (content) {
|
|
729
|
-
tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
|
|
729
|
+
tagValidationResponse = this.validateTags(content, tags, injectedTags, false, this.props?.isFullMode);
|
|
730
730
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
? tagValidationResponse
|
|
734
|
-
: { valid: false, missingTags: [], isBraceError: false };
|
|
735
|
-
if (tagResult.valid) {
|
|
731
|
+
|
|
732
|
+
if (tagValidationResponse.valid) {
|
|
736
733
|
errorData[count][`sms-editor${index > 1 ? index : ''}`] = false;
|
|
737
734
|
} else {
|
|
738
|
-
|
|
739
|
-
const {
|
|
740
|
-
|
|
741
|
-
errorData[count][`
|
|
735
|
+
errorData[count]['invalid-tags'] = tagValidationResponse.unsupportedTags;
|
|
736
|
+
const { MISSING_TAG_ERROR, UNSUPPORTED_TAG_ERROR, GENERIC_VALIDATION_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags || {};
|
|
737
|
+
const { missingTags, unsupportedTags, isBraceError} = tagValidationResponse;
|
|
738
|
+
errorData[count][`sms-editor${index > 1 ? index : ''}`] = missingTags && missingTags.length ? MISSING_TAG_ERROR : ( unsupportedTags && unsupportedTags.length ? UNSUPPORTED_TAG_ERROR : (isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : GENERIC_VALIDATION_ERROR));
|
|
739
|
+
errorData[count][`bracket-error`] = tagValidationResponse.isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
|
|
742
740
|
isValid = false;
|
|
743
741
|
}
|
|
744
742
|
if(content !== '' && (ifUnicode && !unicodeCheck)) {
|
|
@@ -766,7 +764,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
766
764
|
if (this.state.formData['message-editor'] !== undefined ) {
|
|
767
765
|
const content = this.state.formData['0']['message-editor'] || '';
|
|
768
766
|
|
|
769
|
-
const tagValidationResponse = this.validateTags((content), tags, false, this.props?.isFullMode);
|
|
767
|
+
const tagValidationResponse = this.validateTags((content), tags, injectedTags, false, this.props?.isFullMode);
|
|
770
768
|
|
|
771
769
|
if (tagValidationResponse.valid) {
|
|
772
770
|
errorData = {
|
|
@@ -849,7 +847,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
849
847
|
errorData[index] = true;
|
|
850
848
|
isValid = false;
|
|
851
849
|
} else {
|
|
852
|
-
const tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
|
|
850
|
+
const tagValidationResponse = this.validateTags(content, tags, injectedTags, false, this.props?.isFullMode);
|
|
853
851
|
|
|
854
852
|
if (tagValidationResponse.valid) {
|
|
855
853
|
errorData[index] = false;
|
|
@@ -915,13 +913,14 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
915
913
|
isCurrentTabValid = false;
|
|
916
914
|
} else {
|
|
917
915
|
errorData[parseInt(index)][`message-editor${selector}`] = false;
|
|
918
|
-
const tagValidationResponse = this.validateTags(message, tags, false, this.props?.isFullMode);
|
|
916
|
+
const tagValidationResponse = this.validateTags(message, tags, injectedTags, false, this.props?.isFullMode);
|
|
919
917
|
|
|
920
918
|
if (tagValidationResponse.valid) {
|
|
921
919
|
errorData[parseInt(index)][`message-editor${selector}`] = false;
|
|
922
920
|
} else {
|
|
923
|
-
const {
|
|
921
|
+
const {isBraceError} = tagValidationResponse;
|
|
924
922
|
errorData[parseInt(index)][`message-editor${selector}`] = isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : true;
|
|
923
|
+
errorData[parseInt(index)]['invalid-tags'] = tagValidationResponse.unsupportedTags;
|
|
925
924
|
errorData[parseInt(index)][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
|
|
926
925
|
isValid = false;
|
|
927
926
|
}
|
|
@@ -947,7 +946,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
947
946
|
isCurrentTabValid = false;
|
|
948
947
|
} else {
|
|
949
948
|
errorData[parseInt(index)][`message-title${selector}`] = false;
|
|
950
|
-
const tagValidationResponse = this.validateTags(title, tags, false, this.props?.isFullMode);
|
|
949
|
+
const tagValidationResponse = this.validateTags(title, tags, injectedTags, false, this.props?.isFullMode);
|
|
951
950
|
|
|
952
951
|
if (tagValidationResponse.valid) {
|
|
953
952
|
errorData[parseInt(index)][`message-title${selector}`] = false;
|
|
@@ -1201,7 +1200,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1201
1200
|
if (!content) {
|
|
1202
1201
|
return false;
|
|
1203
1202
|
}
|
|
1204
|
-
const tagValidationResponse = this.validateTags(content, tags, isEmail, this.props?.isFullMode);
|
|
1203
|
+
const tagValidationResponse = this.validateTags(content, tags, injectedTags, isEmail, this.props?.isFullMode);
|
|
1204
|
+
|
|
1205
1205
|
// Check for base64 images in email content
|
|
1206
1206
|
isEmail && containsBase64Images({content, callback:()=>{
|
|
1207
1207
|
tagValidationResponse.valid = false;
|
|
@@ -1217,20 +1217,23 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1217
1217
|
errorData[index][currentLang]['template-content'] = true;
|
|
1218
1218
|
isValid = false;
|
|
1219
1219
|
isLiquidValid = false;
|
|
1220
|
-
if ((showMessages && !isNaN(index)) || this.
|
|
1221
|
-
if (tagValidationResponse?.missingTags?.length > 0) {
|
|
1220
|
+
if ((showMessages && !isNaN(index)) || this.liquidFlow()) {
|
|
1221
|
+
if (tagValidationResponse?.missingTags?.length > 0 || tagValidationResponse?.unsupportedTags?.length > 0) {
|
|
1222
1222
|
errorString += `${this.props.intl.formatMessage(messages.contentNotValidLanguage)} ${currentLang}\n`;
|
|
1223
1223
|
}
|
|
1224
1224
|
if (tagValidationResponse?.missingTags?.length > 0) {
|
|
1225
1225
|
errorString += `${this.props.intl.formatMessage(messages.missingTags)} ${tagValidationResponse.missingTags.toString()}\n`;
|
|
1226
1226
|
}
|
|
1227
|
+
if (tagValidationResponse?.unsupportedTags?.length > 0) {
|
|
1228
|
+
errorString += `${this.props.intl.formatMessage(messages.unsupportedTags)} ${tagValidationResponse.unsupportedTags.toString()}\n`;
|
|
1229
|
+
}
|
|
1227
1230
|
if (tagValidationResponse?.isBraceError){
|
|
1228
1231
|
errorString += this.props.intl.formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
1229
1232
|
}
|
|
1230
1233
|
if (tagValidationResponse?.isContentEmpty) {
|
|
1231
1234
|
errorString += this.props.intl.formatMessage(messages.emailBodyEmptyError);
|
|
1232
1235
|
// Adds a bypass for cases where content is initially empty in the creation flow.
|
|
1233
|
-
if(this.
|
|
1236
|
+
if(this.liquidFlow()){
|
|
1234
1237
|
errorString = "";
|
|
1235
1238
|
isLiquidValid = true;
|
|
1236
1239
|
}
|
|
@@ -1242,7 +1245,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1242
1245
|
}
|
|
1243
1246
|
}
|
|
1244
1247
|
if (errorString) {
|
|
1245
|
-
if (this.
|
|
1248
|
+
if (this.liquidFlow()) {
|
|
1246
1249
|
this.setState(
|
|
1247
1250
|
(prevState) => ({
|
|
1248
1251
|
liquidErrorMessage: {
|
|
@@ -1269,7 +1272,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1269
1272
|
});
|
|
1270
1273
|
}
|
|
1271
1274
|
|
|
1272
|
-
const isTemplateValid = this.
|
|
1275
|
+
const isTemplateValid = this.liquidFlow() ? isLiquidValid : isValid;
|
|
1273
1276
|
//Updating the state with the error data
|
|
1274
1277
|
this.setState((prevState) => ({
|
|
1275
1278
|
isFormValid: isTemplateValid,
|
|
@@ -1328,59 +1331,55 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1328
1331
|
}
|
|
1329
1332
|
onSubmitWrapper = (args) => {
|
|
1330
1333
|
const {singleTab = null} = args || {};
|
|
1331
|
-
|
|
1332
|
-
const runLiquidValidation = this.isLiquidFlowSupportedByChannel() && !this.props.isFullMode;
|
|
1333
|
-
if (runLiquidValidation) {
|
|
1334
|
+
if (this.liquidFlow()) {
|
|
1334
1335
|
// For MPUSH, we need to validate both Android and iOS content separately
|
|
1335
1336
|
if (this.props.channel === MOBILE_PUSH || this.props?.schema?.channel?.toUpperCase() === MOBILE_PUSH) {
|
|
1336
1337
|
this.validateFormBuilderMPush(this.state.formData, singleTab);
|
|
1337
1338
|
return;
|
|
1338
1339
|
}
|
|
1339
|
-
|
|
1340
|
-
// For other channels (EMAIL, SMS, INAPP)
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
},
|
|
1357
|
-
}),
|
|
1358
|
-
() => {
|
|
1359
|
-
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
|
|
1360
|
-
this.props.stopValidation();
|
|
1361
|
-
this.props.onFormValidityChange(false, this.state.errorData);
|
|
1362
|
-
}
|
|
1363
|
-
);
|
|
1364
|
-
};
|
|
1365
|
-
|
|
1366
|
-
const onSuccess = (contentToSubmit) => {
|
|
1367
|
-
const channel = this.props.channel || this.props?.schema?.channel?.toUpperCase();
|
|
1368
|
-
if(channel === EMAIL) {
|
|
1369
|
-
const content = this.state.formData?.base?.[this.props.baseLanguage]?.["template-content"] || "";
|
|
1370
|
-
this.handleLiquidTemplateSubmit(content);
|
|
1371
|
-
} else {
|
|
1372
|
-
this.handleLiquidTemplateSubmit(contentToSubmit);
|
|
1340
|
+
|
|
1341
|
+
// For other channels (EMAIL, SMS, INAPP)
|
|
1342
|
+
const content = getChannelData(this.props.schema.channel || this.props.channel, this.state.formData, this.props.baseLanguage);
|
|
1343
|
+
|
|
1344
|
+
// Set up callbacks for error and success handling
|
|
1345
|
+
const onError = ({ standardErrors, liquidErrors }) => {
|
|
1346
|
+
this.setState(
|
|
1347
|
+
(prevState) => ({
|
|
1348
|
+
liquidErrorMessage: {
|
|
1349
|
+
...prevState.liquidErrorMessage,
|
|
1350
|
+
STANDARD_ERROR_MSG: standardErrors,
|
|
1351
|
+
LIQUID_ERROR_MSG: liquidErrors,
|
|
1352
|
+
},
|
|
1353
|
+
}),
|
|
1354
|
+
() => {
|
|
1355
|
+
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
|
|
1356
|
+
this.props.stopValidation();
|
|
1373
1357
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1358
|
+
);
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1361
|
+
const onSuccess = (contentToSubmit) => {
|
|
1362
|
+
const channel = this.props.channel || this.props?.schema?.channel?.toUpperCase();
|
|
1363
|
+
if(channel === EMAIL) {
|
|
1364
|
+
const content = this.state.formData?.base?.[this.props.baseLanguage]?.["template-content"] || "";
|
|
1365
|
+
this.handleLiquidTemplateSubmit(content);
|
|
1366
|
+
} else {
|
|
1367
|
+
this.handleLiquidTemplateSubmit(contentToSubmit);
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
// Call the common validation function
|
|
1372
|
+
validateLiquidTemplateContent(content, {
|
|
1373
|
+
getLiquidTags: this.props.actions.getLiquidTags,
|
|
1374
|
+
formatMessage: this.props.intl.formatMessage,
|
|
1375
|
+
messages,
|
|
1376
|
+
onError,
|
|
1377
|
+
onSuccess,
|
|
1378
|
+
tagLookupMap: this.props?.metaEntities?.tagLookupMap,
|
|
1379
|
+
eventContextTags: this.props?.eventContextTags,
|
|
1380
|
+
isLiquidFlow: this.liquidFlow(),
|
|
1381
|
+
forwardedTags: this.props?.isLoyaltyModule ? this.props?.forwardedTags : {},
|
|
1382
|
+
skipTags: this.skipTags.bind(this)
|
|
1384
1383
|
});
|
|
1385
1384
|
} else {
|
|
1386
1385
|
this.props.onSubmit(this.state.formData);
|
|
@@ -1407,6 +1406,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1407
1406
|
|
|
1408
1407
|
// Set up callbacks for error and success handling
|
|
1409
1408
|
const onLiquidError = ({ standardErrors, liquidErrors }) => {
|
|
1409
|
+
|
|
1410
1410
|
this.setState(
|
|
1411
1411
|
(prevState) => ({
|
|
1412
1412
|
liquidErrorMessage: {
|
|
@@ -1418,8 +1418,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1418
1418
|
() => {
|
|
1419
1419
|
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.state.currentTab);
|
|
1420
1420
|
this.props.stopValidation();
|
|
1421
|
-
// Block save: tell parent form is invalid so Done/submit is blocked
|
|
1422
|
-
this.props.onFormValidityChange(false, this.state.errorData);
|
|
1423
1421
|
}
|
|
1424
1422
|
);
|
|
1425
1423
|
};
|
|
@@ -1449,6 +1447,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1449
1447
|
getLiquidTags: this.props.actions.getLiquidTags,
|
|
1450
1448
|
formatMessage: this.props.intl.formatMessage,
|
|
1451
1449
|
messages: messages,
|
|
1450
|
+
tagLookupMap: this.props?.metaEntities?.tagLookupMap,
|
|
1451
|
+
eventContextTags: this.props?.eventContextTags,
|
|
1452
|
+
isLiquidFlow: this.liquidFlow(), // Use the method instead of props
|
|
1453
|
+
forwardedTags: this.props?.isLoyaltyModule ? this.props?.forwardedTags : {},
|
|
1454
|
+
skipTags: this.skipTags.bind(this),
|
|
1455
|
+
extractNames,
|
|
1456
|
+
checkSupport,
|
|
1452
1457
|
singleTab: singleTab?.toUpperCase(),
|
|
1453
1458
|
});
|
|
1454
1459
|
}
|
|
@@ -1503,40 +1508,115 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1503
1508
|
});
|
|
1504
1509
|
}
|
|
1505
1510
|
|
|
1506
|
-
validateTags(content, tagsParam, isEmail = false, isFullMode = this.props?.isFullMode) {
|
|
1511
|
+
validateTags(content, tagsParam, injectedTagsParams, isEmail = false, isFullMode = this.props?.isFullMode) {
|
|
1512
|
+
const type = (this.props.location && this.props.location.query.type) ? this.props.location.query.type : 'full';
|
|
1507
1513
|
let currentModule = this.props.location.query.module ? this.props.location.query.module : 'default';
|
|
1508
1514
|
if (this.props.tagModule) {
|
|
1509
1515
|
currentModule = this.props.tagModule;
|
|
1510
1516
|
}
|
|
1511
1517
|
const tags = tagsParam ? tagsParam : this.props.tags;
|
|
1512
|
-
const
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1518
|
+
const injectedTags = this.transformInjectedTags(injectedTagsParams ? injectedTagsParams : this.props.injectedTags);
|
|
1519
|
+
const excludedTags = ['user_id_b64', 'outbox_id_b64'];
|
|
1520
|
+
|
|
1521
|
+
|
|
1522
|
+
const response = {};
|
|
1523
|
+
response.valid = true;
|
|
1524
|
+
response.missingTags = [];
|
|
1525
|
+
response.unsupportedTags = [];
|
|
1526
|
+
response.isBraceError = false;
|
|
1527
|
+
response.isContentEmpty = false;
|
|
1528
|
+
const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content ;
|
|
1529
|
+
const isModuleTypeOutbound = (this.props?.moduleType || '').toUpperCase() === OUTBOUND;
|
|
1530
|
+
// Run tag validation (missing + unsupported): library mode, or full mode with liquid support, or
|
|
1531
|
+
// legacy Email (CK Editor) when unsubscribe is required (EMAIL_UNSUBSCRIBE_TAG_MANDATORY false) so missing-unsubscribe error shows
|
|
1532
|
+
const shouldRunTagValidation = !isFullMode
|
|
1533
|
+
|| (isEmail && hasLiquidSupportFeature())
|
|
1534
|
+
|| (isEmail && !isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound);
|
|
1535
|
+
if (tags && tags.length && !isFullMode) {
|
|
1536
|
+
_.forEach(tags, (tag) => {
|
|
1537
|
+
_.forEach(tag.definition.supportedModules, (module) => {
|
|
1538
|
+
if (module.mandatory && (currentModule === module.context)) {
|
|
1539
|
+
if (content.indexOf(`{{${tag.definition.value}}}`) === -1) {
|
|
1540
|
+
response.valid = false;
|
|
1541
|
+
response.missingTags.push(tag.definition.value);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
});
|
|
1546
|
+
// Legacy Email (CK Editor): when unsubscribe is required, ensure we validate it even if tag schema didn't mark it mandatory for this module
|
|
1547
|
+
if (isEmail && !isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound) {
|
|
1548
|
+
const hasUnsubscribeInContent = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g.test(content);
|
|
1549
|
+
if (!hasUnsubscribeInContent && response.missingTags.indexOf('unsubscribe') === -1) {
|
|
1550
|
+
response.valid = false;
|
|
1551
|
+
response.missingTags.push('unsubscribe');
|
|
1552
|
+
}
|
|
1536
1553
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1554
|
+
const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
|
|
1555
|
+
let match = regex.exec(content);
|
|
1556
|
+
const regexImgSrc=/<img[^>]*\bsrc\s*=\s*"[^"]*{{customer_barcode}}[^"]*"/;
|
|
1557
|
+
let matchImg = regexImgSrc.exec(content);
|
|
1558
|
+
const regexCustomerBarcode = /{{customer_barcode}}(?![^<]*>)/g;
|
|
1559
|
+
let matchCustomerBarcode = regexCustomerBarcode.exec(content);
|
|
1560
|
+
// \S matches anything other than a space, a tab, a newline, or a carriage return.
|
|
1561
|
+
const validString= /\S/.test(contentForValidation);
|
|
1562
|
+
if (isEmailUnsubscribeTagMandatory() && isEmail && isModuleTypeOutbound) {
|
|
1563
|
+
const missingTagIndex = response?.missingTags?.indexOf("unsubscribe");
|
|
1564
|
+
if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
|
|
1565
|
+
response?.missingTags?.splice(missingTagIndex, 1);
|
|
1566
|
+
}
|
|
1567
|
+
if (validString) {
|
|
1568
|
+
response.valid = true;
|
|
1569
|
+
} else {
|
|
1570
|
+
response.isContentEmpty = true;
|
|
1571
|
+
}
|
|
1539
1572
|
}
|
|
1573
|
+
while (match !== null ) {
|
|
1574
|
+
const tagValue = match[0].substring(this.indexOfEnd(match[0], '{{'), match[0].indexOf('}}'));
|
|
1575
|
+
const tagIndex = match?.index;
|
|
1576
|
+
match = regex.exec(content);
|
|
1577
|
+
let ifSupported = false;
|
|
1578
|
+
_.forEach(tags, (tag) => {
|
|
1579
|
+
if (tag.definition.value === tagValue) {
|
|
1580
|
+
ifSupported = true;
|
|
1581
|
+
}
|
|
1582
|
+
if(tagValue === CUSTOMER_BARCODE_TAG && (matchImg === null || matchCustomerBarcode !== null)){
|
|
1583
|
+
ifSupported = false;
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
const ifSkipped = this.skipTags(tagValue);
|
|
1587
|
+
if (ifSkipped) {
|
|
1588
|
+
ifSupported = true;
|
|
1589
|
+
let isUnsubscribeSkipped = tagValue.indexOf("unsubscribe") != -1 ;
|
|
1590
|
+
if (isUnsubscribeSkipped) {
|
|
1591
|
+
const missingTagIndex = response.missingTags.indexOf("unsubscribe");
|
|
1592
|
+
if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
|
|
1593
|
+
response.missingTags.splice(missingTagIndex, 1);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// Event Context Tags support
|
|
1599
|
+
this.props?.eventContextTags?.forEach((tag) => {
|
|
1600
|
+
if (tagValue === tag?.tagName) {
|
|
1601
|
+
ifSupported = true;
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
|
|
1606
|
+
// Only add to unsupportedTags if not inside a {% ... %} block (scenario 3: liquid orgs also get unsupported-tag errors)
|
|
1607
|
+
if (!ifSupported && !isInsideLiquidBlock(content, tagIndex)) {
|
|
1608
|
+
response.unsupportedTags.push(tagValue);
|
|
1609
|
+
response.valid = false;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
if (response?.unsupportedTags?.length == 0 && response?.missingTags?.length == 0 ) {
|
|
1613
|
+
response.valid = true;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
if(!validateIfTagClosed(contentForValidation)){
|
|
1618
|
+
response.isBraceError = true;
|
|
1619
|
+
response.valid = false;
|
|
1540
1620
|
}
|
|
1541
1621
|
return response;
|
|
1542
1622
|
}
|
|
@@ -2770,21 +2850,21 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2770
2850
|
|
|
2771
2851
|
|
|
2772
2852
|
getMissingOrUnsupportedTagsName = (content = '', type) => {
|
|
2773
|
-
const { MISSING_TAGS } = tagsTypes;
|
|
2853
|
+
const { MISSING_TAGS, UNSUPPORTED_TAGS} = tagsTypes;
|
|
2774
2854
|
const tagValidationResponse = this.validateTags(content);
|
|
2775
|
-
if (type && type === MISSING_TAGS) {
|
|
2776
|
-
return
|
|
2855
|
+
if (type && (type === MISSING_TAGS || type === UNSUPPORTED_TAGS)) {
|
|
2856
|
+
return tagValidationResponse[type].join(', ').toString();
|
|
2777
2857
|
}
|
|
2778
2858
|
return null;
|
|
2779
2859
|
};
|
|
2780
2860
|
|
|
2781
2861
|
renderTextAreaContent = (styling, columns, val, isVersionEnable, rows, cols, offset = 0) => {
|
|
2782
2862
|
const { checkValidation, errorData, currentTab, formData } = this.state;
|
|
2783
|
-
const { MISSING_TAGS } = tagsTypes;
|
|
2863
|
+
const { MISSING_TAGS, UNSUPPORTED_TAGS } = tagsTypes;
|
|
2784
2864
|
const errorType = (isVersionEnable ? errorData[`${currentTab - 1}`][val.id] : errorData[val.id]);
|
|
2785
2865
|
const ifError = checkValidation && errorType;
|
|
2786
2866
|
const messageContent = isVersionEnable ? formData[`${currentTab - 1}`][val.id] : formData[val.id];
|
|
2787
|
-
const { MISSING_TAG_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
|
|
2867
|
+
const { MISSING_TAG_ERROR, UNSUPPORTED_TAG_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
|
|
2788
2868
|
const { formatMessage } = this.props.intl;
|
|
2789
2869
|
|
|
2790
2870
|
const { accessibleFeatures = [] } = this.props.currentOrgDetails || {};
|
|
@@ -2797,6 +2877,9 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2797
2877
|
case MISSING_TAG_ERROR:
|
|
2798
2878
|
errorMessageText = formatMessage(messages.missingTagsValidationError, {missingTags: this.getMissingOrUnsupportedTagsName(messageContent, MISSING_TAGS)});
|
|
2799
2879
|
break;
|
|
2880
|
+
case UNSUPPORTED_TAG_ERROR:
|
|
2881
|
+
errorMessageText = formatMessage(messages.unsupportedTagsValidationError, {unsupportedTags: this.getMissingOrUnsupportedTagsName(messageContent, UNSUPPORTED_TAGS)});
|
|
2882
|
+
break;
|
|
2800
2883
|
case TAG_BRACKET_COUNT_MISMATCH_ERROR:
|
|
2801
2884
|
errorMessageText = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
2802
2885
|
break;
|
|
@@ -2810,13 +2893,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2810
2893
|
if (this.props.restrictPersonalization && hasPersonalizationTags(messageContent)) {
|
|
2811
2894
|
errorMessageText = formatMessage(messages.personalizationTagsErrorMessage);
|
|
2812
2895
|
}
|
|
2813
|
-
// Empty/required error: only show after user has triggered validation (ifError / "Done").
|
|
2814
|
-
// All other errors (brace, personalization, missing tags, generic): show in real time while typing.
|
|
2815
|
-
const isContentEmpty = !messageContent || !/\S/.test(String(messageContent).trim());
|
|
2816
|
-
const isEmptyError = errorType && isContentEmpty;
|
|
2817
|
-
const showError = errorType && (isEmptyError ? ifError : true);
|
|
2818
2896
|
const prevErrorMessage = this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.[0];
|
|
2819
|
-
if (prevErrorMessage !== errorMessageText && errorMessageText && this.
|
|
2897
|
+
if (prevErrorMessage !== errorMessageText && errorMessageText && this.liquidFlow()) {
|
|
2820
2898
|
this.setState((prevState) => ({
|
|
2821
2899
|
liquidErrorMessage: {
|
|
2822
2900
|
...prevState.liquidErrorMessage,
|
|
@@ -2827,25 +2905,15 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2827
2905
|
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
|
|
2828
2906
|
});
|
|
2829
2907
|
}
|
|
2830
|
-
|
|
2831
|
-
// Previously the textarea was inside the `else` branch, so it was not rendered during the
|
|
2832
|
-
// one render cycle when the liquid error message was first set – causing the input to lose
|
|
2833
|
-
// focus whenever a brace/tag error first appeared.
|
|
2908
|
+
else{
|
|
2834
2909
|
if (styling === 'semantic') {
|
|
2835
2910
|
columns.push(
|
|
2836
2911
|
<CapColumn key="input" span={val.width} offset={offset}>
|
|
2837
2912
|
<TextArea
|
|
2838
2913
|
id={val.id}
|
|
2839
2914
|
placeholder={val.placeholder ? val.placeholder : ''}
|
|
2840
|
-
className={`${
|
|
2841
|
-
errorMessage={
|
|
2842
|
-
showError && errorMessageText && (
|
|
2843
|
-
!this.isLiquidFlowSupportedByChannel() ||
|
|
2844
|
-
[MOBILE_PUSH, INAPP].includes(this.props.schema?.channel?.toUpperCase())
|
|
2845
|
-
)
|
|
2846
|
-
? errorMessageText
|
|
2847
|
-
: ''
|
|
2848
|
-
}
|
|
2915
|
+
className={`${ifError ? 'error-form-builder' : ''}`}
|
|
2916
|
+
errorMessage={errorMessageText && !this.liquidFlow() ? errorMessageText : ''}
|
|
2849
2917
|
label={val.label}
|
|
2850
2918
|
autosize={val.autosize ? val.autosizeParams : false}
|
|
2851
2919
|
onChange={(e) => this.updateFormData(e.target.value, val)}
|
|
@@ -2876,6 +2944,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2876
2944
|
</CapColumn>
|
|
2877
2945
|
);
|
|
2878
2946
|
}
|
|
2947
|
+
}
|
|
2879
2948
|
};
|
|
2880
2949
|
|
|
2881
2950
|
|
|
@@ -3938,7 +4007,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3938
4007
|
<CapColumn
|
|
3939
4008
|
style={val.colStyle ? val.colStyle : {border : ""}}
|
|
3940
4009
|
span={val.width}
|
|
3941
|
-
className={(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.
|
|
4010
|
+
className={`${(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.liquidFlow() && "error-boundary"} `}
|
|
3942
4011
|
>
|
|
3943
4012
|
<CKEditor
|
|
3944
4013
|
id={val.id}
|
|
@@ -3982,7 +4051,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3982
4051
|
isModuleFilterEnabled = this.props.isFullMode;
|
|
3983
4052
|
}
|
|
3984
4053
|
columns.push(
|
|
3985
|
-
<CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.
|
|
4054
|
+
<CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={`${(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.liquidFlow() && "error-boundary"}`}>
|
|
3986
4055
|
<BeeEditor
|
|
3987
4056
|
uid={uuid}
|
|
3988
4057
|
tokenData={beeToken}
|
|
@@ -4268,7 +4337,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4268
4337
|
|
|
4269
4338
|
|
|
4270
4339
|
return (
|
|
4271
|
-
<CapSpin spinning={Boolean((this.
|
|
4340
|
+
<CapSpin spinning={Boolean((this.liquidFlow() && this.props.liquidExtractionInProgress) || this.props.metaDataStatus === REQUEST)} tip={this.props.intl.formatMessage(messages.liquidSpinText)} >
|
|
4272
4341
|
<CapRow>
|
|
4273
4342
|
{this.props.schema && this.renderForm()}
|
|
4274
4343
|
<SlideBox
|
|
@@ -26,6 +26,10 @@ export default defineMessages({
|
|
|
26
26
|
id: 'creatives.components.FormBuilder.missingTagsValidationError',
|
|
27
27
|
defaultMessage: 'Missing tags: {missingTags}. Please add them to this message.',
|
|
28
28
|
},
|
|
29
|
+
unsupportedTagsValidationError: {
|
|
30
|
+
id: 'creatives.components.FormBuilder.unsupportedTagsValidationError',
|
|
31
|
+
defaultMessage: 'Unsupported tags: {unsupportedTags}. Please remove them from this message.',
|
|
32
|
+
},
|
|
29
33
|
genericTagsValidationError: {
|
|
30
34
|
id: 'creatives.components.FormBuilder.genericTagsValidationError',
|
|
31
35
|
defaultMessage: 'Please check the message content for unsupported/missing tags',
|
|
@@ -38,6 +42,10 @@ export default defineMessages({
|
|
|
38
42
|
id: 'creatives.componentsV2.FormBuilder.missingTags',
|
|
39
43
|
defaultMessage: 'Missing tags are:',
|
|
40
44
|
},
|
|
45
|
+
unsupportedTags: {
|
|
46
|
+
id: 'creatives.componentsV2.FormBuilder.unsupportedTags',
|
|
47
|
+
defaultMessage: 'Unsupported tags are:',
|
|
48
|
+
},
|
|
41
49
|
upload: {
|
|
42
50
|
id: 'creatives.componentsV2.FormBuilder.upload',
|
|
43
51
|
defaultMessage: 'Upload',
|