@capillarytech/creatives-library 8.0.285-alpha.1 → 8.0.286

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.
Files changed (45) hide show
  1. package/constants/unified.js +1 -0
  2. package/initialState.js +2 -0
  3. package/package.json +1 -1
  4. package/utils/common.js +8 -5
  5. package/utils/commonUtils.js +83 -2
  6. package/utils/tagValidations.js +222 -84
  7. package/utils/tests/commonUtil.test.js +118 -147
  8. package/utils/tests/tagValidations.test.js +358 -280
  9. package/v2Components/ErrorInfoNote/index.js +5 -2
  10. package/v2Components/FormBuilder/index.js +158 -64
  11. package/v2Components/FormBuilder/messages.js +8 -0
  12. package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
  13. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  14. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
  15. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +2 -1
  16. package/v2Containers/Cap/mockData.js +14 -0
  17. package/v2Containers/Cap/reducer.js +55 -3
  18. package/v2Containers/Cap/tests/reducer.test.js +102 -0
  19. package/v2Containers/CreativesContainer/index.js +1 -0
  20. package/v2Containers/Email/index.js +5 -1
  21. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +62 -10
  22. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +115 -12
  23. package/v2Containers/FTP/index.js +51 -2
  24. package/v2Containers/FTP/messages.js +4 -0
  25. package/v2Containers/InApp/index.js +96 -1
  26. package/v2Containers/InApp/tests/index.test.js +6 -17
  27. package/v2Containers/InappAdvance/index.js +103 -2
  28. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +24 -3
  29. package/v2Containers/Line/Container/Text/index.js +1 -0
  30. package/v2Containers/MobilePushNew/index.js +33 -2
  31. package/v2Containers/Rcs/index.js +37 -12
  32. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +18 -4
  33. package/v2Containers/SmsTrai/Create/index.scss +1 -1
  34. package/v2Containers/SmsTrai/Edit/index.js +47 -6
  35. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  36. package/v2Containers/Viber/index.js +1 -0
  37. package/v2Containers/Viber/index.scss +1 -1
  38. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
  39. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
  40. package/v2Containers/WebPush/Create/index.js +2 -2
  41. package/v2Containers/WebPush/Create/utils/validation.js +9 -18
  42. package/v2Containers/WebPush/Create/utils/validation.test.js +24 -0
  43. package/v2Containers/Whatsapp/index.js +17 -9
  44. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +624 -248
  45. package/v2Containers/Zalo/index.js +11 -3
