@capillarytech/creatives-library 8.0.286 → 8.0.287
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/package.json +1 -1
- package/utils/commonUtils.js +3 -0
- package/v2Components/CapTagList/index.js +6 -2
- package/v2Components/CapTagListWithInput/index.js +4 -0
- package/v2Components/FormBuilder/index.js +26 -3
- package/v2Components/FormBuilder/messages.js +4 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +20 -0
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +39 -3
- package/v2Containers/CreativesContainer/constants.js +6 -0
- package/v2Containers/CreativesContainer/index.js +32 -1
- 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/MobilePush/Create/index.js +45 -0
- package/v2Containers/MobilePush/Create/messages.js +4 -0
- package/v2Containers/MobilePush/Edit/index.js +45 -0
- 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 +32 -3
- package/v2Containers/MobilePushNew/messages.js +8 -0
- package/v2Containers/MobilepushWrapper/index.js +7 -1
- package/v2Containers/SmsTrai/Create/index.scss +1 -1
- 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.scss +1 -1
- package/v2Containers/WebPush/Create/index.js +25 -6
- package/v2Containers/WebPush/Create/messages.js +8 -1
- package/v2Containers/WebPush/Create/utils/validation.js +16 -9
- package/v2Containers/WebPush/Create/utils/validation.test.js +28 -0
|
@@ -396,6 +396,17 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
396
396
|
}
|
|
397
397
|
|
|
398
398
|
render() {
|
|
399
|
+
const { restrictPersonalization, disabled, disableTooltipMsg, intl } = this.props;
|
|
400
|
+
|
|
401
|
+
// Compute disabled state and tooltip message
|
|
402
|
+
let isDisabled = disabled || false;
|
|
403
|
+
let tooltipMsg = disableTooltipMsg;
|
|
404
|
+
|
|
405
|
+
if (restrictPersonalization && !disabled) {
|
|
406
|
+
isDisabled = true;
|
|
407
|
+
tooltipMsg = intl.formatMessage(messages.personalizationNotSupportedAnonymous);
|
|
408
|
+
}
|
|
409
|
+
|
|
399
410
|
return (
|
|
400
411
|
<div className={this.props.className ? this.props.className : ''}>
|
|
401
412
|
<CapTagList
|
|
@@ -411,7 +422,9 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
411
422
|
modalProps={this.props.modalProps}
|
|
412
423
|
currentOrgDetails={this.props.currentOrgDetails}
|
|
413
424
|
channel={this.props.channel}
|
|
414
|
-
disabled={
|
|
425
|
+
disabled={isDisabled}
|
|
426
|
+
// custom tooltip message to show when disabled
|
|
427
|
+
disableTooltipMsg={tooltipMsg}
|
|
415
428
|
fetchingSchemaError={this?.state?.tagsError}
|
|
416
429
|
popoverPlacement={this.props.popoverPlacement}
|
|
417
430
|
/>
|
|
@@ -445,6 +458,9 @@ TagList.propTypes = {
|
|
|
445
458
|
fetchingSchemaError: PropTypes.bool,
|
|
446
459
|
eventContextTags: PropTypes.array,
|
|
447
460
|
popoverPlacement: PropTypes.string,
|
|
461
|
+
// message to show when Add Label button is disabled (e.g. personalization restriction)
|
|
462
|
+
disableTooltipMsg: PropTypes.string,
|
|
463
|
+
restrictPersonalization: PropTypes.bool,
|
|
448
464
|
intl: PropTypes.shape({
|
|
449
465
|
formatMessage: PropTypes.func.isRequired,
|
|
450
466
|
locale: PropTypes.string,
|
|
@@ -15,4 +15,8 @@ export default defineMessages({
|
|
|
15
15
|
id: `${scope}.entryEvent`,
|
|
16
16
|
defaultMessage: 'Entry event',
|
|
17
17
|
},
|
|
18
|
+
personalizationNotSupportedAnonymous: {
|
|
19
|
+
id: `${scope}.personalizationNotSupportedAnonymous`,
|
|
20
|
+
defaultMessage: 'Personalization tags are not supported for anonymous customers',
|
|
21
|
+
},
|
|
18
22
|
});
|
|
@@ -12,7 +12,6 @@ import { createStructuredSelector } from 'reselect';
|
|
|
12
12
|
import { bindActionCreators, compose } from 'redux';
|
|
13
13
|
import { CapTab, CapCustomCard, CapButton, CapHeader, CapSpin, CapIcon, CapTooltip } from '@capillarytech/cap-ui-library';
|
|
14
14
|
import { find, get } from 'lodash';
|
|
15
|
-
import isEmpty from 'lodash/isEmpty';
|
|
16
15
|
import Helmet from 'react-helmet';
|
|
17
16
|
|
|
18
17
|
import { UserIsAuthenticated } from '../../utils/authWrapper';
|
|
@@ -58,6 +57,7 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
58
57
|
cap = {},
|
|
59
58
|
loyaltyMetaData = {},
|
|
60
59
|
isLoyaltyModule = false,
|
|
60
|
+
isAnonymousType = false,
|
|
61
61
|
} = props;
|
|
62
62
|
|
|
63
63
|
const defaultPanes = {
|
|
@@ -89,44 +89,54 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
89
89
|
key: 'wechat',
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
// Robust normalization function: converts camelCase, hyphens, spaces and mixed-case to snake_case lowercase
|
|
93
|
+
const normalizeChannel = (raw = '') => {
|
|
94
|
+
const str = (raw || '').toString();
|
|
95
|
+
return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[^a-zA-Z0-9]+/g, '_').toLowerCase();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const normalizedChannelsToHideSet = new Set((channelsToHide || []).map((c) => normalizeChannel(c)));
|
|
99
|
+
const normalizedChannelsToDisableSet = new Set((channelsToDisable || []).map((c) => normalizeChannel(c)));
|
|
100
|
+
|
|
101
|
+
// Build filtered panes by examining each pane's `key` and checking against normalized hide set
|
|
102
|
+
let filteredPanes = Object.keys(defaultPanes).map((k) => defaultPanes[k]).filter((pane) => {
|
|
103
|
+
const paneKey = normalizeChannel(pane.key);
|
|
104
|
+
return !normalizedChannelsToHideSet.has(paneKey);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (isFullMode) {
|
|
108
|
+
filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.gallery), key: 'assets' });
|
|
100
109
|
} else {
|
|
101
|
-
|
|
102
|
-
|
|
110
|
+
// Add special-mode panes only when not hidden (use normalized checks)
|
|
111
|
+
if (!normalizedChannelsToHideSet.has('call_task')) {
|
|
112
|
+
filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.callTask), key: 'call_task' });
|
|
103
113
|
}
|
|
104
|
-
if (!
|
|
105
|
-
filteredPanes.push({content: <></>, tab: intl.formatMessage(messages.FTP), key: 'ftp'});
|
|
114
|
+
if (!normalizedChannelsToHideSet.has('ftp')) {
|
|
115
|
+
filteredPanes.push({ content: <></>, tab: intl.formatMessage(messages.FTP), key: 'ftp' });
|
|
106
116
|
defaultChannel = 'FTP';
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
// Create a local copy of COMMON_CHANNELS to avoid mutating the imported array
|
|
110
120
|
const channels = [...COMMON_CHANNELS];
|
|
111
|
-
const { actionName = ''} = loyaltyMetaData;
|
|
121
|
+
const { actionName = '' } = loyaltyMetaData;
|
|
112
122
|
if (isLoyaltyModule && actionName === LOYALTY_SUPPORTED_ACTION) {
|
|
113
123
|
channels.push(WHATSAPP, ZALO);
|
|
114
124
|
}
|
|
115
125
|
|
|
116
|
-
// we only show channels
|
|
117
|
-
// if it is coming in enableNewChannels array
|
|
126
|
+
// we only show channels other than COMMON_CHANNELS if they are present in enableNewChannels
|
|
118
127
|
filteredPanes = filteredPanes.filter((item) => {
|
|
119
|
-
const
|
|
120
|
-
if (!channels.includes(
|
|
121
|
-
return enableNewChannels.includes(
|
|
128
|
+
const channelKey = normalizeChannel(item.key);
|
|
129
|
+
if (!channels.includes(channelKey)) {
|
|
130
|
+
return enableNewChannels.includes(channelKey.toUpperCase());
|
|
122
131
|
}
|
|
123
132
|
return true;
|
|
124
133
|
});
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
|
|
128
|
-
filteredPanes = filteredPanes.map(
|
|
129
|
-
|
|
137
|
+
filteredPanes = filteredPanes.map((pane) => {
|
|
138
|
+
const paneKeyNorm = normalizeChannel(pane.key);
|
|
139
|
+
if (normalizedChannelsToDisableSet.has(paneKeyNorm)) {
|
|
130
140
|
// eslint-disable-next-line no-param-reassign
|
|
131
141
|
pane.disabled = true;
|
|
132
142
|
if (pane.key === 'facebook' && showDisabledFBInfo) {
|
|
@@ -151,10 +161,18 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
151
161
|
filteredPanes = hideEngagementChannel ? filteredPanes?.filter((pane) => [EMAIL, LINE, ASSETS].includes(pane?.key) && pane) : filteredPanes;
|
|
152
162
|
defaultChannel = hideEngagementChannel ? EMAIL : defaultChannel;
|
|
153
163
|
|
|
164
|
+
// If audience is anonymous, prefer mobilepush as default (if not hidden)
|
|
165
|
+
if (isAnonymousType) {
|
|
166
|
+
const mobilePushNorm = normalizeChannel('mobilepush');
|
|
167
|
+
if (!normalizedChannelsToHideSet.has(mobilePushNorm)) {
|
|
168
|
+
defaultChannel = 'mobilepush';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
154
172
|
const channel = ['sms', 'email', 'mobilepush', 'line', 'call_task'];
|
|
155
|
-
if (
|
|
173
|
+
if (normalizedChannelsToDisableSet.size > 0) {
|
|
156
174
|
channel.some((ch) => {
|
|
157
|
-
if (!
|
|
175
|
+
if (!normalizedChannelsToDisableSet.has(ch)) {
|
|
158
176
|
defaultChannel = ch;
|
|
159
177
|
return true;
|
|
160
178
|
}
|
|
@@ -380,6 +398,8 @@ TemplatesV2.propTypes = {
|
|
|
380
398
|
FTPMode: PropTypes.string,
|
|
381
399
|
messageStrategy: PropTypes.string,
|
|
382
400
|
currentOrgDetails: PropTypes.object,
|
|
401
|
+
restrictPersonalization: PropTypes.bool,
|
|
402
|
+
isAnonymousType: PropTypes.bool,
|
|
383
403
|
};
|
|
384
404
|
|
|
385
405
|
TemplatesV2.defaultProps = {
|
|
@@ -85,6 +85,9 @@ const MemoizedTagList = memo(({
|
|
|
85
85
|
eventContextTags,
|
|
86
86
|
forwardedTags,
|
|
87
87
|
onTagSelect,
|
|
88
|
+
restrictPersonalization = false,
|
|
89
|
+
disabled = false,
|
|
90
|
+
disableTooltipMsg,
|
|
88
91
|
}) => (
|
|
89
92
|
<TagList
|
|
90
93
|
moduleFilterEnabled={moduleFilterEnabled}
|
|
@@ -97,6 +100,9 @@ const MemoizedTagList = memo(({
|
|
|
97
100
|
eventContextTags={eventContextTags}
|
|
98
101
|
forwardedTags={forwardedTags}
|
|
99
102
|
onTagSelect={onTagSelect}
|
|
103
|
+
restrictPersonalization={restrictPersonalization}
|
|
104
|
+
disabled={disabled || restrictPersonalization}
|
|
105
|
+
disableTooltipMsg={disableTooltipMsg}
|
|
100
106
|
/>
|
|
101
107
|
), (prevProps, nextProps) => {
|
|
102
108
|
// Custom comparison function for better memoization
|
|
@@ -111,6 +117,9 @@ const MemoizedTagList = memo(({
|
|
|
111
117
|
&& prevProps.eventContextTags === nextProps.eventContextTags
|
|
112
118
|
&& prevProps.forwardedTags === nextProps.forwardedTags
|
|
113
119
|
&& prevProps.onTagSelect === nextProps.onTagSelect
|
|
120
|
+
&& prevProps.restrictPersonalization === nextProps.restrictPersonalization
|
|
121
|
+
&& prevProps.disabled === nextProps.disabled
|
|
122
|
+
&& prevProps.disableTooltipMsg === nextProps.disableTooltipMsg
|
|
114
123
|
);
|
|
115
124
|
});
|
|
116
125
|
|
|
@@ -144,6 +153,7 @@ const WebPushCreate = ({
|
|
|
144
153
|
eventContextTags = [],
|
|
145
154
|
templateActions: templateActionsProps,
|
|
146
155
|
Templates,
|
|
156
|
+
restrictPersonalization = false,
|
|
147
157
|
}) => {
|
|
148
158
|
const { formatMessage } = intl;
|
|
149
159
|
|
|
@@ -285,8 +295,8 @@ const WebPushCreate = ({
|
|
|
285
295
|
const validateTemplateName = useCallback((value) => validateTemplateNameUtil(value), []);
|
|
286
296
|
|
|
287
297
|
const validateTitle = useCallback(
|
|
288
|
-
(value) => validateTitleUtil(value, formatMessage, messages),
|
|
289
|
-
[formatMessage],
|
|
298
|
+
(value) => validateTitleUtil(value, formatMessage, messages, restrictPersonalization),
|
|
299
|
+
[formatMessage, restrictPersonalization],
|
|
290
300
|
);
|
|
291
301
|
|
|
292
302
|
const validateUrl = useCallback(
|
|
@@ -309,8 +319,8 @@ const WebPushCreate = ({
|
|
|
309
319
|
|
|
310
320
|
|
|
311
321
|
const validateMessageContent = useCallback(
|
|
312
|
-
(value) => validateMessageContentUtil(value, formatMessage, messages, validationConfig, isFullMode),
|
|
313
|
-
[formatMessage, validationConfig, isFullMode],
|
|
322
|
+
(value) => validateMessageContentUtil(value, formatMessage, messages, validationConfig, isFullMode, restrictPersonalization),
|
|
323
|
+
[formatMessage, validationConfig, isFullMode, restrictPersonalization],
|
|
314
324
|
);
|
|
315
325
|
|
|
316
326
|
useEffect(() => {
|
|
@@ -546,7 +556,7 @@ const WebPushCreate = ({
|
|
|
546
556
|
// Pure validator that returns boolean without setting error state
|
|
547
557
|
const validateFormSilent = () => {
|
|
548
558
|
const templateNameInvalid = isFullMode && validateTemplateName(templateName);
|
|
549
|
-
const titleValidation = validateTitle(notificationTitle);
|
|
559
|
+
const titleValidation = validateTitle(notificationTitle, restrictPersonalization);
|
|
550
560
|
const messageValidation = validateMessageContent(message);
|
|
551
561
|
|
|
552
562
|
return !(templateNameInvalid || titleValidation || messageValidation);
|
|
@@ -569,6 +579,7 @@ const WebPushCreate = ({
|
|
|
569
579
|
return;
|
|
570
580
|
}
|
|
571
581
|
|
|
582
|
+
|
|
572
583
|
// Set flag to indicate save/edit operation has been initiated
|
|
573
584
|
saveInitiatedRef.current = true;
|
|
574
585
|
|
|
@@ -825,8 +836,11 @@ const WebPushCreate = ({
|
|
|
825
836
|
selectedOfferDetails,
|
|
826
837
|
eventContextTags,
|
|
827
838
|
forwardedTags,
|
|
839
|
+
restrictPersonalization,
|
|
840
|
+
disabled: restrictPersonalization,
|
|
841
|
+
disableTooltipMsg: restrictPersonalization ? formatMessage(messages.personalizationNotSupportedAnonymous) : undefined,
|
|
828
842
|
}),
|
|
829
|
-
[tags, injectedTags, selectedOfferDetails, eventContextTags, forwardedTags],
|
|
843
|
+
[tags, injectedTags, selectedOfferDetails, eventContextTags, forwardedTags, restrictPersonalization, formatMessage],
|
|
830
844
|
);
|
|
831
845
|
|
|
832
846
|
// Memoized TagList components with optimized props
|
|
@@ -881,6 +895,7 @@ const WebPushCreate = ({
|
|
|
881
895
|
)
|
|
882
896
|
)
|
|
883
897
|
|| (onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL && (!redirectUrl.trim() || redirectUrlError))
|
|
898
|
+
|| !!titleError || !!messageError
|
|
884
899
|
),
|
|
885
900
|
[
|
|
886
901
|
createTemplateInProgress,
|
|
@@ -906,6 +921,8 @@ const WebPushCreate = ({
|
|
|
906
921
|
onClickBehaviour,
|
|
907
922
|
redirectUrl,
|
|
908
923
|
redirectUrlError,
|
|
924
|
+
titleError,
|
|
925
|
+
messageError,
|
|
909
926
|
],
|
|
910
927
|
);
|
|
911
928
|
|
|
@@ -1064,6 +1081,7 @@ WebPushCreate.propTypes = {
|
|
|
1064
1081
|
selectedOfferDetails: PropTypes.array,
|
|
1065
1082
|
eventContextTags: PropTypes.array,
|
|
1066
1083
|
templateActions: PropTypes.object,
|
|
1084
|
+
restrictPersonalization: PropTypes.bool,
|
|
1067
1085
|
};
|
|
1068
1086
|
|
|
1069
1087
|
WebPushCreate.defaultProps = {
|
|
@@ -1092,6 +1110,7 @@ WebPushCreate.defaultProps = {
|
|
|
1092
1110
|
eventContextTags: [],
|
|
1093
1111
|
templateActions: {},
|
|
1094
1112
|
Templates: {},
|
|
1113
|
+
restrictPersonalization: false,
|
|
1095
1114
|
};
|
|
1096
1115
|
|
|
1097
1116
|
const mapStateToProps = createStructuredSelector({
|
|
@@ -207,5 +207,12 @@ export default defineMessages({
|
|
|
207
207
|
id: `${scope}.templateIdMissingError`,
|
|
208
208
|
defaultMessage: 'Unable to save template: Template ID is missing. Please refresh the page and try again.',
|
|
209
209
|
},
|
|
210
|
+
personalizationTokensErrorMessage: {
|
|
211
|
+
id: `${scope}.personalizationTokensErrorMessage`,
|
|
212
|
+
defaultMessage: 'Personalization tags are not supported for anonymous customers. Please remove the tags.',
|
|
213
|
+
},
|
|
214
|
+
personalizationNotSupportedAnonymous: {
|
|
215
|
+
id: `${scope}.personalizationNotSupportedAnonymous`,
|
|
216
|
+
defaultMessage: 'Personalization tags are not supported for anonymous customers',
|
|
217
|
+
},
|
|
210
218
|
});
|
|
211
|
-
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isValidHttpUrl } from './urlValidation';
|
|
2
2
|
import { validateTags } from '../../../../utils/tagValidations';
|
|
3
3
|
import globalMessages from '../../../Cap/messages';
|
|
4
|
+
import { hasPersonalizationTags } from '../../../../utils/commonUtils';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Validates template name (checks if empty)
|
|
@@ -16,10 +17,13 @@ export const validateTemplateName = (value) => !value || value.trim() === '';
|
|
|
16
17
|
* @param {Object} messages - Message definitions
|
|
17
18
|
* @returns {string} Error message if invalid, empty string if valid
|
|
18
19
|
*/
|
|
19
|
-
export const validateTitle = (value, formatMessage, messages) => {
|
|
20
|
+
export const validateTitle = (value, formatMessage, messages, restrictPersonalization) => {
|
|
20
21
|
if (!value || value.trim() === '') {
|
|
21
22
|
return formatMessage(messages.titleRequired);
|
|
22
23
|
}
|
|
24
|
+
if (restrictPersonalization && hasPersonalizationTags(value)) {
|
|
25
|
+
return formatMessage(messages.personalizationTokensErrorMessage);
|
|
26
|
+
}
|
|
23
27
|
return '';
|
|
24
28
|
};
|
|
25
29
|
|
|
@@ -34,11 +38,11 @@ export const validateUrl = (value, formatMessage, messages) => {
|
|
|
34
38
|
if (!value || value.trim() === '') {
|
|
35
39
|
return formatMessage(messages.urlRequired);
|
|
36
40
|
}
|
|
37
|
-
|
|
41
|
+
|
|
38
42
|
if (!isValidHttpUrl(value)) {
|
|
39
43
|
return formatMessage(messages.urlInvalid);
|
|
40
44
|
}
|
|
41
|
-
|
|
45
|
+
|
|
42
46
|
return '';
|
|
43
47
|
};
|
|
44
48
|
|
|
@@ -50,27 +54,30 @@ export const validateUrl = (value, formatMessage, messages) => {
|
|
|
50
54
|
* @param {Object} validationConfig - Configuration for tag validation
|
|
51
55
|
* @returns {string} Error message if invalid, empty string if valid
|
|
52
56
|
*/
|
|
53
|
-
export const validateMessageContent = (value, formatMessage, messages, validationConfig, isFullMode) => {
|
|
57
|
+
export const validateMessageContent = (value, formatMessage, messages, validationConfig, isFullMode, restrictPersonalization) => {
|
|
54
58
|
if (!value || value.trim() === '') {
|
|
55
59
|
return formatMessage(messages.messageRequired);
|
|
56
60
|
}
|
|
57
|
-
|
|
61
|
+
|
|
58
62
|
const validationResponse = validateTags({
|
|
59
63
|
content: value,
|
|
60
64
|
...validationConfig,
|
|
61
65
|
isFullMode,
|
|
62
66
|
}) || {};
|
|
63
|
-
|
|
67
|
+
|
|
64
68
|
if (validationResponse?.unsupportedTags?.length) {
|
|
65
69
|
return formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
66
70
|
unsupportedTags: validationResponse.unsupportedTags.join(', '),
|
|
67
71
|
});
|
|
68
72
|
}
|
|
69
|
-
|
|
73
|
+
|
|
70
74
|
if (validationResponse?.isBraceError) {
|
|
71
75
|
return formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
72
76
|
}
|
|
73
|
-
|
|
77
|
+
|
|
78
|
+
if (restrictPersonalization && hasPersonalizationTags(value)) {
|
|
79
|
+
return formatMessage(messages.personalizationTokensErrorMessage);
|
|
80
|
+
}
|
|
81
|
+
|
|
74
82
|
return '';
|
|
75
83
|
};
|
|
76
|
-
|
|
@@ -44,6 +44,9 @@ describe('validation', () => {
|
|
|
44
44
|
messageRequired: {
|
|
45
45
|
defaultMessage: 'Message is required',
|
|
46
46
|
},
|
|
47
|
+
personalizationTokensErrorMessage: {
|
|
48
|
+
defaultMessage: 'Personalization tags are not supported for anonymous customers',
|
|
49
|
+
},
|
|
47
50
|
};
|
|
48
51
|
|
|
49
52
|
beforeEach(() => {
|
|
@@ -113,6 +116,17 @@ describe('validation', () => {
|
|
|
113
116
|
const result = validateTitle(' Valid Title ', mockFormatMessage, mockMessages);
|
|
114
117
|
expect(result).toBe('');
|
|
115
118
|
});
|
|
119
|
+
|
|
120
|
+
it('should return personalization error when restrictPersonalization is true and title has personalization tags', () => {
|
|
121
|
+
const result = validateTitle(
|
|
122
|
+
'Hello {{name}}',
|
|
123
|
+
mockFormatMessage,
|
|
124
|
+
mockMessages,
|
|
125
|
+
true
|
|
126
|
+
);
|
|
127
|
+
expect(result).toBe('Personalization tags are not supported for anonymous customers');
|
|
128
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.personalizationTokensErrorMessage);
|
|
129
|
+
});
|
|
116
130
|
});
|
|
117
131
|
|
|
118
132
|
describe('validateUrl', () => {
|
|
@@ -278,6 +292,20 @@ describe('validation', () => {
|
|
|
278
292
|
...mockValidationConfig,
|
|
279
293
|
});
|
|
280
294
|
});
|
|
295
|
+
|
|
296
|
+
it('should return personalization error when restrictPersonalization is true and message has personalization tags', () => {
|
|
297
|
+
validateTags.mockReturnValue({});
|
|
298
|
+
const result = validateMessageContent(
|
|
299
|
+
'Hello {{name}}',
|
|
300
|
+
mockFormatMessage,
|
|
301
|
+
mockMessages,
|
|
302
|
+
mockValidationConfig,
|
|
303
|
+
true,
|
|
304
|
+
true
|
|
305
|
+
);
|
|
306
|
+
expect(result).toBe('Personalization tags are not supported for anonymous customers');
|
|
307
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.personalizationTokensErrorMessage);
|
|
308
|
+
});
|
|
281
309
|
});
|
|
282
310
|
});
|
|
283
311
|
|