@capillarytech/creatives-library 8.0.292-alpha.0 → 8.0.292-alpha.10
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 +203 -137
- package/v2Components/FormBuilder/messages.js +8 -0
- package/v2Components/HtmlEditor/HTMLEditor.js +11 -2
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
- package/v2Components/HtmlEditor/_htmlEditor.scss +6 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +10 -10
- package/v2Components/HtmlEditor/components/DeviceToggle/FLOW_AND_CLICK_BEHAVIOUR.md +70 -0
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +15 -8
- 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 +2 -5
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -13
- package/v2Containers/CreativesContainer/index.js +10 -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 +139 -22
- package/v2Containers/InApp/tests/index.test.js +6 -17
- package/v2Containers/InappAdvance/index.js +118 -8
- package/v2Containers/InappAdvance/tests/index.test.js +2 -3
- 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
|
@@ -6,7 +6,7 @@ import CapError from '@capillarytech/cap-ui-library/CapError';
|
|
|
6
6
|
import PropTypes from 'prop-types';
|
|
7
7
|
import messages from './messages';
|
|
8
8
|
import ErrorInfoNote from '../../v2Components/ErrorInfoNote';
|
|
9
|
-
import { PREVIEW
|
|
9
|
+
import { PREVIEW } from './constants';
|
|
10
10
|
import { EMAIL_CREATE_MODES } from '../EmailWrapper/constants';
|
|
11
11
|
import { hasSupportCKEditor } from '../../utils/common';
|
|
12
12
|
import { getMessageForDevice, getTitleForDevice } from '../../utils/commonUtils';
|
|
@@ -15,7 +15,7 @@ function getFullModeSaveBtn(slidBoxContent, isCreatingTemplate) {
|
|
|
15
15
|
if (isCreatingTemplate) {
|
|
16
16
|
return <FormattedMessage {...messages.creativesTemplatesDone} />;
|
|
17
17
|
}
|
|
18
|
-
return slidBoxContent ===
|
|
18
|
+
return slidBoxContent === "editTemplate"
|
|
19
19
|
? <FormattedMessage {...messages.creativesTemplatesUpdate} />
|
|
20
20
|
: <FormattedMessage {...messages.creativesTemplatesSaveFullMode} />;
|
|
21
21
|
}
|
|
@@ -52,13 +52,8 @@ function SlideBoxFooter(props) {
|
|
|
52
52
|
// Calculate if buttons should be disabled
|
|
53
53
|
// Only apply validation state checks for EMAIL channel in HTML Editor mode (not BEE/DragDrop)
|
|
54
54
|
// For other channels, BEE editor, or when htmlEditorValidationState is not provided, don't disable based on validation
|
|
55
|
-
const isEmailChannel = currentChannel?.toUpperCase() === EMAIL;
|
|
56
|
-
const
|
|
57
|
-
// Use templateData.type in library/edit so footer shows when editing a mobile push template (currentChannel may not be set to template channel)
|
|
58
|
-
const isMobilePushChannel =
|
|
59
|
-
currentChannel?.toUpperCase() === MOBILE_PUSH ||
|
|
60
|
-
(templateData?.type && templateData.type.toUpperCase() === MOBILE_PUSH);
|
|
61
|
-
const isEditMode = slidBoxContent === EDIT_TEMPLATE;
|
|
55
|
+
const isEmailChannel = currentChannel?.toUpperCase() === 'EMAIL';
|
|
56
|
+
const isEditMode = slidBoxContent === 'editTemplate';
|
|
62
57
|
|
|
63
58
|
// Use selectedEmailCreateMode for accurate mode detection in create mode (emailCreateMode is mapped for backwards compatibility)
|
|
64
59
|
// In edit mode: htmlEditorValidationState is initialized as {} but only updated by HTML Editor
|
|
@@ -134,11 +129,8 @@ function SlideBoxFooter(props) {
|
|
|
134
129
|
const isBEEEditorModeInCreate = !isHTMLEditorMode && !isEditMode;
|
|
135
130
|
const isBEEEditorMode = isBEEEditorModeInEdit || isBEEEditorModeInCreate;
|
|
136
131
|
const hasBEEEditorErrors = isEmailChannel && isBEEEditorMode && (hasStandardErrors || hasLiquidErrors) && (!htmlEditorValidationState || !htmlEditorHasErrors);
|
|
137
|
-
const hasSmsValidationErrors = isSmsChannel && (hasStandardErrors || hasLiquidErrors);
|
|
138
|
-
// Mobile Push OLD: footer only for extractTags/Aira liquid errors, not standard tag errors
|
|
139
|
-
const hasMobilePushValidationErrors = isMobilePushChannel && hasLiquidErrors;
|
|
140
132
|
|
|
141
|
-
const shouldShowErrorInfoNote = hasBEEEditorErrors ||
|
|
133
|
+
const shouldShowErrorInfoNote = hasBEEEditorErrors || isSupportCKEditor;
|
|
142
134
|
|
|
143
135
|
// Check for personalization tokens in title/message when anonymous user tries to save
|
|
144
136
|
const hasPersonalizationTokens = () => {
|
|
@@ -132,6 +132,7 @@ export class Creatives extends React.Component {
|
|
|
132
132
|
},
|
|
133
133
|
hasPersonalizationTokenError: false, // Track personalization token errors in form
|
|
134
134
|
};
|
|
135
|
+
this.liquidFlow = Boolean(commonUtil.hasLiquidSupportFeature());
|
|
135
136
|
this.creativesTemplateSteps = {
|
|
136
137
|
1: 'modeSelection',
|
|
137
138
|
2: 'templateSelection', // only for email in current flows wil be used for mpush, line and wechat as well.
|
|
@@ -255,23 +256,12 @@ export class Creatives extends React.Component {
|
|
|
255
256
|
};
|
|
256
257
|
|
|
257
258
|
onShowTemplates = () => {
|
|
258
|
-
this.setState({
|
|
259
|
-
slidBoxContent: 'templates',
|
|
260
|
-
showSlideBox: true,
|
|
261
|
-
isGetFormData: false,
|
|
262
|
-
liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
|
|
263
|
-
isLiquidValidationError: false,
|
|
264
|
-
});
|
|
259
|
+
this.setState({ slidBoxContent: 'templates', showSlideBox: true, isGetFormData: false });
|
|
265
260
|
this.resetStep();
|
|
266
261
|
};
|
|
267
262
|
|
|
268
263
|
onChannelChange = (channel) => {
|
|
269
|
-
this.setState({
|
|
270
|
-
currentChannel: channel,
|
|
271
|
-
templateData: null,
|
|
272
|
-
liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
|
|
273
|
-
isLiquidValidationError: false,
|
|
274
|
-
});
|
|
264
|
+
this.setState({ currentChannel: channel, templateData: null });
|
|
275
265
|
}
|
|
276
266
|
|
|
277
267
|
onCreateNextStep = () => {
|
|
@@ -1479,12 +1469,11 @@ export class Creatives extends React.Component {
|
|
|
1479
1469
|
}
|
|
1480
1470
|
|
|
1481
1471
|
getFormData = (template) => {
|
|
1482
|
-
// Always reset isGetFormData so the child does not re-send form data on every re-render
|
|
1483
|
-
// (e.g. when user fixes validation error by typing, we must not auto-close the slidebox)
|
|
1484
|
-
this.setState({ isGetFormData: false });
|
|
1485
1472
|
if (template.validity) {
|
|
1486
1473
|
this.setState(
|
|
1487
|
-
{
|
|
1474
|
+
{
|
|
1475
|
+
isGetFormData: false,
|
|
1476
|
+
},
|
|
1488
1477
|
() => {
|
|
1489
1478
|
const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
|
|
1490
1479
|
const channel = templateData.type;
|
|
@@ -1568,8 +1557,6 @@ export class Creatives extends React.Component {
|
|
|
1568
1557
|
...prevState,
|
|
1569
1558
|
templateData: undefined,
|
|
1570
1559
|
showSlideBox: false,
|
|
1571
|
-
liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
|
|
1572
|
-
isLiquidValidationError: false,
|
|
1573
1560
|
}), () => this.props.handleCloseCreatives(reloadTemplates));
|
|
1574
1561
|
};
|
|
1575
1562
|
|
|
@@ -1772,23 +1759,16 @@ export class Creatives extends React.Component {
|
|
|
1772
1759
|
showTemplateNameComponentEdit: true,
|
|
1773
1760
|
localTemplateName: name || '', // Initialize local state with current value
|
|
1774
1761
|
});
|
|
1762
|
+
} else {
|
|
1763
|
+
// e.g. InApp edit with empty name: still show header with "Edit name" link
|
|
1764
|
+
this.setState({ showTemplateNameComponentEdit: false });
|
|
1775
1765
|
}
|
|
1776
1766
|
}
|
|
1777
1767
|
}
|
|
1778
1768
|
|
|
1779
1769
|
showLiquidErrorInFooter = (errorMessagesFromFormBuilder, currentFormBuilderTab) => {
|
|
1780
|
-
const liquidMsgs = get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, []);
|
|
1781
|
-
const standardMsgs = get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, []);
|
|
1782
|
-
const hasLiquid = Array.isArray(liquidMsgs) ? liquidMsgs.length > 0 : !isEmpty(liquidMsgs);
|
|
1783
|
-
const hasStandard = Array.isArray(standardMsgs) ? standardMsgs.length > 0 : !isEmpty(standardMsgs);
|
|
1784
|
-
const isLiquidValidationError = hasLiquid || hasStandard;
|
|
1785
|
-
// Don't overwrite existing liquid error with empty only for Mobile Push OLD (FormBuilder/clear calls empty there); SMS/others clear on input change
|
|
1786
|
-
const isMobilePush = this.state.currentChannel?.toUpperCase() === constants.MOBILE_PUSH;
|
|
1787
|
-
if (!hasLiquid && !hasStandard && this.state.isLiquidValidationError && isMobilePush) {
|
|
1788
|
-
return;
|
|
1789
|
-
}
|
|
1790
1770
|
this.setState({
|
|
1791
|
-
isLiquidValidationError,
|
|
1771
|
+
isLiquidValidationError: !isEmpty(get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, [])) || !isEmpty(get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, [])),
|
|
1792
1772
|
liquidErrorMessage: errorMessagesFromFormBuilder,
|
|
1793
1773
|
activeFormBuilderTab: currentFormBuilderTab === 1 ? constants.ANDROID : (currentFormBuilderTab === 2 ? constants.IOS : null), // Update activeFormBuilderTab, default to 1 if undefined
|
|
1794
1774
|
});
|
|
@@ -26,7 +26,7 @@ import * as globalActions from '../Cap/actions';
|
|
|
26
26
|
import './_email.scss';
|
|
27
27
|
import {getMessageObject} from '../../utils/messageUtils';
|
|
28
28
|
import EmailPreview from '../../v2Components/EmailPreview';
|
|
29
|
-
import { getDecodedFileName, hasSupportCKEditor } from '../../utils/common';
|
|
29
|
+
import { getDecodedFileName, hasLiquidSupportFeature, hasSupportCKEditor } from '../../utils/common';
|
|
30
30
|
import Pagination from '../../v2Components/Pagination';
|
|
31
31
|
import * as creativesContainerActions from '../CreativesContainer/actions';
|
|
32
32
|
import withCreatives from '../../hoc/withCreatives';
|
|
@@ -170,6 +170,7 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
170
170
|
deleteLanguage: this.deleteLanguage,
|
|
171
171
|
},
|
|
172
172
|
};
|
|
173
|
+
this.liquidFlow = hasLiquidSupportFeature();
|
|
173
174
|
}
|
|
174
175
|
componentWillMount() {
|
|
175
176
|
const formData = this.initFormData(this.props, true); //_.cloneDeep(this.state.formData);
|
|
@@ -254,6 +255,7 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
254
255
|
layout: 'EMAIL',
|
|
255
256
|
type: 'LAYOUT',
|
|
256
257
|
version: 'v2',
|
|
258
|
+
liquidFlow:this.liquidFlow,
|
|
257
259
|
};
|
|
258
260
|
this.props.globalActions.fetchSchemaForEntity(query);
|
|
259
261
|
window.addEventListener("message", this.handleFrameTasks);
|
|
@@ -345,6 +347,7 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
345
347
|
type: 'TAG',
|
|
346
348
|
context: this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default',
|
|
347
349
|
embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
|
|
350
|
+
liquidFlow:this.liquidFlow
|
|
348
351
|
};
|
|
349
352
|
if (this.props.getDefaultTags) {
|
|
350
353
|
query.context = this.props.getDefaultTags;
|
|
@@ -2370,6 +2373,7 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
2370
2373
|
type: 'TAG',
|
|
2371
2374
|
context: (data || '').toLowerCase() === 'all' ? 'default' : (data || '').toLowerCase(),
|
|
2372
2375
|
embedded: 'full',
|
|
2376
|
+
liquidFlow:this.liquidFlow
|
|
2373
2377
|
};
|
|
2374
2378
|
this.props.globalActions.fetchSchemaForEntity(query);
|
|
2375
2379
|
}
|
|
@@ -15,7 +15,7 @@ import HTMLEditor from '../../../v2Components/HtmlEditor';
|
|
|
15
15
|
import CapTagListWithInput from '../../../v2Components/CapTagListWithInput';
|
|
16
16
|
import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
|
|
17
17
|
import { validateLiquidTemplateContent } from '../../../utils/commonUtils';
|
|
18
|
-
import {
|
|
18
|
+
import { hasLiquidSupportFeature, isEmailUnsubscribeTagMandatory } from '../../../utils/common';
|
|
19
19
|
import history from '../../../utils/history';
|
|
20
20
|
import messages from '../messages';
|
|
21
21
|
import emailMessages from '../../Email/messages';
|
|
@@ -108,10 +108,13 @@ const EmailHTMLEditor = (props) => {
|
|
|
108
108
|
standardErrors: [],
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
// Merge tag validation errors (missing) into apiValidationErrors so they show in ValidationErrorDisplay
|
|
111
|
+
// Merge tag validation errors (unsupported/missing) into apiValidationErrors so they show in ValidationErrorDisplay
|
|
112
112
|
const mergedApiValidationErrors = useMemo(() => {
|
|
113
113
|
const tagMessages = [];
|
|
114
|
-
if (tagValidationError?.
|
|
114
|
+
if (tagValidationError?.unsupportedTags?.length) {
|
|
115
|
+
tagMessages.push(`Unsupported tags are: ${tagValidationError.unsupportedTags.join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
if (tagValidationError?.missingTags?.length && !isEmailUnsubscribeTagMandatory()) {
|
|
115
118
|
tagMessages.push(`Missing tags are: ${tagValidationError.missingTags.join(', ')}`);
|
|
116
119
|
}
|
|
117
120
|
if (tagMessages.length === 0) {
|
|
@@ -187,6 +190,9 @@ const EmailHTMLEditor = (props) => {
|
|
|
187
190
|
},
|
|
188
191
|
}), [htmlContent, subject, currentOrgDetails]);
|
|
189
192
|
|
|
193
|
+
// Check if liquid support is enabled
|
|
194
|
+
const isLiquidEnabled = hasLiquidSupportFeature();
|
|
195
|
+
|
|
190
196
|
// Detect edit mode: when isEditEmail is false (create flow), never treat as edit or fetch template details
|
|
191
197
|
const hasParamsId = params?.id || location?.query?.id || location?.params?.id || location?.pathname?.includes('/edit/');
|
|
192
198
|
const currentTemplateId = isEditEmail
|
|
@@ -482,25 +488,15 @@ const EmailHTMLEditor = (props) => {
|
|
|
482
488
|
const handleContentChange = useCallback((content) => {
|
|
483
489
|
setHtmlContent(content);
|
|
484
490
|
|
|
485
|
-
// Clear previous liquid/API validation errors so Done button can be enabled after user fixes content
|
|
486
|
-
setApiValidationErrors({
|
|
487
|
-
liquidErrors: [],
|
|
488
|
-
standardErrors: [],
|
|
489
|
-
});
|
|
490
|
-
if (showLiquidErrorInFooter) {
|
|
491
|
-
showLiquidErrorInFooter({
|
|
492
|
-
STANDARD_ERROR_MSG: [],
|
|
493
|
-
LIQUID_ERROR_MSG: [],
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
|
|
497
491
|
// Validate tags
|
|
498
492
|
if (tags.length > 0 || !isEmpty(injectedTags)) {
|
|
499
493
|
const validationResult = validateTags({
|
|
500
494
|
content,
|
|
501
495
|
tagsParam: tags,
|
|
496
|
+
injectedTagsParams: injectedTags,
|
|
502
497
|
location,
|
|
503
498
|
tagModule: getDefaultTags,
|
|
499
|
+
eventContextTags,
|
|
504
500
|
isFullMode,
|
|
505
501
|
});
|
|
506
502
|
|
|
@@ -510,7 +506,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
510
506
|
setTagValidationError(null);
|
|
511
507
|
}
|
|
512
508
|
}
|
|
513
|
-
}, [tags, injectedTags, location, getDefaultTags, eventContextTags
|
|
509
|
+
}, [tags, injectedTags, location, getDefaultTags, eventContextTags]);
|
|
514
510
|
|
|
515
511
|
// Store the last validation state received from HTMLEditor
|
|
516
512
|
const lastValidationStateRef = useRef(null);
|
|
@@ -654,7 +650,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
654
650
|
// IMPORTANT: Clear API validation errors FIRST before checking for validation errors
|
|
655
651
|
// This ensures that old API errors don't block the save when user fixes content and clicks Update again
|
|
656
652
|
// We'll re-validate with fresh API call anyway
|
|
657
|
-
if (getLiquidTags) {
|
|
653
|
+
if (isLiquidEnabled && getLiquidTags) {
|
|
658
654
|
setApiValidationErrors({
|
|
659
655
|
liquidErrors: [],
|
|
660
656
|
standardErrors: [],
|
|
@@ -716,7 +712,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
716
712
|
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false: validate and require unsubscribe tag.
|
|
717
713
|
// Run for both library and full mode so liquid-enabled orgs also get the error (notification + ValidationErrorDisplay).
|
|
718
714
|
const isModuleTypeOutbound = (moduleType || '').toUpperCase() === OUTBOUND;
|
|
719
|
-
if (!
|
|
715
|
+
if (!isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound) {
|
|
720
716
|
const unsubscribeRegex = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g; // eslint-disable-line no-useless-escape
|
|
721
717
|
const hasUnsubscribeTag = unsubscribeRegex.test(htmlContent);
|
|
722
718
|
|
|
@@ -748,17 +744,52 @@ const EmailHTMLEditor = (props) => {
|
|
|
748
744
|
const validationResult = validateTags({
|
|
749
745
|
content: htmlContent,
|
|
750
746
|
tagsParam: tags,
|
|
747
|
+
injectedTagsParams: injectedTags,
|
|
751
748
|
location,
|
|
752
749
|
tagModule: getDefaultTags,
|
|
750
|
+
eventContextTags,
|
|
753
751
|
isFullMode,
|
|
754
752
|
});
|
|
755
753
|
|
|
756
|
-
|
|
754
|
+
const hasUnsupportedTags = validationResult?.unsupportedTags?.length > 0;
|
|
755
|
+
if (!validationResult?.valid || (hasUnsupportedTags && !isFullMode)) {
|
|
757
756
|
setTagValidationError(validationResult);
|
|
758
|
-
// For liquid orgs,
|
|
757
|
+
// IMPORTANT: For non-liquid orgs, block save (like CK/BEE editor)
|
|
758
|
+
// For liquid orgs, continue (extractTags API will validate)
|
|
759
|
+
if (!isLiquidEnabled) {
|
|
760
|
+
// Show notification popup like CK/BEE editor
|
|
761
|
+
const baseLanguage = get(currentOrgDetails, 'basic_details.base_language', 'en');
|
|
762
|
+
|
|
763
|
+
const contentNotValidMsg = intl.formatMessage(formBuilderMessages.contentNotValidLanguage);
|
|
764
|
+
let errorMessage = `${contentNotValidMsg} ${baseLanguage}`;
|
|
765
|
+
|
|
766
|
+
if (hasUnsupportedTags) {
|
|
767
|
+
const unsupportedTagsMsg = intl.formatMessage(formBuilderMessages.unsupportedTags);
|
|
768
|
+
errorMessage += `\n${unsupportedTagsMsg} ${validationResult?.unsupportedTags?.join(', ')}`;
|
|
769
|
+
}
|
|
770
|
+
if (validationResult?.missingTags?.length > 0) {
|
|
771
|
+
const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
|
|
772
|
+
errorMessage += `\n${missingTagsMsg} ${validationResult?.missingTags?.join(', ')}`;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const type = 'error';
|
|
776
|
+
CapNotification[type]({
|
|
777
|
+
message: `${type.toUpperCase()} ! ! ! `,
|
|
778
|
+
description: errorMessage,
|
|
779
|
+
duration: 5,
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// Reset parent state so next click is detected as a change
|
|
783
|
+
if (onValidationFail) {
|
|
784
|
+
onValidationFail();
|
|
785
|
+
}
|
|
786
|
+
// Block save for non-liquid orgs
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
// For liquid orgs, just show warning and continue
|
|
759
790
|
}
|
|
760
791
|
// Clear tag errors if valid
|
|
761
|
-
if (tagValidationError && validationResult?.valid) {
|
|
792
|
+
if (tagValidationError && validationResult?.valid && !hasUnsupportedTags) {
|
|
762
793
|
setTagValidationError(null);
|
|
763
794
|
}
|
|
764
795
|
}
|
|
@@ -925,9 +956,13 @@ const EmailHTMLEditor = (props) => {
|
|
|
925
956
|
}
|
|
926
957
|
};
|
|
927
958
|
|
|
928
|
-
//
|
|
929
|
-
if (
|
|
959
|
+
// If liquid enabled, validate first using extractTags API
|
|
960
|
+
if (isLiquidEnabled && getLiquidTags) {
|
|
961
|
+
// Note: API validation errors are already cleared at the start of handleSave
|
|
962
|
+
// This ensures fresh validation on every save attempt
|
|
963
|
+
|
|
930
964
|
const onError = ({ standardErrors, liquidErrors }) => {
|
|
965
|
+
// Store API validation errors in state so they can be displayed in UI
|
|
931
966
|
setApiValidationErrors({
|
|
932
967
|
liquidErrors: liquidErrors || [],
|
|
933
968
|
standardErrors: standardErrors || [],
|
|
@@ -939,12 +974,15 @@ const EmailHTMLEditor = (props) => {
|
|
|
939
974
|
LIQUID_ERROR_MSG: liquidErrors || [],
|
|
940
975
|
});
|
|
941
976
|
}
|
|
977
|
+
// Don't reset ref here - liquid validation is async and resetting causes infinite loop
|
|
978
|
+
// The parent's isGetFormData will be reset by onValidationFail, and the next click will be detected
|
|
942
979
|
if (onValidationFail) {
|
|
943
980
|
onValidationFail();
|
|
944
981
|
}
|
|
945
982
|
};
|
|
946
983
|
|
|
947
984
|
const onSuccess = () => {
|
|
985
|
+
// Clear API validation errors on success
|
|
948
986
|
setApiValidationErrors({
|
|
949
987
|
liquidErrors: [],
|
|
950
988
|
standardErrors: [],
|
|
@@ -960,6 +998,10 @@ const EmailHTMLEditor = (props) => {
|
|
|
960
998
|
messages: formBuilderMessages,
|
|
961
999
|
onError,
|
|
962
1000
|
onSuccess,
|
|
1001
|
+
tagLookupMap: metaEntities?.tagLookupMap,
|
|
1002
|
+
eventContextTags,
|
|
1003
|
+
isLiquidFlow: true,
|
|
1004
|
+
forwardedTags: forwardedTags || {},
|
|
963
1005
|
});
|
|
964
1006
|
} else {
|
|
965
1007
|
performSave();
|
|
@@ -971,6 +1013,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
971
1013
|
injectedTags,
|
|
972
1014
|
location,
|
|
973
1015
|
getDefaultTags,
|
|
1016
|
+
eventContextTags,
|
|
974
1017
|
formatMessage,
|
|
975
1018
|
subjectError,
|
|
976
1019
|
isFullMode,
|
|
@@ -982,8 +1025,11 @@ const EmailHTMLEditor = (props) => {
|
|
|
982
1025
|
emailActions,
|
|
983
1026
|
getFormdata,
|
|
984
1027
|
isGetFormData,
|
|
1028
|
+
isLiquidEnabled,
|
|
985
1029
|
getLiquidTags,
|
|
986
1030
|
showLiquidErrorInFooter,
|
|
1031
|
+
metaEntities,
|
|
1032
|
+
forwardedTags,
|
|
987
1033
|
globalActions,
|
|
988
1034
|
intl,
|
|
989
1035
|
extractedTemplateName,
|
|
@@ -1129,6 +1175,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
1129
1175
|
userLocale={intl.locale || 'en'}
|
|
1130
1176
|
moduleFilterEnabled={location?.query?.type !== EMBEDDED}
|
|
1131
1177
|
onTagContextChange={handleOnTagsContextChange}
|
|
1178
|
+
isLiquidEnabled={isLiquidEnabled}
|
|
1132
1179
|
isFullMode={isFullMode}
|
|
1133
1180
|
onErrorAcknowledged={handleErrorAcknowledged}
|
|
1134
1181
|
onValidationChange={handleValidationChange}
|