@@ -2,9 +2,11 @@ export const expectedStateGetLiquidTagsRequest = {
2
2
  fetchingLiquidTags: true,
3
3
  fetchingSchema: true,
4
4
  fetchingSchemaError: "",
5
+ liquidTags: [],
5
6
  messages: [],
6
7
  metaEntities: {
7
8
  layouts: [],
9
+ tagLookupMap: {},
8
10
  tags: [],
9
11
  },
10
12
  orgID: "",
@@ -15,9 +17,11 @@ export const expectedStateGetLiquidTagsFailure = {
15
17
  fetchingLiquidTags: false,
16
18
  fetchingSchema: true,
17
19
  fetchingSchemaError: "",
20
+ liquidTags: [],
18
21
  messages: [],
19
22
  metaEntities: {
20
23
  layouts: [],
24
+ tagLookupMap: {},
21
25
  tags: [],
22
26
  },
23
27
  orgID: "",
@@ -28,9 +32,11 @@ export const expectedStateGetLiquidTagsSuccess = {
28
32
  fetchingLiquidTags: false,
29
33
  fetchingSchema: true,
30
34
  fetchingSchemaError: "",
35
+ liquidTags: [],
31
36
  messages: [],
32
37
  metaEntities: {
33
38
  layouts: [],
39
+ tagLookupMap: {},
34
40
  tags: [],
35
41
  },
36
42
  orgID: "",
@@ -41,9 +47,11 @@ export const expectedStateGetSchemaForEntitySuccessTAG = {
41
47
  fetchingLiquidTags: false,
42
48
  fetchingSchema: false,
43
49
  fetchingSchemaError: false,
50
+ liquidTags: [],
44
51
  messages: [],
45
52
  metaEntities: {
46
53
  layouts: undefined,
54
+ tagLookupMap: { undefined: { definition: {} } },
47
55
  tags: { standard: { random: "32" } },
48
56
  },
49
57
  orgID: "",
@@ -54,9 +62,11 @@ export const expectedStateGetSchemaForEntitySuccess = {
54
62
  fetchingLiquidTags: false,
55
63
  fetchingSchema: false,
56
64
  fetchingSchemaError: false,
65
+ liquidTags: [],
57
66
  messages: [],
58
67
  metaEntities: {
59
68
  layouts: undefined,
69
+ tagLookupMap: undefined,
60
70
  tags: undefined,
61
71
  },
62
72
  orgID: "",
@@ -68,9 +78,13 @@ export const expectedForwardedTags = {
68
78
  fetchingSchema: false,
69
79
  fetchingSchemaError: '',
70
80
  injectedTags: undefined,
81
+ liquidTags: [],
71
82
  messages: [],
72
83
  metaEntities: {
73
84
  layouts: [],
85
+ tagLookupMap: {
86
+
87
+ },
74
88
  tags: [],
75
89
  },
76
90
  orgID: "",
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Created by vivek on 22/5/17.
3
3
  */
4
- import { fromJS } from 'immutable';
4
+ import { fromJS, Map as ImmutableMap } from 'immutable';
5
5
  import _ from 'lodash';
6
6
  import * as types from './constants';
7
7
  import initialState from '../../initialState';
8
8
  import { FAILURE } from '../App/constants';
9
+ import { TAG } from '../Whatsapp/constants';
10
+ import { getTagMapValue, getForwardedMapValues, getLoyaltyTagsMapValue } from '../../utils/tagValidations';
9
11
 
10
12
  function capReducer(state = fromJS(initialState.cap), action) {
11
13
  switch (action.type) {
@@ -96,6 +98,39 @@ function capReducer(state = fromJS(initialState.cap), action) {
96
98
  return state
97
99
  .set('fetchingLiquidTags', false);
98
100
  case types.GET_SCHEMA_FOR_ENTITY_SUCCESS: {
101
+ //Process standard tags
102
+ const standardTagMapInitial = _.keyBy(
103
+ action?.data?.metaEntities?.standard,
104
+ item => item?.definition?.value
105
+ );
106
+ // Mapping only the `definition` object instead of the entire item, to reduce space used
107
+ const standardTagMap = _.mapValues(standardTagMapInitial, item => ({
108
+ definition: item?.definition ?? {},
109
+ }));
110
+
111
+ // Process custom tags
112
+ const customSubtags = getTagMapValue(action?.data?.metaEntities?.custom)
113
+ // Process extended tags
114
+ const extendedSubtags = getTagMapValue(action?.data?.metaEntities?.extended);
115
+
116
+ const loyaltySubTagsData = getLoyaltyTagsMapValue(action?.data?.metaEntities?.loyaltyTags);
117
+
118
+ const getExistingTagLookupMap = (state) => {
119
+ if (!state || !state.get) return {};
120
+ const tagLookupMap = state.getIn(['metaEntities', 'tagLookupMap']);
121
+ return state.get('metaEntities') && ImmutableMap.isMap(tagLookupMap)
122
+ ? tagLookupMap.toJS()
123
+ : {};
124
+ };
125
+
126
+ // Combine all maps
127
+ const combinedTagMap = {
128
+ ...standardTagMap,
129
+ ...customSubtags,
130
+ ...extendedSubtags,
131
+ ...loyaltySubTagsData,
132
+ ...getExistingTagLookupMap(state),
133
+ };
99
134
  const stateMeta = state.get("metaEntities");
100
135
  return state
101
136
  .set('fetchingSchema', false)
@@ -103,6 +138,7 @@ function capReducer(state = fromJS(initialState.cap), action) {
103
138
  .set('metaEntities', {
104
139
  layouts: action.data && action.entityType === 'LAYOUT' ? action.data.metaEntities : stateMeta?.layouts,
105
140
  tags: action.data && action.entityType === 'TAG' ? action.data.metaEntities : stateMeta?.tags,
141
+ tagLookupMap: action?.data && action?.entityType === TAG ? combinedTagMap : stateMeta?.tagLookupMap,
106
142
  })
107
143
  .set('fetchingSchemaError', false);
108
144
  }
@@ -110,6 +146,7 @@ function capReducer(state = fromJS(initialState.cap), action) {
110
146
  return state.set('metaEntities', {
111
147
  layouts: [],
112
148
  tags: [],
149
+ tagLookupMap: {},
113
150
  });
114
151
  // eslint-disable-next-line no-case-declarations
115
152
  case types.HIDE_TAGS:
@@ -117,8 +154,23 @@ function capReducer(state = fromJS(initialState.cap), action) {
117
154
  metaEntities.tags.standard = _.filter(state.get('metaEntities').tags.standard, (tag) => action.tagList.indexOf(tag.definition.value) === -1);
118
155
  metaEntities.tags.custom = _.filter(state.get('metaEntities').tags.custom, (tag) => action.tagList.indexOf(tag.name) === -1);
119
156
  return state.setIn(['metaEntities'], metaEntities);
120
- case types.SET_INJECTED_TAGS:
121
- return state.set("injectedTags", action.injectedTags);
157
+ case types.SET_INJECTED_TAGS:
158
+
159
+ // Deep clone the tagLookupMap to avoid direct mutations
160
+ let updatedMetaEntitiesTagLookUp = _.cloneDeep(state.getIn(['metaEntities', 'tagLookupMap']));
161
+ const formattedInjectedTags = getForwardedMapValues(action?.injectedTags);
162
+ // Merge the injectedTags with the existing tagLookupMap
163
+ updatedMetaEntitiesTagLookUp = {
164
+ ...formattedInjectedTags || {},
165
+ ...updatedMetaEntitiesTagLookUp || {},
166
+ };
167
+ return state.set("injectedTags", action.injectedTags).setIn(
168
+ ["metaEntities"],
169
+ fromJS({
170
+ ...state.get("metaEntities"),
171
+ tagLookupMap: updatedMetaEntitiesTagLookUp
172
+ })
173
+ );
122
174
  case types.GET_TOPBAR_MENU_DATA_REQUEST:
123
175
  return state.set('topbarMenuData', fromJS({ status: 'request' }));
124
176
  case types.GET_TOPBAR_MENU_DATA_SUCCESS:
@@ -21,6 +21,7 @@ import {
21
21
  expectedStateGetSchemaForEntitySuccessTAG,
22
22
  expectedStateGetSchemaForEntitySuccess,
23
23
  } from '../mockData';
24
+ import { TAG } from '../../Whatsapp/constants';
24
25
  import { loadItem } from '../../../services/localStorageApi';
25
26
 
26
27
 
@@ -117,3 +118,104 @@ describe('should handle GET_SUPPORT_VIDEOS_CONFIG', () => {
117
118
  expect(reducer(mockedInitialState, action).toJS())?.fetchingSchema?.toEqual(true);
118
119
  });
119
120
  });
121
+
122
+ describe('GET_SCHEMA_FOR_ENTITY_SUCCESS handler', () => {
123
+ it.concurrent('should handle existing tagLookupMap correctly when metaEntities and tagLookupMap exist', () => {
124
+ const initialStateTest = fromJS({
125
+ token: loadItem('token') || '',
126
+ orgID: loadItem('orgID') || '',
127
+ messages: [],
128
+ metaEntities: {
129
+ tags: [{ id: 1, name: 'tag1' }],
130
+ layouts: ['layout1', 'layout2'],
131
+ tagLookupMap: {
132
+ existingTag: { definition: { value: 'existing' } },
133
+ anotherTag: { definition: { value: 'another' } },
134
+ },
135
+ standard: [],
136
+ },
137
+ liquidTags: [],
138
+ fetchingLiquidTags: false,
139
+ fetchingSchema: false,
140
+ fetchingSchemaError: '',
141
+ });
142
+
143
+ let action = {
144
+ type: GET_SCHEMA_FOR_ENTITY_SUCCESS,
145
+ entityType: TAG,
146
+ data: {
147
+ metaEntities: {
148
+ standard: [],
149
+ custom: [],
150
+ extended: [],
151
+ },
152
+ },
153
+ };
154
+
155
+ let newState = reducer(initialStateTest, action);
156
+ let metaEntities = newState.get('metaEntities');
157
+
158
+ expect(metaEntities).toEqual(expect.objectContaining({
159
+ tagLookupMap: expect.objectContaining({
160
+ existingTag: expect.objectContaining({
161
+ definition: expect.objectContaining({
162
+ value: 'existing',
163
+ }),
164
+ }),
165
+ }),
166
+ }));
167
+
168
+ action = {
169
+ type: GET_SCHEMA_FOR_ENTITY_SUCCESS,
170
+ entityType: 'LAYOUT',
171
+ data: {
172
+ metaEntities: {
173
+ layouts: ['layout1', 'layout2'],
174
+ standard: [],
175
+ },
176
+ },
177
+ };
178
+ newState = reducer(initialStateTest, action);
179
+ metaEntities = newState.get('metaEntities');
180
+ expect(metaEntities).toEqual(expect.objectContaining({
181
+ layouts: expect.objectContaining({
182
+ layouts: expect.arrayContaining(['layout1', 'layout2']),
183
+ }),
184
+ }));
185
+ });
186
+
187
+ it.concurrent('should handle non-existent tagLookupMap by returning empty object', () => {
188
+ const initialStateTest = fromJS({
189
+ token: loadItem('token') || '',
190
+ orgID: loadItem('orgID') || '',
191
+ messages: [],
192
+ metaEntities: {
193
+ tagLookupMap: {},
194
+ },
195
+ liquidTags: [],
196
+ fetchingLiquidTags: false,
197
+ fetchingSchema: false,
198
+ fetchingSchemaError: '',
199
+ });
200
+
201
+ const action = {
202
+ type: GET_SCHEMA_FOR_ENTITY_SUCCESS,
203
+ entityType: TAG,
204
+ data: {
205
+ metaEntities: {
206
+ standard: [],
207
+ custom: [],
208
+ extended: [],
209
+ },
210
+ },
211
+ };
212
+
213
+ const newState = reducer(initialStateTest, action);
214
+ const metaEntities = newState.get('metaEntities');
215
+
216
+ // Updated assertions to handle plain object
217
+ expect(metaEntities).toBeDefined();
218
+ expect(metaEntities.tagLookupMap).toBeDefined();
219
+ expect(metaEntities.tagLookupMap).toEqual({});
220
+ });
221
+ });
@@ -131,6 +131,7 @@ export class Creatives extends React.Component {
131
131
  errorsAcknowledged: false, // Flag to track if user has acknowledged errors by clicking redirection icon
132
132
  },
133
133
  };
134
+ this.liquidFlow = Boolean(commonUtil.hasLiquidSupportFeature());
134
135
  this.creativesTemplateSteps = {
135
136
  1: 'modeSelection',
136
137
  2: 'templateSelection', // only for email in current flows wil be used for mpush, line and wechat as well.
@@ -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 { isEmailUnsubscribeTagOptional } from '../../../utils/common';
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?.missingTags?.length && !isEmailUnsubscribeTagOptional()) {
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
@@ -487,8 +493,10 @@ const EmailHTMLEditor = (props) => {
487
493
  const validationResult = validateTags({
488
494
  content,
489
495
  tagsParam: tags,
496
+ injectedTagsParams: injectedTags,
490
497
  location,
491
498
  tagModule: getDefaultTags,
499
+ eventContextTags,
492
500
  isFullMode,
493
501
  });
494
502
 
@@ -642,7 +650,7 @@ const EmailHTMLEditor = (props) => {
642
650
  // IMPORTANT: Clear API validation errors FIRST before checking for validation errors
643
651
  // This ensures that old API errors don't block the save when user fixes content and clicks Update again
644
652
  // We'll re-validate with fresh API call anyway
645
- if (getLiquidTags) {
653
+ if (isLiquidEnabled && getLiquidTags) {
646
654
  setApiValidationErrors({
647
655
  liquidErrors: [],
648
656
  standardErrors: [],
@@ -704,7 +712,7 @@ const EmailHTMLEditor = (props) => {
704
712
  // When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false: validate and require unsubscribe tag.
705
713
  // Run for both library and full mode so liquid-enabled orgs also get the error (notification + ValidationErrorDisplay).
706
714
  const isModuleTypeOutbound = (moduleType || '').toUpperCase() === OUTBOUND;
707
- if (!isEmailUnsubscribeTagOptional() && isModuleTypeOutbound) {
715
+ if (!isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound) {
708
716
  const unsubscribeRegex = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g; // eslint-disable-line no-useless-escape
709
717
  const hasUnsubscribeTag = unsubscribeRegex.test(htmlContent);
710
718
 
@@ -736,17 +744,52 @@ const EmailHTMLEditor = (props) => {
736
744
  const validationResult = validateTags({
737
745
  content: htmlContent,
738
746
  tagsParam: tags,
747
+ injectedTagsParams: injectedTags,
739
748
  location,
740
749
  tagModule: getDefaultTags,
750
+ eventContextTags,
741
751
  isFullMode,
742
752
  });
743
753
 
744
- if (!validationResult?.valid) {
754
+ const hasUnsupportedTags = validationResult?.unsupportedTags?.length > 0;
755
+ if (!validationResult?.valid || (hasUnsupportedTags && !isFullMode)) {
745
756
  setTagValidationError(validationResult);
746
- // For liquid orgs, show warning and continue (extractTags API will validate)
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
747
790
  }
748
791
  // Clear tag errors if valid
749
- if (tagValidationError && validationResult?.valid) {
792
+ if (tagValidationError && validationResult?.valid && !hasUnsupportedTags) {
750
793
  setTagValidationError(null);
751
794
  }
752
795
  }
@@ -913,8 +956,8 @@ const EmailHTMLEditor = (props) => {
913
956
  }
914
957
  };
915
958
 
916
- // Validate first using extractTags API
917
- if (getLiquidTags) {
959
+ // If liquid enabled, validate first using extractTags API
960
+ if (isLiquidEnabled && getLiquidTags) {
918
961
  // Note: API validation errors are already cleared at the start of handleSave
919
962
  // This ensures fresh validation on every save attempt
920
963
 
@@ -955,6 +998,10 @@ const EmailHTMLEditor = (props) => {
955
998
  messages: formBuilderMessages,
956
999
  onError,
957
1000
  onSuccess,
1001
+ tagLookupMap: metaEntities?.tagLookupMap,
1002
+ eventContextTags,
1003
+ isLiquidFlow: true,
1004
+ forwardedTags: forwardedTags || {},
958
1005
  });
959
1006
  } else {
960
1007
  performSave();
@@ -966,6 +1013,7 @@ const EmailHTMLEditor = (props) => {
966
1013
  injectedTags,
967
1014
  location,
968
1015
  getDefaultTags,
1016
+ eventContextTags,
969
1017
  formatMessage,
970
1018
  subjectError,
971
1019
  isFullMode,
@@ -977,8 +1025,11 @@ const EmailHTMLEditor = (props) => {
977
1025
  emailActions,
978
1026
  getFormdata,
979
1027
  isGetFormData,
1028
+ isLiquidEnabled,
980
1029
  getLiquidTags,
981
1030
  showLiquidErrorInFooter,
1031
+ metaEntities,
1032
+ forwardedTags,
982
1033
  globalActions,
983
1034
  intl,
984
1035
  extractedTemplateName,
@@ -1124,6 +1175,7 @@ const EmailHTMLEditor = (props) => {
1124
1175
  userLocale={intl.locale || 'en'}
1125
1176
  moduleFilterEnabled={location?.query?.type !== EMBEDDED}
1126
1177
  onTagContextChange={handleOnTagsContextChange}
1178
+ isLiquidEnabled={isLiquidEnabled}
1127
1179
  isFullMode={isFullMode}
1128
1180
  onErrorAcknowledged={handleErrorAcknowledged}
1129
1181
  onValidationChange={handleValidationChange}