@capillarytech/creatives-library 8.0.306 → 8.0.307

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 (54) hide show
  1. package/constants/unified.js +5 -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 +36 -93
  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/constants.js +6 -0
  22. package/v2Containers/CreativesContainer/index.js +47 -7
  23. package/v2Containers/Email/index.js +1 -5
  24. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +23 -70
  25. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -120
  26. package/v2Containers/FTP/index.js +2 -51
  27. package/v2Containers/FTP/messages.js +0 -4
  28. package/v2Containers/InApp/index.js +35 -107
  29. package/v2Containers/InApp/tests/index.test.js +17 -6
  30. package/v2Containers/InappAdvance/index.js +4 -112
  31. package/v2Containers/InappAdvance/tests/index.test.js +2 -0
  32. package/v2Containers/Line/Container/Text/index.js +0 -1
  33. package/v2Containers/MobilePush/Create/index.js +59 -19
  34. package/v2Containers/MobilePush/Edit/index.js +48 -20
  35. package/v2Containers/MobilePushNew/index.js +12 -32
  36. package/v2Containers/MobilepushWrapper/index.js +3 -1
  37. package/v2Containers/Rcs/index.js +12 -37
  38. package/v2Containers/Sms/Create/index.js +39 -3
  39. package/v2Containers/Sms/Create/messages.js +4 -0
  40. package/v2Containers/Sms/Edit/index.js +35 -3
  41. package/v2Containers/Sms/commonMethods.js +3 -6
  42. package/v2Containers/Sms/tests/commonMethods.test.js +122 -0
  43. package/v2Containers/SmsTrai/Edit/index.js +11 -47
  44. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  45. package/v2Containers/SmsWrapper/index.js +2 -0
  46. package/v2Containers/TemplatesV2/index.js +28 -13
  47. package/v2Containers/Viber/index.js +0 -1
  48. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
  49. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
  50. package/v2Containers/WebPush/Create/index.js +2 -2
  51. package/v2Containers/WebPush/Create/utils/validation.js +17 -8
  52. package/v2Containers/WebPush/Create/utils/validation.test.js +44 -24
  53. package/v2Containers/Whatsapp/index.js +9 -17
  54. 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)
