@capillarytech/creatives-library 8.0.291 → 8.0.292-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.
Files changed (52) hide show
  1. package/constants/unified.js +3 -1
  2. package/initialState.js +0 -2
  3. package/package.json +1 -1
  4. package/utils/common.js +5 -8
  5. package/utils/commonUtils.js +4 -85
  6. package/utils/tagValidations.js +83 -223
  7. package/utils/tests/commonUtil.test.js +147 -124
  8. package/utils/tests/tagValidations.test.js +441 -358
  9. package/v2Components/ErrorInfoNote/index.js +2 -5
  10. package/v2Components/FormBuilder/index.js +137 -203
  11. package/v2Components/FormBuilder/messages.js +0 -8
  12. package/v2Components/HtmlEditor/HTMLEditor.js +0 -5
  13. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
  14. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +0 -15
  15. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +1 -2
  16. package/v2Containers/Cap/mockData.js +0 -14
  17. package/v2Containers/Cap/reducer.js +3 -55
  18. package/v2Containers/Cap/tests/reducer.test.js +0 -102
  19. package/v2Containers/CreativesContainer/SlideBoxContent.js +5 -1
  20. package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -5
  21. package/v2Containers/CreativesContainer/index.js +30 -7
  22. package/v2Containers/Email/index.js +1 -5
  23. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +23 -70
  24. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +29 -137
  25. package/v2Containers/FTP/index.js +2 -51
  26. package/v2Containers/FTP/messages.js +0 -4
  27. package/v2Containers/InApp/index.js +4 -104
  28. package/v2Containers/InApp/tests/index.test.js +17 -6
  29. package/v2Containers/InappAdvance/index.js +4 -108
  30. package/v2Containers/InappAdvance/tests/index.test.js +2 -0
  31. package/v2Containers/Line/Container/Text/index.js +0 -1
  32. package/v2Containers/MobilePush/Create/index.js +42 -19
  33. package/v2Containers/MobilePush/Edit/index.js +42 -19
  34. package/v2Containers/MobilePushNew/index.js +12 -32
  35. package/v2Containers/MobilepushWrapper/index.js +3 -1
  36. package/v2Containers/Rcs/index.js +12 -37
  37. package/v2Containers/Sms/Create/index.js +39 -3
  38. package/v2Containers/Sms/Create/messages.js +4 -0
  39. package/v2Containers/Sms/Edit/index.js +35 -3
  40. package/v2Containers/Sms/commonMethods.js +3 -6
  41. package/v2Containers/Sms/tests/commonMethods.test.js +122 -0
  42. package/v2Containers/SmsTrai/Edit/index.js +11 -47
  43. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  44. package/v2Containers/SmsWrapper/index.js +2 -0
  45. package/v2Containers/Viber/index.js +0 -1
  46. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
  47. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
  48. package/v2Containers/WebPush/Create/index.js +2 -2
  49. package/v2Containers/WebPush/Create/utils/validation.js +17 -2
  50. package/v2Containers/WebPush/Create/utils/validation.test.js +59 -24
  51. package/v2Containers/Whatsapp/index.js +9 -17
  52. package/v2Containers/Zalo/index.js +3 -11
