@capillarytech/creatives-library 8.0.287-alpha.3 → 8.0.288
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 -0
- package/initialState.js +2 -0
- package/package.json +1 -1
- package/utils/common.js +8 -5
- package/utils/commonUtils.js +111 -2
- package/utils/tagValidations.js +222 -84
- package/utils/tests/commonUtil.test.js +118 -147
- package/utils/tests/tagValidations.test.js +358 -280
- package/v2Components/CapTagList/index.js +7 -2
- package/v2Components/CapTagListWithInput/index.js +4 -0
- package/v2Components/ErrorInfoNote/index.js +5 -2
- package/v2Components/FormBuilder/index.js +187 -74
- package/v2Components/FormBuilder/messages.js +12 -0
- package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +2 -1
- package/v2Containers/Cap/mockData.js +14 -0
- package/v2Containers/Cap/reducer.js +55 -3
- package/v2Containers/Cap/tests/reducer.test.js +102 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +20 -0
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +40 -6
- package/v2Containers/CreativesContainer/constants.js +6 -0
- package/v2Containers/CreativesContainer/index.js +36 -5
- package/v2Containers/CreativesContainer/messages.js +12 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +339 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +18 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +37 -0
- package/v2Containers/Email/index.js +5 -1
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +62 -10
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +115 -12
- package/v2Containers/FTP/index.js +51 -2
- package/v2Containers/FTP/messages.js +4 -0
- package/v2Containers/InApp/index.js +96 -1
- package/v2Containers/InApp/tests/index.test.js +6 -17
- package/v2Containers/InappAdvance/index.js +103 -2
- package/v2Containers/Line/Container/Text/index.js +1 -0
- package/v2Containers/MobilePush/Create/index.js +37 -1
- package/v2Containers/MobilePush/Create/messages.js +4 -0
- package/v2Containers/MobilePush/Edit/index.js +37 -2
- package/v2Containers/MobilePush/Edit/messages.js +4 -0
- package/v2Containers/MobilePushNew/components/PlatformContentFields.js +36 -12
- package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +68 -27
- package/v2Containers/MobilePushNew/index.js +92 -5
- package/v2Containers/MobilePushNew/messages.js +8 -0
- package/v2Containers/MobilepushWrapper/index.js +7 -1
- package/v2Containers/Rcs/index.js +37 -12
- package/v2Containers/Sms/Create/index.js +3 -31
- package/v2Containers/Sms/Create/messages.js +0 -4
- package/v2Containers/Sms/Edit/index.js +3 -29
- package/v2Containers/Sms/commonMethods.js +6 -6
- package/v2Containers/SmsTrai/Edit/index.js +47 -6
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/TagList/index.js +17 -1
- package/v2Containers/TagList/messages.js +4 -0
- package/v2Containers/TemplatesV2/index.js +43 -23
- 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 +25 -6
- package/v2Containers/WebPush/Create/messages.js +8 -1
- package/v2Containers/WebPush/Create/utils/validation.js +20 -22
- package/v2Containers/WebPush/Create/utils/validation.test.js +52 -0
- package/v2Containers/Whatsapp/index.js +17 -9
- package/v2Containers/Zalo/index.js +11 -3
|
@@ -26,6 +26,10 @@ export default defineMessages({
|
|
|
26
26
|
id: 'creatives.components.FormBuilder.missingTagsValidationError',
|
|
27
27
|
defaultMessage: 'Missing tags: {missingTags}. Please add them to this message.',
|
|
28
28
|
},
|
|
29
|
+
unsupportedTagsValidationError: {
|
|
30
|
+
id: 'creatives.components.FormBuilder.unsupportedTagsValidationError',
|
|
31
|
+
defaultMessage: 'Unsupported tags: {unsupportedTags}. Please remove them from this message.',
|
|
32
|
+
},
|
|
29
33
|
genericTagsValidationError: {
|
|
30
34
|
id: 'creatives.components.FormBuilder.genericTagsValidationError',
|
|
31
35
|
defaultMessage: 'Please check the message content for unsupported/missing tags',
|
|
@@ -38,6 +42,10 @@ export default defineMessages({
|
|
|
38
42
|
id: 'creatives.componentsV2.FormBuilder.missingTags',
|
|
39
43
|
defaultMessage: 'Missing tags are:',
|
|
40
44
|
},
|
|
45
|
+
unsupportedTags: {
|
|
46
|
+
id: 'creatives.componentsV2.FormBuilder.unsupportedTags',
|
|
47
|
+
defaultMessage: 'Unsupported tags are:',
|
|
48
|
+
},
|
|
41
49
|
upload: {
|
|
42
50
|
id: 'creatives.componentsV2.FormBuilder.upload',
|
|
43
51
|
defaultMessage: 'Upload',
|
|
@@ -94,4 +102,8 @@ export default defineMessages({
|
|
|
94
102
|
id: 'creatives.componentsV2.FormBuilder.base64ImageError',
|
|
95
103
|
defaultMessage: 'Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.',
|
|
96
104
|
},
|
|
105
|
+
personalizationTagsErrorMessage: {
|
|
106
|
+
id: `creatives.componentsV2.FormBuilder.personalizationTagsErrorMessage`,
|
|
107
|
+
defaultMessage: 'Personalization tags are not supported for anonymous customers, please remove the tags.',
|
|
108
|
+
},
|
|
97
109
|
});
|
|
@@ -102,6 +102,7 @@ const HTMLEditor = forwardRef(({
|
|
|
102
102
|
onTagSelect = null,
|
|
103
103
|
onContextChange = null,
|
|
104
104
|
globalActions = null,
|
|
105
|
+
isLiquidEnabled = false, // Controls Liquid tab visibility in ValidationTabs
|
|
105
106
|
isFullMode = true, // Full mode vs library mode - controls layout and visibility
|
|
106
107
|
onErrorAcknowledged = null, // Callback when user clicks redirection icon to acknowledge errors
|
|
107
108
|
onValidationChange = null, // Callback when validation state changes (for parent to track errors)
|
|
@@ -572,6 +573,7 @@ const HTMLEditor = forwardRef(({
|
|
|
572
573
|
content,
|
|
573
574
|
layout,
|
|
574
575
|
validation,
|
|
576
|
+
isLiquidEnabled,
|
|
575
577
|
editorRef: getActiveEditorRef(),
|
|
576
578
|
handleLabelInsert,
|
|
577
579
|
handleSave,
|
|
@@ -591,6 +593,7 @@ const HTMLEditor = forwardRef(({
|
|
|
591
593
|
content,
|
|
592
594
|
layout,
|
|
593
595
|
validation,
|
|
596
|
+
isLiquidEnabled,
|
|
594
597
|
getActiveEditorRef,
|
|
595
598
|
handleLabelInsert,
|
|
596
599
|
handleSave,
|
|
@@ -779,6 +782,7 @@ HTMLEditor.propTypes = {
|
|
|
779
782
|
onTagSelect: PropTypes.func,
|
|
780
783
|
onContextChange: PropTypes.func, // Deprecated: use globalActions instead
|
|
781
784
|
globalActions: PropTypes.object,
|
|
785
|
+
isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
|
|
782
786
|
isFullMode: PropTypes.bool, // Full mode vs library mode
|
|
783
787
|
onErrorAcknowledged: PropTypes.func, // Callback when user clicks redirection icon to acknowledge errors
|
|
784
788
|
onValidationChange: PropTypes.func, // Callback when validation state changes
|
|
@@ -812,6 +816,7 @@ HTMLEditor.defaultProps = {
|
|
|
812
816
|
onTagSelect: null,
|
|
813
817
|
onContextChange: null,
|
|
814
818
|
globalActions: null, // Redux actions for API calls
|
|
819
|
+
isLiquidEnabled: false,
|
|
815
820
|
isFullMode: true, // Default to full mode
|
|
816
821
|
onErrorAcknowledged: null, // Callback when user clicks redirection icon to acknowledge errors
|
|
817
822
|
onValidationChange: null, // Callback when validation state changes
|
|
@@ -3201,6 +3201,7 @@ describe('HTMLEditor', () => {
|
|
|
3201
3201
|
onTagSelect={onTagSelect}
|
|
3202
3202
|
onContextChange={onContextChange}
|
|
3203
3203
|
globalActions={globalActions}
|
|
3204
|
+
isLiquidEnabled={true}
|
|
3204
3205
|
isFullMode={false}
|
|
3205
3206
|
onErrorAcknowledged={onErrorAcknowledged}
|
|
3206
3207
|
onValidationChange={onValidationChange}
|
|
@@ -3260,6 +3261,20 @@ describe('HTMLEditor', () => {
|
|
|
3260
3261
|
expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
|
|
3261
3262
|
});
|
|
3262
3263
|
|
|
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
|
+
|
|
3263
3278
|
it('should handle isFullMode prop', () => {
|
|
3264
3279
|
render(
|
|
3265
3280
|
<TestWrapper>
|
|
@@ -69,7 +69,7 @@ const CodeEditorPaneComponent = ({
|
|
|
69
69
|
}) => {
|
|
70
70
|
const context = useEditorContext();
|
|
71
71
|
const {
|
|
72
|
-
content, validation, variant,
|
|
72
|
+
content, validation, variant, isLiquidEnabled,
|
|
73
73
|
} = context || {};
|
|
74
74
|
const { content: contentValue, updateContent } = content || {};
|
|
75
75
|
const editorRef = useRef(null);
|
|
@@ -298,6 +298,7 @@ const CodeEditorPaneComponent = ({
|
|
|
298
298
|
<ValidationErrorDisplay
|
|
299
299
|
validation={validation}
|
|
300
300
|
onErrorClick={onErrorClick}
|
|
301
|
+
isLiquidEnabled={isLiquidEnabled}
|
|
301
302
|
className="code-editor-pane__validation"
|
|
302
303
|
/>
|
|
303
304
|
</div>
|
|
@@ -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
|
-
|
|
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
|
+
});
|
|
@@ -173,6 +173,9 @@ export function SlideBoxContent(props) {
|
|
|
173
173
|
showTestAndPreviewSlidebox,
|
|
174
174
|
handleTestAndPreview,
|
|
175
175
|
handleCloseTestAndPreview,
|
|
176
|
+
restrictPersonalization = false,
|
|
177
|
+
isAnonymousType = false,
|
|
178
|
+
onPersonalizationTokensChange,
|
|
176
179
|
isTestAndPreviewMode,
|
|
177
180
|
onHtmlEditorValidationStateChange,
|
|
178
181
|
} = props;
|
|
@@ -637,6 +640,8 @@ export function SlideBoxContent(props) {
|
|
|
637
640
|
smsRegister={smsRegister}
|
|
638
641
|
onShowTemplates={onShowTemplates}
|
|
639
642
|
eventContextTags={eventContextTags}
|
|
643
|
+
restrictPersonalization={restrictPersonalization}
|
|
644
|
+
isAnonymousType={isAnonymousType}
|
|
640
645
|
/>
|
|
641
646
|
)}
|
|
642
647
|
|
|
@@ -677,6 +682,8 @@ export function SlideBoxContent(props) {
|
|
|
677
682
|
isTestAndPreviewMode={isTestAndPreviewMode}
|
|
678
683
|
location={location}
|
|
679
684
|
onHtmlEditorValidationStateChange={onHtmlEditorValidationStateChange}
|
|
685
|
+
restrictPersonalization={restrictPersonalization}
|
|
686
|
+
isAnonymousType={isAnonymousType}
|
|
680
687
|
/>
|
|
681
688
|
)}
|
|
682
689
|
{(isEditEmailWithId || isEmailEditWithContent) && (
|
|
@@ -797,6 +804,9 @@ export function SlideBoxContent(props) {
|
|
|
797
804
|
showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
|
|
798
805
|
handleTestAndPreview={handleTestAndPreview}
|
|
799
806
|
handleCloseTestAndPreview={handleCloseTestAndPreview}
|
|
807
|
+
restrictPersonalization={restrictPersonalization}
|
|
808
|
+
isAnonymousType={isAnonymousType}
|
|
809
|
+
onPersonalizationTokensChange={onPersonalizationTokensChange}
|
|
800
810
|
/>
|
|
801
811
|
) : (
|
|
802
812
|
<MobilePushNew
|
|
@@ -824,6 +834,9 @@ export function SlideBoxContent(props) {
|
|
|
824
834
|
eventContextTags={eventContextTags}
|
|
825
835
|
showLiquidErrorInFooter={showLiquidErrorInFooter}
|
|
826
836
|
handleClose={handleClose}
|
|
837
|
+
restrictPersonalization={restrictPersonalization}
|
|
838
|
+
isAnonymousType={isAnonymousType}
|
|
839
|
+
onPersonalizationTokensChange={onPersonalizationTokensChange}
|
|
827
840
|
/>
|
|
828
841
|
)
|
|
829
842
|
)}
|
|
@@ -861,6 +874,9 @@ export function SlideBoxContent(props) {
|
|
|
861
874
|
showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
|
|
862
875
|
handleTestAndPreview={handleTestAndPreview}
|
|
863
876
|
handleCloseTestAndPreview={handleCloseTestAndPreview}
|
|
877
|
+
restrictPersonalization={restrictPersonalization}
|
|
878
|
+
isAnonymousType={isAnonymousType}
|
|
879
|
+
onPersonalizationTokensChange={onPersonalizationTokensChange}
|
|
864
880
|
/>
|
|
865
881
|
) : (
|
|
866
882
|
<MobilePushNew
|
|
@@ -895,6 +911,8 @@ export function SlideBoxContent(props) {
|
|
|
895
911
|
showLiquidErrorInFooter={showLiquidErrorInFooter}
|
|
896
912
|
onCreateComplete={onCreateComplete}
|
|
897
913
|
creativesMode={creativesMode}
|
|
914
|
+
restrictPersonalization={restrictPersonalization}
|
|
915
|
+
isAnonymousType={isAnonymousType}
|
|
898
916
|
/>
|
|
899
917
|
)
|
|
900
918
|
)}
|
|
@@ -1143,6 +1161,8 @@ export function SlideBoxContent(props) {
|
|
|
1143
1161
|
supportedTags={supportedTags}
|
|
1144
1162
|
selectedOfferDetails={selectedOfferDetails}
|
|
1145
1163
|
eventContextTags={eventContextTags}
|
|
1164
|
+
restrictPersonalization={restrictPersonalization}
|
|
1165
|
+
isAnonymousType={isAnonymousType}
|
|
1146
1166
|
/>
|
|
1147
1167
|
)}
|
|
1148
1168
|
{isCreateRcs && (<Rcs
|
|
@@ -9,6 +9,7 @@ import ErrorInfoNote from '../../v2Components/ErrorInfoNote';
|
|
|
9
9
|
import { PREVIEW } from './constants';
|
|
10
10
|
import { EMAIL_CREATE_MODES } from '../EmailWrapper/constants';
|
|
11
11
|
import { hasSupportCKEditor } from '../../utils/common';
|
|
12
|
+
import { getMessageForDevice, getTitleForDevice } from '../../utils/commonUtils';
|
|
12
13
|
|
|
13
14
|
function getFullModeSaveBtn(slidBoxContent, isCreatingTemplate) {
|
|
14
15
|
if (isCreatingTemplate) {
|
|
@@ -43,13 +44,15 @@ function SlideBoxFooter(props) {
|
|
|
43
44
|
currentChannel,
|
|
44
45
|
emailCreateMode,
|
|
45
46
|
selectedEmailCreateMode,
|
|
47
|
+
restrictPersonalization = false,
|
|
48
|
+
isAnonymousType = false,
|
|
49
|
+
templateData = {},
|
|
50
|
+
hasPersonalizationTokenError: hasPersonalizationTokenErrorProp = false,
|
|
46
51
|
} = props;
|
|
47
|
-
|
|
48
52
|
// Calculate if buttons should be disabled
|
|
49
53
|
// Only apply validation state checks for EMAIL channel in HTML Editor mode (not BEE/DragDrop)
|
|
50
54
|
// For other channels, BEE editor, or when htmlEditorValidationState is not provided, don't disable based on validation
|
|
51
55
|
const isEmailChannel = currentChannel?.toUpperCase() === 'EMAIL';
|
|
52
|
-
const isSmsChannel = currentChannel?.toUpperCase() === 'SMS';
|
|
53
56
|
const isEditMode = slidBoxContent === 'editTemplate';
|
|
54
57
|
|
|
55
58
|
// Use selectedEmailCreateMode for accurate mode detection in create mode (emailCreateMode is mapped for backwards compatibility)
|
|
@@ -126,9 +129,33 @@ function SlideBoxFooter(props) {
|
|
|
126
129
|
const isBEEEditorModeInCreate = !isHTMLEditorMode && !isEditMode;
|
|
127
130
|
const isBEEEditorMode = isBEEEditorModeInEdit || isBEEEditorModeInCreate;
|
|
128
131
|
const hasBEEEditorErrors = isEmailChannel && isBEEEditorMode && (hasStandardErrors || hasLiquidErrors) && (!htmlEditorValidationState || !htmlEditorHasErrors);
|
|
129
|
-
const hasSmsValidationErrors = isSmsChannel && hasStandardErrors;
|
|
130
132
|
|
|
131
|
-
const shouldShowErrorInfoNote = hasBEEEditorErrors ||
|
|
133
|
+
const shouldShowErrorInfoNote = hasBEEEditorErrors || isSupportCKEditor;
|
|
134
|
+
|
|
135
|
+
// Check for personalization tokens in title/message when anonymous user tries to save
|
|
136
|
+
const hasPersonalizationTokens = () => {
|
|
137
|
+
if (!isAnonymousType || !templateData) return false;
|
|
138
|
+
|
|
139
|
+
const androidTitle = getTitleForDevice(templateData, 'ANDROID') || '';
|
|
140
|
+
const iosTitle = getTitleForDevice(templateData, 'IOS') || '';
|
|
141
|
+
const androidMessageBody = getMessageForDevice(templateData, 'ANDROID') || '';
|
|
142
|
+
const iosMessageBody = getMessageForDevice(templateData, 'IOS') || '';
|
|
143
|
+
// Check for personalization tags {{ }} in title or message body
|
|
144
|
+
const contentToCheck = `${androidTitle} ${iosTitle} ${androidMessageBody} ${iosMessageBody}`;
|
|
145
|
+
// Check for liquid tags {{ }}
|
|
146
|
+
if (contentToCheck.includes('{{') && contentToCheck.includes('}}')) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
// Check for event context tags [
|
|
150
|
+
if (contentToCheck.includes('[') && contentToCheck.includes(']')) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Use prop from parent (FormBuilder flow) when available; else fall back to templateData check
|
|
157
|
+
const hasPersonalizationTokenError = hasPersonalizationTokenErrorProp === true || (restrictPersonalization && hasPersonalizationTokens());
|
|
158
|
+
|
|
132
159
|
return (
|
|
133
160
|
<div className="template-footer-width">
|
|
134
161
|
{shouldShowErrorInfoNote && (
|
|
@@ -149,7 +176,7 @@ function SlideBoxFooter(props) {
|
|
|
149
176
|
<CapRow>
|
|
150
177
|
<CapButton
|
|
151
178
|
onClick={onSave}
|
|
152
|
-
disabled={isTemplateNameEmpty || fetchingCmsData || shouldDisableButtons}
|
|
179
|
+
disabled={isTemplateNameEmpty || fetchingCmsData || shouldDisableButtons || hasPersonalizationTokenError}
|
|
153
180
|
>
|
|
154
181
|
{isFullMode ? (
|
|
155
182
|
getFullModeSaveBtn(slidBoxContent, isCreatingTemplate)
|
|
@@ -161,7 +188,7 @@ function SlideBoxFooter(props) {
|
|
|
161
188
|
<CapButton
|
|
162
189
|
type="secondary"
|
|
163
190
|
onClick={onTestAndPreview}
|
|
164
|
-
disabled={shouldDisableButtons}
|
|
191
|
+
disabled={shouldDisableButtons || hasPersonalizationTokenError}
|
|
165
192
|
style={{ marginLeft: '8px' }}
|
|
166
193
|
>
|
|
167
194
|
<FormattedMessage {...messages.testAndPreview} />
|
|
@@ -222,6 +249,11 @@ SlideBoxFooter.propTypes = {
|
|
|
222
249
|
currentChannel: PropTypes.string,
|
|
223
250
|
emailCreateMode: PropTypes.string,
|
|
224
251
|
selectedEmailCreateMode: PropTypes.string,
|
|
252
|
+
restrictPersonalization: PropTypes.bool,
|
|
253
|
+
isAnonymousType: PropTypes.bool,
|
|
254
|
+
templateData: PropTypes.object,
|
|
255
|
+
formData: PropTypes.array,
|
|
256
|
+
hasPersonalizationTokenError: PropTypes.bool,
|
|
225
257
|
};
|
|
226
258
|
|
|
227
259
|
SlideBoxFooter.defaultProps = {
|
|
@@ -247,5 +279,7 @@ SlideBoxFooter.defaultProps = {
|
|
|
247
279
|
currentChannel: '',
|
|
248
280
|
emailCreateMode: '',
|
|
249
281
|
selectedEmailCreateMode: '',
|
|
282
|
+
formData: [],
|
|
283
|
+
hasPersonalizationTokenError: false,
|
|
250
284
|
};
|
|
251
285
|
export default SlideBoxFooter;
|
|
@@ -52,3 +52,9 @@ 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
|
+
export const MIXED = "MIXED";
|
|
56
|
+
export const VISITOR = "VISITOR";
|
|
57
|
+
export const ALLOWED_CHANNELS_FOR_ANONYMOUS = ['mobilepush', 'webpush'];
|
|
58
|
+
export const ALL_CHANNELS_NEW = [
|
|
59
|
+
'sms', 'email', 'whatsapp', 'facebook', 'line', 'viber', 'rcs', 'zalo', 'inapp', 'call_task', 'ftp',
|
|
60
|
+
];
|