@@ -574,7 +573,6 @@ const HTMLEditor = forwardRef(({
574
573
  content,
575
574
  layout,
576
575
  validation,
577
- isLiquidEnabled,
578
576
  editorRef: getActiveEditorRef(),
579
577
  handleLabelInsert,
580
578
  handleSave,
@@ -594,7 +592,6 @@ const HTMLEditor = forwardRef(({
594
592
  content,
595
593
  layout,
596
594
  validation,
597
- isLiquidEnabled,
598
595
  getActiveEditorRef,
599
596
  handleLabelInsert,
600
597
  handleSave,
@@ -783,7 +780,6 @@ HTMLEditor.propTypes = {
783
780
  onTagSelect: PropTypes.func,
784
781
  onContextChange: PropTypes.func, // Deprecated: use globalActions instead
785
782
  globalActions: PropTypes.object,
786
- isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
787
783
  isFullMode: PropTypes.bool, // Full mode vs library mode
788
784
  onErrorAcknowledged: PropTypes.func, // Callback when user clicks redirection icon to acknowledge errors
789
785
  onValidationChange: PropTypes.func, // Callback when validation state changes
@@ -817,7 +813,6 @@ HTMLEditor.defaultProps = {
817
813
  onTagSelect: null,
818
814
  onContextChange: null,
819
815
  globalActions: null, // Redux actions for API calls
820
- isLiquidEnabled: false,
821
816
  isFullMode: true, // Default to full mode
822
817
  onErrorAcknowledged: null, // Callback when user clicks redirection icon to acknowledge errors
823
818
  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(),
@@ -3202,7 +3202,6 @@ describe('HTMLEditor', () => {
3202
3202
  onTagSelect={onTagSelect}
3203
3203
  onContextChange={onContextChange}
3204
3204
  globalActions={globalActions}
3205
- isLiquidEnabled={true}
3206
3205
  isFullMode={false}
3207
3206
  onErrorAcknowledged={onErrorAcknowledged}
3208
3207
  onValidationChange={onValidationChange}
@@ -3262,20 +3261,6 @@ describe('HTMLEditor', () => {
3262
3261
  expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
3263
3262
  });
3264
3263
 
3265
- it('should handle isLiquidEnabled prop', () => {
3266
- render(
3267
- <TestWrapper>
3268
- <HTMLEditor isLiquidEnabled={true} />
3269
- </TestWrapper>
3270
- );
3271
-
3272
- act(() => {
3273
- jest.runAllTimers();
3274
- });
3275
-
3276
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3277
- });
3278
-
3279
3264
  it('should handle isFullMode prop', () => {
3280
3265
  render(
3281
3266
  <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 = () => {
@@ -52,6 +52,12 @@ export const GENERIC = "GENERIC";
52
52
  export const LIQUID_ERROR_MSG = "LIQUID_ERROR_MSG";
53
53
  export const STANDARD_ERROR_MSG = "STANDARD_ERROR_MSG";
54
54
  export const COMMON_CHANNELS = ['sms', 'email', 'wechat', 'mobilepush', 'webpush', 'line', 'viber', 'facebook', 'call_task', 'ftp', 'assets'];
55
+
56
+ /** Normalized channel forms (e.g. from camelCase) that do not match pane keys; maps to canonical pane key for hide/disable. */
57
+ export const NORMALIZED_CHANNEL_ALIASES = {
58
+ we_chat: WECHAT.toLowerCase(),
59
+ m_push: MOBILE_PUSH.toLowerCase(),
60
+ };
55
61
  export const MIXED = "MIXED";
56
62
  export const VISITOR = "VISITOR";
57
63
  export const ALLOWED_CHANNELS_FOR_ANONYMOUS = ['mobilepush', 'webpush'];
@@ -69,6 +69,23 @@ import {
69
69
  import { MANUAL_CAROUSEL } from '../MobilePushNew/constants';
70
70
  import { BIG_HTML } from '../InApp/constants';
71
71
 
72
+ /**
73
+ * Returns true if value is "deep empty": no errors present.
74
+ * - null/undefined: empty
75
+ * - string: empty if length === 0
76
+ * - array: empty if length === 0
77
+ * - plain object (e.g. { android: [], ios: [], generic: [] }): empty only if every value is deep-empty
78
+ */
79
+ function isDeepEmpty(value) {
80
+ if (value == null) return true;
81
+ if (typeof value === 'string') return value.length === 0;
82
+ if (Array.isArray(value)) return value.length === 0;
83
+ if (typeof value === 'object') {
84
+ return Object.values(value).every(isDeepEmpty);
85
+ }
86
+ return false;
87
+ }
88
+
72
89
  const classPrefix = 'add-creatives-section';
73
90
  const CREATIVES_CONTAINER = 'creativesContainer';
74
91
 
@@ -132,7 +149,6 @@ export class Creatives extends React.Component {
132
149
  },
133
150
  hasPersonalizationTokenError: false, // Track personalization token errors in form
134
151
  };
135
- this.liquidFlow = Boolean(commonUtil.hasLiquidSupportFeature());
136
152
  this.creativesTemplateSteps = {
137
153
  1: 'modeSelection',
138
154
  2: 'templateSelection', // only for email in current flows wil be used for mpush, line and wechat as well.
@@ -256,12 +272,23 @@ export class Creatives extends React.Component {
256
272
  };
257
273
 
258
274
  onShowTemplates = () => {
259
- this.setState({ slidBoxContent: 'templates', showSlideBox: true, isGetFormData: false });
275
+ this.setState({
276
+ slidBoxContent: 'templates',
277
+ showSlideBox: true,
278
+ isGetFormData: false,
279
+ liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
280
+ isLiquidValidationError: false,
281
+ });
260
282
  this.resetStep();
261
283
  };
262
284
 
263
285
  onChannelChange = (channel) => {
264
- this.setState({ currentChannel: channel, templateData: null });
286
+ this.setState({
287
+ currentChannel: channel,
288
+ templateData: null,
289
+ liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
290
+ isLiquidValidationError: false,
291
+ });
265
292
  }
266
293
 
267
294
  onCreateNextStep = () => {
@@ -1469,11 +1496,12 @@ export class Creatives extends React.Component {
1469
1496
  }
1470
1497
 
1471
1498
  getFormData = (template) => {
1499
+ // Always reset isGetFormData so the child does not re-send form data on every re-render
1500
+ // (e.g. when user fixes validation error by typing, we must not auto-close the slidebox)
1501
+ this.setState({ isGetFormData: false });
1472
1502
  if (template.validity) {
1473
1503
  this.setState(
1474
- {
1475
- isGetFormData: false,
1476
- },
1504
+ {},
1477
1505
  () => {
1478
1506
  const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1479
1507
  const channel = templateData.type;
@@ -1557,6 +1585,8 @@ export class Creatives extends React.Component {
1557
1585
  ...prevState,
1558
1586
  templateData: undefined,
1559
1587
  showSlideBox: false,
1588
+ liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
1589
+ isLiquidValidationError: false,
1560
1590
  }), () => this.props.handleCloseCreatives(reloadTemplates));
1561
1591
  };
1562
1592
 
@@ -1764,8 +1794,18 @@ export class Creatives extends React.Component {
1764
1794
  }
1765
1795
 
1766
1796
  showLiquidErrorInFooter = (errorMessagesFromFormBuilder, currentFormBuilderTab) => {
1797
+ const liquidMsgs = get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, []);
1798
+ const standardMsgs = get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, []);
1799
+ const hasLiquid = !isDeepEmpty(liquidMsgs);
1800
+ const hasStandard = !isDeepEmpty(standardMsgs);
1801
+ const isLiquidValidationError = hasLiquid || hasStandard;
1802
+ // Don't overwrite existing liquid error with empty only for Mobile Push OLD (FormBuilder/clear calls empty there); SMS/others clear on input change
1803
+ const isMobilePush = this.state.currentChannel?.toUpperCase() === constants.MOBILE_PUSH;
1804
+ if (!hasLiquid && !hasStandard && this.state.isLiquidValidationError && isMobilePush) {
1805
+ return;
1806
+ }
1767
1807
  this.setState({
1768
- isLiquidValidationError: !isEmpty(get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, [])) || !isEmpty(get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, [])),
1808
+ isLiquidValidationError,
1769
1809
  liquidErrorMessage: errorMessagesFromFormBuilder,
1770
1810
  activeFormBuilderTab: currentFormBuilderTab === 1 ? constants.ANDROID : (currentFormBuilderTab === 2 ? constants.IOS : null), // Update activeFormBuilderTab, default to 1 if undefined
1771
1811
  });
@@ -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
  }