@@ -102,7 +102,6 @@ const HTMLEditor = forwardRef(({
102
102
  onTagSelect = null,
103
103
  onContextChange = null,
104
104
  globalActions = null,
105
- isLiquidEnabled = false, // Controls Liquid tab visibility in ValidationTabs
106
105
  isFullMode = true, // Full mode vs library mode - controls layout and visibility
107
106
  onErrorAcknowledged = null, // Callback when user clicks redirection icon to acknowledge errors
108
107
  onValidationChange = null, // Callback when validation state changes (for parent to track errors)
@@ -573,7 +572,6 @@ const HTMLEditor = forwardRef(({
573
572
  content,
574
573
  layout,
575
574
  validation,
576
- isLiquidEnabled,
577
575
  editorRef: getActiveEditorRef(),
578
576
  handleLabelInsert,
579
577
  handleSave,
@@ -593,7 +591,6 @@ const HTMLEditor = forwardRef(({
593
591
  content,
594
592
  layout,
595
593
  validation,
596
- isLiquidEnabled,
597
594
  getActiveEditorRef,
598
595
  handleLabelInsert,
599
596
  handleSave,
@@ -782,7 +779,6 @@ HTMLEditor.propTypes = {
782
779
  onTagSelect: PropTypes.func,
783
780
  onContextChange: PropTypes.func, // Deprecated: use globalActions instead
784
781
  globalActions: PropTypes.object,
785
- isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
786
782
  isFullMode: PropTypes.bool, // Full mode vs library mode
787
783
  onErrorAcknowledged: PropTypes.func, // Callback when user clicks redirection icon to acknowledge errors
788
784
  onValidationChange: PropTypes.func, // Callback when validation state changes
@@ -816,7 +812,6 @@ HTMLEditor.defaultProps = {
816
812
  onTagSelect: null,
817
813
  onContextChange: null,
818
814
  globalActions: null, // Redux actions for API calls
819
- isLiquidEnabled: false,
820
815
  isFullMode: true, // Default to full mode
821
816
  onErrorAcknowledged: null, // Callback when user clicks redirection icon to acknowledge errors
822
817
  onValidationChange: null, // Callback when validation state changes
@@ -229,7 +229,6 @@ const defaultProps = {
229
229
  channel: 'EMAIL',
230
230
  userLocale: 'en',
231
231
  moduleFilterEnabled: true,
232
- isLiquidEnabled: false,
233
232
  isFullMode: true,
234
233
  onErrorAcknowledged: jest.fn(),
235
234
  onValidationChange: jest.fn(),
@@ -3201,7 +3201,6 @@ describe('HTMLEditor', () => {
3201
3201
  onTagSelect={onTagSelect}
3202
3202
  onContextChange={onContextChange}
3203
3203
  globalActions={globalActions}
3204
- isLiquidEnabled={true}
3205
3204
  isFullMode={false}
3206
3205
  onErrorAcknowledged={onErrorAcknowledged}
3207
3206
  onValidationChange={onValidationChange}
@@ -3261,20 +3260,6 @@ describe('HTMLEditor', () => {
3261
3260
  expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
3262
3261
  });
3263
3262
 
3264
- it('should handle isLiquidEnabled prop', () => {
3265
- render(
3266
- <TestWrapper>
3267
- <HTMLEditor isLiquidEnabled={true} />
3268
- </TestWrapper>
3269
- );
3270
-
3271
- act(() => {
3272
- jest.runAllTimers();
3273
- });
3274
-
3275
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3276
- });
3277
-
3278
3263
  it('should handle isFullMode prop', () => {
3279
3264
  render(
3280
3265
  <TestWrapper>
@@ -69,7 +69,7 @@ const CodeEditorPaneComponent = ({
69
69
  }) => {
70
70
  const context = useEditorContext();
71
71
  const {
72
- content, validation, variant, isLiquidEnabled,
72
+ content, validation, variant,
73
73
  } = context || {};
74
74
  const { content: contentValue, updateContent } = content || {};
75
75
  const editorRef = useRef(null);
@@ -298,7 +298,6 @@ const CodeEditorPaneComponent = ({
298
298
  <ValidationErrorDisplay
299
299
  validation={validation}
300
300
  onErrorClick={onErrorClick}
301
- isLiquidEnabled={isLiquidEnabled}
302
301
  className="code-editor-pane__validation"
303
302
  />
304
303
  </div>
@@ -2,11 +2,9 @@ export const expectedStateGetLiquidTagsRequest = {
2
2
  fetchingLiquidTags: true,
3
3
  fetchingSchema: true,
4
4
  fetchingSchemaError: "",
5
- liquidTags: [],
6
5
  messages: [],
7
6
  metaEntities: {
8
7
  layouts: [],
9
- tagLookupMap: {},
10
8
  tags: [],
11
9
  },
12
10
  orgID: "",
@@ -17,11 +15,9 @@ export const expectedStateGetLiquidTagsFailure = {
17
15
  fetchingLiquidTags: false,
18
16
  fetchingSchema: true,
19
17
  fetchingSchemaError: "",
20
- liquidTags: [],
21
18
  messages: [],
22
19
  metaEntities: {
23
20
  layouts: [],
24
- tagLookupMap: {},
25
21
  tags: [],
26
22
  },
27
23
  orgID: "",
@@ -32,11 +28,9 @@ export const expectedStateGetLiquidTagsSuccess = {
32
28
  fetchingLiquidTags: false,
33
29
  fetchingSchema: true,
34
30
  fetchingSchemaError: "",
35
- liquidTags: [],
36
31
  messages: [],
37
32
  metaEntities: {
38
33
  layouts: [],
39
- tagLookupMap: {},
40
34
  tags: [],
41
35
  },
42
36
  orgID: "",
@@ -47,11 +41,9 @@ export const expectedStateGetSchemaForEntitySuccessTAG = {
47
41
  fetchingLiquidTags: false,
48
42
  fetchingSchema: false,
49
43
  fetchingSchemaError: false,
50
- liquidTags: [],
51
44
  messages: [],
52
45
  metaEntities: {
53
46
  layouts: undefined,
54
- tagLookupMap: { undefined: { definition: {} } },
55
47
  tags: { standard: { random: "32" } },
56
48
  },
57
49
  orgID: "",
@@ -62,11 +54,9 @@ export const expectedStateGetSchemaForEntitySuccess = {
62
54
  fetchingLiquidTags: false,
63
55
  fetchingSchema: false,
64
56
  fetchingSchemaError: false,
65
- liquidTags: [],
66
57
  messages: [],
67
58
  metaEntities: {
68
59
  layouts: undefined,
69
- tagLookupMap: undefined,
70
60
  tags: undefined,
71
61
  },
72
62
  orgID: "",
@@ -78,13 +68,9 @@ export const expectedForwardedTags = {
78
68
  fetchingSchema: false,
79
69
  fetchingSchemaError: '',
80
70
  injectedTags: undefined,
81
- liquidTags: [],
82
71
  messages: [],
83
72
  metaEntities: {
84
73
  layouts: [],
85
- tagLookupMap: {
86
-
87
- },
88
74
  tags: [],
89
75
  },
90
76
  orgID: "",
@@ -1,13 +1,11 @@
1
1
  /**
2
2
  * Created by vivek on 22/5/17.
3
3
  */
4
- import { fromJS, Map as ImmutableMap } from 'immutable';
4
+ import { fromJS } 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';
11
9
 
12
10
  function capReducer(state = fromJS(initialState.cap), action) {
13
11
  switch (action.type) {
@@ -98,39 +96,6 @@ function capReducer(state = fromJS(initialState.cap), action) {
98
96
  return state
99
97
  .set('fetchingLiquidTags', false);
100
98
  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
- };
134
99
  const stateMeta = state.get("metaEntities");
135
100
  return state
136
101
  .set('fetchingSchema', false)
@@ -138,7 +103,6 @@ function capReducer(state = fromJS(initialState.cap), action) {
138
103
  .set('metaEntities', {
139
104
  layouts: action.data && action.entityType === 'LAYOUT' ? action.data.metaEntities : stateMeta?.layouts,
140
105
  tags: action.data && action.entityType === 'TAG' ? action.data.metaEntities : stateMeta?.tags,
141
- tagLookupMap: action?.data && action?.entityType === TAG ? combinedTagMap : stateMeta?.tagLookupMap,
142
106
  })
143
107
  .set('fetchingSchemaError', false);
144
108
  }
@@ -146,7 +110,6 @@ function capReducer(state = fromJS(initialState.cap), action) {
146
110
  return state.set('metaEntities', {
147
111
  layouts: [],
148
112
  tags: [],
149
- tagLookupMap: {},
150
113
  });
151
114
  // eslint-disable-next-line no-case-declarations
152
115
  case types.HIDE_TAGS:
@@ -154,23 +117,8 @@ function capReducer(state = fromJS(initialState.cap), action) {
154
117
  metaEntities.tags.standard = _.filter(state.get('metaEntities').tags.standard, (tag) => action.tagList.indexOf(tag.definition.value) === -1);
155
118
  metaEntities.tags.custom = _.filter(state.get('metaEntities').tags.custom, (tag) => action.tagList.indexOf(tag.name) === -1);
156
119
  return state.setIn(['metaEntities'], metaEntities);
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
- );
120
+ case types.SET_INJECTED_TAGS:
121
+ return state.set("injectedTags", action.injectedTags);
174
122
  case types.GET_TOPBAR_MENU_DATA_REQUEST:
175
123
  return state.set('topbarMenuData', fromJS({ status: 'request' }));
176
124
  case types.GET_TOPBAR_MENU_DATA_SUCCESS:
@@ -21,7 +21,6 @@ import {
21
21
  expectedStateGetSchemaForEntitySuccessTAG,
22
22
  expectedStateGetSchemaForEntitySuccess,
23
23
  } from '../mockData';
24
- import { TAG } from '../../Whatsapp/constants';
25
24
  import { loadItem } from '../../../services/localStorageApi';
26
25
 
27
26
 
@@ -118,104 +117,3 @@ describe('should handle GET_SUPPORT_VIDEOS_CONFIG', () => {
118
117
  expect(reducer(mockedInitialState, action).toJS())?.fetchingSchema?.toEqual(true);
119
118
  });
120
119
  });
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
- });
@@ -566,6 +566,7 @@ export function SlideBoxContent(props) {
566
566
  handleTestAndPreview={handleTestAndPreview}
567
567
  handleCloseTestAndPreview={handleCloseTestAndPreview}
568
568
  isTestAndPreviewMode={isTestAndPreviewMode}
569
+ onValidationFail={onValidationFail}
569
570
  />
570
571
  )}
571
572
  {isEditFTP && (
@@ -632,6 +633,7 @@ export function SlideBoxContent(props) {
632
633
  getLiquidTags={getLiquidTags}
633
634
  getDefaultTags={type}
634
635
  isFullMode={isFullMode}
636
+ onValidationFail={onValidationFail}
635
637
  forwardedTags={forwardedTags}
636
638
  selectedOfferDetails={selectedOfferDetails}
637
639
  onPreviewContentClicked={onPreviewContentClicked}
@@ -780,6 +782,7 @@ export function SlideBoxContent(props) {
780
782
  <MobliPushEdit
781
783
  getFormLibraryData={getFormData}
782
784
  setIsLoadingContent={setIsLoadingContent}
785
+ getLiquidTags={getLiquidTags}
783
786
  location={{
784
787
  pathname: `/mobilepush/edit/`,
785
788
  query,
@@ -852,6 +855,7 @@ export function SlideBoxContent(props) {
852
855
  mobilePushCreateMode={mobilePushCreateMode}
853
856
  isGetFormData={isGetFormData}
854
857
  getFormData={getFormData}
858
+ getLiquidTags={getLiquidTags}
855
859
  templateData={templateData}
856
860
  type={type}
857
861
  step={templateStep}
@@ -880,7 +884,7 @@ export function SlideBoxContent(props) {
880
884
  />
881
885
  ) : (
882
886
  <MobilePushNew
883
- key="creatives-mobilepush-wrapper"
887
+ key="creatives-mobilepush-create-new"
884
888
  date={new Date().getMilliseconds()}
885
889
  setIsLoadingContent={setIsLoadingContent}
886
890
  onMobilepushModeChange={onMobilepushModeChange}
@@ -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 } from './constants';
9
+ import { PREVIEW, EMAIL, SMS, EDIT_TEMPLATE, MOBILE_PUSH } 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 === "editTemplate"
18
+ return slidBoxContent === EDIT_TEMPLATE
19
19
  ? <FormattedMessage {...messages.creativesTemplatesUpdate} />
20
20
  : <FormattedMessage {...messages.creativesTemplatesSaveFullMode} />;
21
21
  }
@@ -52,8 +52,13 @@ 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 isEditMode = slidBoxContent === 'editTemplate';
55
+ const isEmailChannel = currentChannel?.toUpperCase() === EMAIL;
56
+ const isSmsChannel = currentChannel?.toUpperCase() === SMS;
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;
57
62
 
58
63
  // Use selectedEmailCreateMode for accurate mode detection in create mode (emailCreateMode is mapped for backwards compatibility)
59
64
  // In edit mode: htmlEditorValidationState is initialized as {} but only updated by HTML Editor
@@ -129,8 +134,11 @@ function SlideBoxFooter(props) {
129
134
  const isBEEEditorModeInCreate = !isHTMLEditorMode && !isEditMode;
130
135
  const isBEEEditorMode = isBEEEditorModeInEdit || isBEEEditorModeInCreate;
131
136
  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;
132
140
 
133
- const shouldShowErrorInfoNote = hasBEEEditorErrors || isSupportCKEditor;
141
+ const shouldShowErrorInfoNote = hasBEEEditorErrors || hasSmsValidationErrors || hasMobilePushValidationErrors || isSupportCKEditor;
134
142
 
135
143
  // Check for personalization tokens in title/message when anonymous user tries to save
136
144
  const hasPersonalizationTokens = () => {
@@ -132,7 +132,6 @@ 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());
136
135
  this.creativesTemplateSteps = {
137
136
  1: 'modeSelection',
138
137
  2: 'templateSelection', // only for email in current flows wil be used for mpush, line and wechat as well.
@@ -256,12 +255,23 @@ export class Creatives extends React.Component {
256
255
  };
257
256
 
258
257
  onShowTemplates = () => {
259
- this.setState({ slidBoxContent: 'templates', showSlideBox: true, isGetFormData: false });
258
+ this.setState({
259
+ slidBoxContent: 'templates',
260
+ showSlideBox: true,
261
+ isGetFormData: false,
262
+ liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
263
+ isLiquidValidationError: false,
264
+ });
260
265
  this.resetStep();
261
266
  };
262
267
 
263
268
  onChannelChange = (channel) => {
264
- this.setState({ currentChannel: channel, templateData: null });
269
+ this.setState({
270
+ currentChannel: channel,
271
+ templateData: null,
272
+ liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
273
+ isLiquidValidationError: false,
274
+ });
265
275
  }
266
276
 
267
277
  onCreateNextStep = () => {
@@ -1469,11 +1479,12 @@ export class Creatives extends React.Component {
1469
1479
  }
1470
1480
 
1471
1481
  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 });
1472
1485
  if (template.validity) {
1473
1486
  this.setState(
1474
- {
1475
- isGetFormData: false,
1476
- },
1487
+ {},
1477
1488
  () => {
1478
1489
  const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1479
1490
  const channel = templateData.type;
@@ -1557,6 +1568,8 @@ export class Creatives extends React.Component {
1557
1568
  ...prevState,
1558
1569
  templateData: undefined,
1559
1570
  showSlideBox: false,
1571
+ liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
1572
+ isLiquidValidationError: false,
1560
1573
  }), () => this.props.handleCloseCreatives(reloadTemplates));
1561
1574
  };
1562
1575
 
@@ -1764,8 +1777,18 @@ export class Creatives extends React.Component {
1764
1777
  }
1765
1778
 
1766
1779
  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
+ }
1767
1790
  this.setState({
1768
- isLiquidValidationError: !isEmpty(get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, [])) || !isEmpty(get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, [])),
1791
+ isLiquidValidationError,
1769
1792
  liquidErrorMessage: errorMessagesFromFormBuilder,
1770
1793
  activeFormBuilderTab: currentFormBuilderTab === 1 ? constants.ANDROID : (currentFormBuilderTab === 2 ? constants.IOS : null), // Update activeFormBuilderTab, default to 1 if undefined
1771
1794
  });
@@ -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, hasLiquidSupportFeature, hasSupportCKEditor } from '../../utils/common';
29
+ import { getDecodedFileName, 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,7 +170,6 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
170
170
  deleteLanguage: this.deleteLanguage,
171
171
  },
172
172
  };
173
- this.liquidFlow = hasLiquidSupportFeature();
174
173
  }
175
174
  componentWillMount() {
176
175
  const formData = this.initFormData(this.props, true); //_.cloneDeep(this.state.formData);
@@ -255,7 +254,6 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
255
254
  layout: 'EMAIL',
256
255
  type: 'LAYOUT',
257
256
  version: 'v2',
258
- liquidFlow:this.liquidFlow,
259
257
  };
260
258
  this.props.globalActions.fetchSchemaForEntity(query);
261
259
  window.addEventListener("message", this.handleFrameTasks);
@@ -347,7 +345,6 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
347
345
  type: 'TAG',
348
346
  context: this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default',
349
347
  embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
350
- liquidFlow:this.liquidFlow
351
348
  };
352
349
  if (this.props.getDefaultTags) {
353
350
  query.context = this.props.getDefaultTags;
@@ -2373,7 +2370,6 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
2373
2370
  type: 'TAG',
2374
2371
  context: (data || '').toLowerCase() === 'all' ? 'default' : (data || '').toLowerCase(),
2375
2372
  embedded: 'full',
2376
- liquidFlow:this.liquidFlow
2377
2373
  };
2378
2374
  this.props.globalActions.fetchSchemaForEntity(query);
2379
2375
  }