@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
|
@@ -52,7 +52,6 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
52
52
|
modalContent: {title: "Alert", body: "Do you really want to delete this version?", type: 'confirm', id: 'sms-version-modal'},
|
|
53
53
|
showTestAndPreviewSlidebox: false,
|
|
54
54
|
isTestAndPreviewMode: false,
|
|
55
|
-
pendingGetFormData: false,
|
|
56
55
|
};
|
|
57
56
|
this.saveFormData = this.saveFormData.bind(this);
|
|
58
57
|
this.onFormDataChange = this.onFormDataChange.bind(this);
|
|
@@ -135,14 +134,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
135
134
|
}
|
|
136
135
|
|
|
137
136
|
componentWillReceiveProps(nextProps) {
|
|
138
|
-
if (
|
|
139
|
-
this.
|
|
140
|
-
this.pendingGetFormDataTimeout = setTimeout(() => {
|
|
141
|
-
if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
|
|
142
|
-
this.props.getFormSubscriptionData(this.getFormData());
|
|
143
|
-
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
144
|
-
}
|
|
145
|
-
}, 300);
|
|
137
|
+
if (nextProps.location.query.module === 'library' && nextProps.isGetFormData) {
|
|
138
|
+
nextProps.getFormSubscriptionData(this.getFormData());
|
|
146
139
|
}
|
|
147
140
|
if ( nextProps.location.query.module === 'library' && nextProps.subscriptionTemplateDetails && nextProps.subscriptionTemplateDetails.name && _.isEmpty(this.state.editData) && !_.isEmpty(this.state.schema)) {
|
|
148
141
|
this.setEditState(nextProps.subscriptionTemplateDetails);
|
|
@@ -196,9 +189,6 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
196
189
|
}
|
|
197
190
|
|
|
198
191
|
componentWillUnmount() {
|
|
199
|
-
if (this.pendingGetFormDataTimeout) {
|
|
200
|
-
clearTimeout(this.pendingGetFormDataTimeout);
|
|
201
|
-
}
|
|
202
192
|
if (this.props.setIsLoadingContent) {
|
|
203
193
|
this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
|
|
204
194
|
}
|
|
@@ -327,16 +317,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
327
317
|
}
|
|
328
318
|
|
|
329
319
|
setFormValidity(isFormValid, errorData) {
|
|
330
|
-
this.setState({
|
|
331
|
-
if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
|
|
332
|
-
if (this.pendingGetFormDataTimeout) {
|
|
333
|
-
clearTimeout(this.pendingGetFormDataTimeout);
|
|
334
|
-
this.pendingGetFormDataTimeout = null;
|
|
335
|
-
}
|
|
336
|
-
this.props.getFormSubscriptionData(this.getFormData());
|
|
337
|
-
this.setState({ pendingGetFormData: false, startValidation: false });
|
|
338
|
-
}
|
|
339
|
-
});
|
|
320
|
+
this.setState({isFormValid, errorData});
|
|
340
321
|
}
|
|
341
322
|
|
|
342
323
|
getFormData(e, value) {
|
|
@@ -942,13 +923,6 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
942
923
|
this.setState({startValidation: false});
|
|
943
924
|
}
|
|
944
925
|
saveFormData() {
|
|
945
|
-
// In library mode the template is submitted via getFormSubscriptionData, not editTemplate API.
|
|
946
|
-
// Calling editTemplate here would set editTemplateInProgress: true in redux and, because the
|
|
947
|
-
// slidebox closes before the API responds, that flag would never be reset – causing the spinner
|
|
948
|
-
// to be stuck on the next open.
|
|
949
|
-
if (!this.props.isFullMode) {
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
926
|
//Logic to save in db etc
|
|
953
927
|
//saveFormData gets called only when validation result is true
|
|
954
928
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import isEmpty from 'lodash/isEmpty';
|
|
2
|
-
import CapNotification from '@capillarytech/cap-ui-library
|
|
2
|
+
import { CapNotification } from '@capillarytech/cap-ui-library';
|
|
3
3
|
import messages from './Create/messages';
|
|
4
4
|
export function showError() {
|
|
5
5
|
const {intl} = this.props;
|
|
6
6
|
const {errorData} = this.state;
|
|
7
7
|
const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
|
|
8
8
|
if (!isEmpty(this.state.formData) && !this.state.isFormValid) {
|
|
9
|
-
const
|
|
10
|
-
const isSmsInvalid = Object.values(err0).includes(true);
|
|
11
|
-
const isBraceError = Boolean(err0['bracket-error']);
|
|
9
|
+
const isSmsInvalid = Object.values(errorData[0]).includes(true);
|
|
12
10
|
if (isSmsInvalid) {
|
|
11
|
+
const invalidTags = errorData[0]['invalid-tags'];
|
|
12
|
+
if (!isEmpty(invalidTags)) {
|
|
13
|
+
errorMessage.description = `${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
|
|
14
|
+
}
|
|
13
15
|
CapNotification.error(errorMessage);
|
|
14
|
-
} else if (isBraceError) {
|
|
15
|
-
// Do not trigger toast for this path; footer error is the reliable UX in SMS library flow.
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -39,8 +39,10 @@ import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
|
|
|
39
39
|
import UnifiedPreview from '../../../v2Components/CommonTestAndPreview/UnifiedPreview';
|
|
40
40
|
import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
|
|
41
41
|
import withCreatives from '../../../hoc/withCreatives';
|
|
42
|
+
import { validateTags } from '../../../utils/tagValidations';
|
|
42
43
|
import {
|
|
43
44
|
CHARLIMIT,
|
|
45
|
+
SMS,
|
|
44
46
|
SMS_TRAI_VAR,
|
|
45
47
|
TAG,
|
|
46
48
|
EMBEDDED,
|
|
@@ -49,15 +51,16 @@ import {
|
|
|
49
51
|
ALL,
|
|
50
52
|
LIBRARY,
|
|
51
53
|
} from './constants';
|
|
52
|
-
import { SMS } from '../../CreativesContainer/constants';
|
|
53
54
|
import v2EditSmsReducer from '../../Sms/Edit/reducer';
|
|
54
55
|
import { v2SmsEditSagas } from '../../Sms/Edit/sagas';
|
|
55
56
|
import ErrorInfoNote from '../../../v2Components/ErrorInfoNote';
|
|
56
57
|
import { validateLiquidTemplateContent } from '../../../utils/commonUtils';
|
|
58
|
+
import { hasLiquidSupportFeature } from '../../../utils/common';
|
|
57
59
|
import { ANDROID } from '../../../v2Components/CommonTestAndPreview/constants';
|
|
58
60
|
|
|
59
61
|
let varMap = {};
|
|
60
62
|
let traiData = {};
|
|
63
|
+
let tagValidationResponse = {};
|
|
61
64
|
const { TextArea } = CapInput;
|
|
62
65
|
const { CapLabelInline } = CapLabel;
|
|
63
66
|
|
|
@@ -91,6 +94,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
91
94
|
const [tags, updateTags] = useState([]);
|
|
92
95
|
const [textAreaId, updateTextAreaId] = useState();
|
|
93
96
|
const [isValidationError, updateIsValidationError] = useState(false);
|
|
97
|
+
const [isTagValidationError, updateIsTagValidationError] = useState(false);
|
|
94
98
|
const [totalMessageLength, setTotalMessageLength] = useState(0);
|
|
95
99
|
const [isUnicodeAllowed, updateIsUnicodeAllowed] = useState(true);
|
|
96
100
|
const [showMsgLengthNote, updateshowMsgLengthNote] = useState(false);
|
|
@@ -225,6 +229,29 @@ export const SmsTraiEdit = (props) => {
|
|
|
225
229
|
}
|
|
226
230
|
}, []);
|
|
227
231
|
|
|
232
|
+
//performs tag validation
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (
|
|
235
|
+
!isFullMode &&
|
|
236
|
+
updatedSmsEditor?.length > 0 &&
|
|
237
|
+
!updatedSmsEditor.includes(SMS_TRAI_VAR)
|
|
238
|
+
) {
|
|
239
|
+
tagValidationResponse =
|
|
240
|
+
validateTags({
|
|
241
|
+
content: updatedSmsEditor.join(''),
|
|
242
|
+
tagsParam: tags,
|
|
243
|
+
injectedTagsParams: injectedTags,
|
|
244
|
+
location,
|
|
245
|
+
tagModule: getDefaultTags,
|
|
246
|
+
eventContextTags,
|
|
247
|
+
isFullMode,
|
|
248
|
+
}) || {};
|
|
249
|
+
updateIsTagValidationError(
|
|
250
|
+
tagValidationResponse.unsupportedTags.length > 0,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}, [updatedSmsEditor, tags]);
|
|
254
|
+
|
|
228
255
|
const computeUpdatedSmsEditor = () => {
|
|
229
256
|
const arr = [...tempMsgArray];
|
|
230
257
|
const varMapKeys = Object.keys(varMap)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
|
|
@@ -261,8 +288,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
261
288
|
};
|
|
262
289
|
|
|
263
290
|
const onSubmitWrapper = () => {
|
|
264
|
-
setIsLiquidValidationError(false);
|
|
265
|
-
setLiquidErrorMessages({});
|
|
266
291
|
const content = updatedSmsEditor.join('');
|
|
267
292
|
const onError = ({ standardErrors, liquidErrors }) => {
|
|
268
293
|
setLiquidErrorMessages({
|
|
@@ -273,8 +298,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
273
298
|
};
|
|
274
299
|
|
|
275
300
|
const onSuccess = () => {
|
|
276
|
-
setIsLiquidValidationError(false);
|
|
277
|
-
setLiquidErrorMessages({});
|
|
278
301
|
onDoneCallback();
|
|
279
302
|
};
|
|
280
303
|
validateLiquidTemplateContent(content, {
|
|
@@ -283,6 +306,10 @@ export const SmsTraiEdit = (props) => {
|
|
|
283
306
|
messages: formBuilderMessages,
|
|
284
307
|
onError,
|
|
285
308
|
onSuccess,
|
|
309
|
+
tagLookupMap: metaEntities?.tagLookupMap,
|
|
310
|
+
eventContextTags,
|
|
311
|
+
isLiquidFlow: true,
|
|
312
|
+
forwardedTags: {},
|
|
286
313
|
});
|
|
287
314
|
};
|
|
288
315
|
|
|
@@ -521,6 +548,17 @@ export const SmsTraiEdit = (props) => {
|
|
|
521
548
|
return countVarChar;
|
|
522
549
|
};
|
|
523
550
|
|
|
551
|
+
const tagValidationErrorMessage = () => {
|
|
552
|
+
const { unsupportedTags = [] } = tagValidationResponse;
|
|
553
|
+
let tagError = '';
|
|
554
|
+
if (unsupportedTags.length > 0) {
|
|
555
|
+
tagError = formatMessage(messages.unsupportedTagsValidationError, {
|
|
556
|
+
unsupportedTags,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return <CapError>{tagError}</CapError>;
|
|
560
|
+
};
|
|
561
|
+
|
|
524
562
|
const disablehandler = () => {
|
|
525
563
|
if (traiData && !isEmpty(traiData)) {
|
|
526
564
|
const msg = get(traiData, `versions.base.sms-editor`, '');
|
|
@@ -566,6 +604,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
566
604
|
setShowTestAndPreviewSlidebox(false);
|
|
567
605
|
};
|
|
568
606
|
|
|
607
|
+
const isLiquidSupportFeatureEnabled = hasLiquidSupportFeature();
|
|
569
608
|
return (
|
|
570
609
|
<>
|
|
571
610
|
<CapSpin spinning={loading || fetchingLiquidTags} tip={fetchingLiquidTags && formatMessage(formBuilderMessages.liquidSpinText)}>
|
|
@@ -623,6 +662,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
623
662
|
<CapRow>
|
|
624
663
|
{smsLengthForVar()}
|
|
625
664
|
</CapRow>
|
|
665
|
+
{isTagValidationError && tagValidationErrorMessage()}
|
|
626
666
|
<CapCheckbox onChange={unicodeHandler} checked={isUnicodeAllowed} disabled={disablehandler()}>
|
|
627
667
|
{formatMessage(messages.unicodeLabel)}
|
|
628
668
|
</CapCheckbox>
|
|
@@ -653,8 +693,9 @@ export const SmsTraiEdit = (props) => {
|
|
|
653
693
|
<FormattedMessage {...messages.testAndPreviewButtonLabel} />
|
|
654
694
|
</CapButton>
|
|
655
695
|
<CapButton
|
|
656
|
-
onClick={onSubmitWrapper}
|
|
696
|
+
onClick={isLiquidSupportFeatureEnabled ? onSubmitWrapper : onDoneCallback}
|
|
657
697
|
className="create-msg"
|
|
698
|
+
disabled={isTagValidationError || (isLiquidSupportFeatureEnabled && !isObject(metaEntities?.tagLookupMap))}
|
|
658
699
|
>
|
|
659
700
|
<FormattedMessage {...messages.saveButtonLabel} />
|
|
660
701
|
</CapButton>
|
|
@@ -4265,7 +4265,7 @@ FREE GIFTS-
|
|
|
4265
4265
|
<CapCheckbox
|
|
4266
4266
|
checked={false}
|
|
4267
4267
|
disabled={false}
|
|
4268
|
-
key=".
|
|
4268
|
+
key=".4"
|
|
4269
4269
|
labelType="h4"
|
|
4270
4270
|
onChange={[Function]}
|
|
4271
4271
|
>
|
|
@@ -4327,7 +4327,7 @@ FREE GIFTS-
|
|
|
4327
4327
|
</div>
|
|
4328
4328
|
</CapCheckbox>
|
|
4329
4329
|
<div
|
|
4330
|
-
key=".
|
|
4330
|
+
key=".6"
|
|
4331
4331
|
style={
|
|
4332
4332
|
Object {
|
|
4333
4333
|
"marginBottom": "100px",
|
|
@@ -15176,7 +15176,7 @@ FREE GIFTS-
|
|
|
15176
15176
|
<CapCheckbox
|
|
15177
15177
|
checked={false}
|
|
15178
15178
|
disabled={false}
|
|
15179
|
-
key=".
|
|
15179
|
+
key=".4"
|
|
15180
15180
|
labelType="h4"
|
|
15181
15181
|
onChange={[Function]}
|
|
15182
15182
|
>
|
|
@@ -15238,7 +15238,7 @@ FREE GIFTS-
|
|
|
15238
15238
|
</div>
|
|
15239
15239
|
</CapCheckbox>
|
|
15240
15240
|
<div
|
|
15241
|
-
key=".
|
|
15241
|
+
key=".6"
|
|
15242
15242
|
style={
|
|
15243
15243
|
Object {
|
|
15244
15244
|
"marginBottom": "100px",
|
|
@@ -26117,7 +26117,7 @@ FREE GIFTS-
|
|
|
26117
26117
|
<CapCheckbox
|
|
26118
26118
|
checked={false}
|
|
26119
26119
|
disabled={false}
|
|
26120
|
-
key=".
|
|
26120
|
+
key=".4"
|
|
26121
26121
|
labelType="h4"
|
|
26122
26122
|
onChange={[Function]}
|
|
26123
26123
|
>
|
|
@@ -26179,7 +26179,7 @@ FREE GIFTS-
|
|
|
26179
26179
|
</div>
|
|
26180
26180
|
</CapCheckbox>
|
|
26181
26181
|
<div
|
|
26182
|
-
key=".
|
|
26182
|
+
key=".6"
|
|
26183
26183
|
style={
|
|
26184
26184
|
Object {
|
|
26185
26185
|
"marginBottom": "100px",
|
|
@@ -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 = {
|
|
@@ -105,10 +105,12 @@ export const useTagManagement = ({
|
|
|
105
105
|
const validationConfig = useMemo(
|
|
106
106
|
() => ({
|
|
107
107
|
tagsParam: tags,
|
|
108
|
+
injectedTagsParams: injectedTags,
|
|
108
109
|
location,
|
|
109
110
|
tagModule: getDefaultTags,
|
|
111
|
+
eventContextTags,
|
|
110
112
|
}),
|
|
111
|
-
[tags, location, getDefaultTags],
|
|
113
|
+
[tags, injectedTags, location, getDefaultTags, eventContextTags],
|
|
112
114
|
);
|
|
113
115
|
|
|
114
116
|
return {
|
|
@@ -528,19 +528,26 @@ describe('useTagManagement', () => {
|
|
|
528
528
|
|
|
529
529
|
describe('validationConfig', () => {
|
|
530
530
|
it('should return validation config with tags', () => {
|
|
531
|
+
const injectedTags = [{ id: 5, name: 'Injected Tag' }];
|
|
532
|
+
const eventContextTags = [{ id: 6, name: 'Event Tag' }];
|
|
533
|
+
|
|
531
534
|
const { result } = renderHook(() =>
|
|
532
535
|
useTagManagement({
|
|
533
536
|
location: defaultLocation,
|
|
534
537
|
globalActions: mockGlobalActions,
|
|
535
538
|
metaEntities: defaultMetaEntities,
|
|
539
|
+
injectedTags,
|
|
540
|
+
eventContextTags,
|
|
536
541
|
getDefaultTags: 'custom',
|
|
537
542
|
})
|
|
538
543
|
);
|
|
539
544
|
|
|
540
545
|
expect(result.current.validationConfig).toEqual({
|
|
541
546
|
tagsParam: defaultMetaEntities.tags.standard,
|
|
547
|
+
injectedTagsParams: injectedTags,
|
|
542
548
|
location: defaultLocation,
|
|
543
549
|
tagModule: 'custom',
|
|
550
|
+
eventContextTags,
|
|
544
551
|
});
|
|
545
552
|
});
|
|
546
553
|
|
|
@@ -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
|
-
|