@capillarytech/creatives-library 8.0.310 → 8.0.312-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +5 -1
- package/initialState.js +0 -2
- package/package.json +1 -1
- package/utils/common.js +5 -8
- package/utils/commonUtils.js +36 -93
- package/utils/tagValidations.js +83 -223
- package/utils/tests/commonUtil.test.js +147 -124
- package/utils/tests/tagValidations.test.js +441 -358
- package/v2Components/ErrorInfoNote/index.js +2 -5
- package/v2Components/FormBuilder/index.js +137 -203
- package/v2Components/FormBuilder/messages.js +0 -8
- package/v2Components/HtmlEditor/HTMLEditor.js +0 -5
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +0 -15
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +1 -2
- package/v2Containers/Cap/mockData.js +0 -14
- package/v2Containers/Cap/reducer.js +3 -55
- package/v2Containers/Cap/tests/reducer.test.js +0 -102
- package/v2Containers/CreativesContainer/SlideBoxContent.js +5 -1
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -5
- package/v2Containers/CreativesContainer/constants.js +6 -0
- package/v2Containers/CreativesContainer/index.js +47 -7
- package/v2Containers/Email/index.js +1 -5
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +23 -70
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -120
- package/v2Containers/FTP/index.js +2 -51
- package/v2Containers/FTP/messages.js +0 -4
- package/v2Containers/InApp/index.js +35 -107
- package/v2Containers/InApp/tests/index.test.js +17 -6
- package/v2Containers/InappAdvance/index.js +4 -112
- package/v2Containers/InappAdvance/tests/index.test.js +2 -0
- package/v2Containers/Line/Container/Text/index.js +0 -1
- package/v2Containers/MobilePush/Create/index.js +59 -19
- package/v2Containers/MobilePush/Edit/index.js +48 -20
- package/v2Containers/MobilePushNew/index.js +13 -33
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/index.js +12 -37
- package/v2Containers/Sms/Create/index.js +39 -3
- package/v2Containers/Sms/Create/messages.js +4 -0
- package/v2Containers/Sms/Edit/index.js +35 -3
- package/v2Containers/Sms/commonMethods.js +3 -6
- package/v2Containers/Sms/tests/commonMethods.test.js +122 -0
- package/v2Containers/SmsTrai/Edit/index.js +71 -71
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/SmsWrapper/index.js +2 -0
- package/v2Containers/TemplatesV2/index.js +28 -13
- package/v2Containers/Viber/index.js +0 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/utils/validation.js +17 -8
- package/v2Containers/WebPush/Create/utils/validation.test.js +44 -24
- package/v2Containers/Whatsapp/index.js +9 -17
- package/v2Containers/Zalo/index.js +3 -11
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable no-restricted-syntax */
|
|
2
2
|
/* eslint-disable no-undef */
|
|
3
|
-
import React, { useState, useEffect } from 'react';
|
|
3
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
4
4
|
import { createStructuredSelector } from 'reselect';
|
|
5
5
|
import { bindActionCreators } from 'redux';
|
|
6
6
|
import { FormattedMessage } from 'react-intl';
|
|
@@ -39,10 +39,8 @@ 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';
|
|
43
42
|
import {
|
|
44
43
|
CHARLIMIT,
|
|
45
|
-
SMS,
|
|
46
44
|
SMS_TRAI_VAR,
|
|
47
45
|
TAG,
|
|
48
46
|
EMBEDDED,
|
|
@@ -51,16 +49,15 @@ import {
|
|
|
51
49
|
ALL,
|
|
52
50
|
LIBRARY,
|
|
53
51
|
} from './constants';
|
|
52
|
+
import { SMS } from '../../CreativesContainer/constants';
|
|
54
53
|
import v2EditSmsReducer from '../../Sms/Edit/reducer';
|
|
55
54
|
import { v2SmsEditSagas } from '../../Sms/Edit/sagas';
|
|
56
55
|
import ErrorInfoNote from '../../../v2Components/ErrorInfoNote';
|
|
57
56
|
import { validateLiquidTemplateContent } from '../../../utils/commonUtils';
|
|
58
|
-
import { hasLiquidSupportFeature } from '../../../utils/common';
|
|
59
57
|
import { ANDROID } from '../../../v2Components/CommonTestAndPreview/constants';
|
|
60
58
|
|
|
61
59
|
let varMap = {};
|
|
62
60
|
let traiData = {};
|
|
63
|
-
let tagValidationResponse = {};
|
|
64
61
|
const { TextArea } = CapInput;
|
|
65
62
|
const { CapLabelInline } = CapLabel;
|
|
66
63
|
|
|
@@ -85,6 +82,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
85
82
|
eventContextTags,
|
|
86
83
|
fetchingLiquidTags,
|
|
87
84
|
getLiquidTags,
|
|
85
|
+
showLiquidErrorInFooter,
|
|
88
86
|
} = props || {};
|
|
89
87
|
|
|
90
88
|
const { formatMessage } = intl;
|
|
@@ -94,13 +92,20 @@ export const SmsTraiEdit = (props) => {
|
|
|
94
92
|
const [tags, updateTags] = useState([]);
|
|
95
93
|
const [textAreaId, updateTextAreaId] = useState();
|
|
96
94
|
const [isValidationError, updateIsValidationError] = useState(false);
|
|
97
|
-
const [isTagValidationError, updateIsTagValidationError] = useState(false);
|
|
98
95
|
const [totalMessageLength, setTotalMessageLength] = useState(0);
|
|
99
96
|
const [isUnicodeAllowed, updateIsUnicodeAllowed] = useState(true);
|
|
100
97
|
const [showMsgLengthNote, updateshowMsgLengthNote] = useState(false);
|
|
101
98
|
const [liquidErrorMessages, setLiquidErrorMessages] = useState({});
|
|
102
99
|
const [isLiquidValidationError, setIsLiquidValidationError] = useState(false);
|
|
100
|
+
/** After user closes the validation panel, keep it hidden until Save is clicked again (even if ErrorInfoNote remounts). */
|
|
101
|
+
const [liquidErrorPanelDismissed, setLiquidErrorPanelDismissed] = useState(false);
|
|
103
102
|
const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
|
|
103
|
+
const routeTemplateId = (params && params.id) || '';
|
|
104
|
+
const traitSource = isFullMode ? templateDetails : templateData;
|
|
105
|
+
const smsEditorSource = get(traitSource, 'versions.base.sms-editor', '') || '';
|
|
106
|
+
const baseTemplateId = get(traitSource, 'versions.base.template_id', '') || '';
|
|
107
|
+
/** Avoid reparsing props into tempMsgArray when only object identity changes (fixes cleared text snapping back). */
|
|
108
|
+
const lastHydratedSignatureRef = useRef(null);
|
|
104
109
|
const SMSTraiFooter = styled.div`
|
|
105
110
|
background-color: ${CAP_WHITE};
|
|
106
111
|
padding: ${CAP_SPACE_32} ${CAP_SPACE_24};
|
|
@@ -148,29 +153,43 @@ export const SmsTraiEdit = (props) => {
|
|
|
148
153
|
//computing placeholder array for mapping values and rendering dynamic form
|
|
149
154
|
useEffect(() => {
|
|
150
155
|
traiData = isFullMode ? templateDetails : templateData;
|
|
151
|
-
if (traiData
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
156
|
+
if (!traiData || isEmpty(traiData)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const hydrateSignature = `${String(isFullMode)}|${routeTemplateId}|${baseTemplateId}|${smsEditorSource}`;
|
|
160
|
+
if (lastHydratedSignatureRef.current === hydrateSignature) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
lastHydratedSignatureRef.current = hydrateSignature;
|
|
164
|
+
|
|
165
|
+
let msg = smsEditorSource;
|
|
166
|
+
const templateMessageArray = [];
|
|
167
|
+
//converting sms-editor string to an array split at '{#var#}'
|
|
168
|
+
//split and push string before '{#var#}[0 to index]', push '{#var#}',
|
|
169
|
+
//split and repeat for remaining string[index+7 to length]
|
|
170
|
+
while (msg.length !== 0) {
|
|
171
|
+
const index = msg.search(SMS_TRAI_VAR);
|
|
172
|
+
if (index !== -1) {
|
|
173
|
+
templateMessageArray.push(msg.substring(0, index));
|
|
174
|
+
templateMessageArray.push(SMS_TRAI_VAR);
|
|
175
|
+
msg = msg.substring(index + 7, msg.length);
|
|
176
|
+
} else {
|
|
177
|
+
templateMessageArray.push(msg);
|
|
178
|
+
break;
|
|
167
179
|
}
|
|
168
|
-
const filteredTemplateMessageArray = templateMessageArray.filter((i) => i === 0 || i);
|
|
169
|
-
updateTempMsgArray(filteredTemplateMessageArray);
|
|
170
|
-
//stop spinner
|
|
171
|
-
updateLoading(false);
|
|
172
180
|
}
|
|
173
|
-
|
|
181
|
+
const filteredTemplateMessageArray = templateMessageArray.filter((i) => i === 0 || i);
|
|
182
|
+
updateTempMsgArray(filteredTemplateMessageArray);
|
|
183
|
+
//stop spinner
|
|
184
|
+
updateLoading(false);
|
|
185
|
+
}, [
|
|
186
|
+
isFullMode,
|
|
187
|
+
routeTemplateId,
|
|
188
|
+
baseTemplateId,
|
|
189
|
+
smsEditorSource,
|
|
190
|
+
templateDetails,
|
|
191
|
+
templateData,
|
|
192
|
+
]);
|
|
174
193
|
|
|
175
194
|
//compute/get varMapped and updated-sms-editor
|
|
176
195
|
useEffect(() => {
|
|
@@ -229,29 +248,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
229
248
|
}
|
|
230
249
|
}, []);
|
|
231
250
|
|
|
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
|
-
|
|
255
251
|
const computeUpdatedSmsEditor = () => {
|
|
256
252
|
const arr = [...tempMsgArray];
|
|
257
253
|
const varMapKeys = Object.keys(varMap)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
|
|
@@ -288,6 +284,14 @@ export const SmsTraiEdit = (props) => {
|
|
|
288
284
|
};
|
|
289
285
|
|
|
290
286
|
const onSubmitWrapper = () => {
|
|
287
|
+
setIsLiquidValidationError(false);
|
|
288
|
+
setLiquidErrorMessages({});
|
|
289
|
+
setLiquidErrorPanelDismissed(false);
|
|
290
|
+
// Liquid validation (extractTags) only in library mode
|
|
291
|
+
if (isFullMode) {
|
|
292
|
+
onDoneCallback();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
291
295
|
const content = updatedSmsEditor.join('');
|
|
292
296
|
const onError = ({ standardErrors, liquidErrors }) => {
|
|
293
297
|
setLiquidErrorMessages({
|
|
@@ -298,6 +302,8 @@ export const SmsTraiEdit = (props) => {
|
|
|
298
302
|
};
|
|
299
303
|
|
|
300
304
|
const onSuccess = () => {
|
|
305
|
+
setIsLiquidValidationError(false);
|
|
306
|
+
setLiquidErrorMessages({});
|
|
301
307
|
onDoneCallback();
|
|
302
308
|
};
|
|
303
309
|
|
|
@@ -313,10 +319,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
313
319
|
messages: formBuilderMessages,
|
|
314
320
|
onError,
|
|
315
321
|
onSuccess,
|
|
316
|
-
tagLookupMap: metaEntities?.tagLookupMap,
|
|
317
|
-
eventContextTags,
|
|
318
|
-
isLiquidFlow: true,
|
|
319
|
-
forwardedTags: {},
|
|
320
322
|
});
|
|
321
323
|
};
|
|
322
324
|
|
|
@@ -555,17 +557,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
555
557
|
return countVarChar;
|
|
556
558
|
};
|
|
557
559
|
|
|
558
|
-
const tagValidationErrorMessage = () => {
|
|
559
|
-
const { unsupportedTags = [] } = tagValidationResponse;
|
|
560
|
-
let tagError = '';
|
|
561
|
-
if (unsupportedTags.length > 0) {
|
|
562
|
-
tagError = formatMessage(messages.unsupportedTagsValidationError, {
|
|
563
|
-
unsupportedTags,
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
return <CapError>{tagError}</CapError>;
|
|
567
|
-
};
|
|
568
|
-
|
|
569
560
|
const disablehandler = () => {
|
|
570
561
|
if (traiData && !isEmpty(traiData)) {
|
|
571
562
|
const msg = get(traiData, `versions.base.sms-editor`, '');
|
|
@@ -615,7 +606,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
615
606
|
setShowTestAndPreviewSlidebox(false);
|
|
616
607
|
};
|
|
617
608
|
|
|
618
|
-
const isLiquidSupportFeatureEnabled = hasLiquidSupportFeature();
|
|
619
609
|
return (
|
|
620
610
|
<>
|
|
621
611
|
<CapSpin spinning={loading || fetchingLiquidTags} tip={fetchingLiquidTags && formatMessage(formBuilderMessages.liquidSpinText)}>
|
|
@@ -673,7 +663,6 @@ export const SmsTraiEdit = (props) => {
|
|
|
673
663
|
<CapRow>
|
|
674
664
|
{smsLengthForVar()}
|
|
675
665
|
</CapRow>
|
|
676
|
-
{isTagValidationError && tagValidationErrorMessage()}
|
|
677
666
|
<CapCheckbox onChange={unicodeHandler} checked={isUnicodeAllowed} disabled={disablehandler()}>
|
|
678
667
|
{formatMessage(messages.unicodeLabel)}
|
|
679
668
|
</CapCheckbox>
|
|
@@ -693,8 +682,20 @@ export const SmsTraiEdit = (props) => {
|
|
|
693
682
|
</CapColumn>
|
|
694
683
|
</CapRow>
|
|
695
684
|
<SMSTraiFooter>
|
|
696
|
-
{isLiquidValidationError && (
|
|
697
|
-
<ErrorInfoNote
|
|
685
|
+
{isLiquidValidationError && !liquidErrorPanelDismissed && (
|
|
686
|
+
<ErrorInfoNote
|
|
687
|
+
errorMessages={liquidErrorMessages}
|
|
688
|
+
intl={intl}
|
|
689
|
+
onClose={() => {
|
|
690
|
+
setLiquidErrorPanelDismissed(true);
|
|
691
|
+
if (typeof showLiquidErrorInFooter === 'function') {
|
|
692
|
+
showLiquidErrorInFooter({
|
|
693
|
+
STANDARD_ERROR_MSG: [],
|
|
694
|
+
LIQUID_ERROR_MSG: [],
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
}}
|
|
698
|
+
/>
|
|
698
699
|
)}
|
|
699
700
|
<CapButton
|
|
700
701
|
onClick={handleTestAndPreview}
|
|
@@ -704,9 +705,8 @@ export const SmsTraiEdit = (props) => {
|
|
|
704
705
|
<FormattedMessage {...messages.testAndPreviewButtonLabel} />
|
|
705
706
|
</CapButton>
|
|
706
707
|
<CapButton
|
|
707
|
-
onClick={
|
|
708
|
+
onClick={onSubmitWrapper}
|
|
708
709
|
className="create-msg"
|
|
709
|
-
disabled={isTagValidationError || (isLiquidSupportFeatureEnabled && !isObject(metaEntities?.tagLookupMap))}
|
|
710
710
|
>
|
|
711
711
|
<FormattedMessage {...messages.saveButtonLabel} />
|
|
712
712
|
</CapButton>
|
|
@@ -4265,7 +4265,7 @@ FREE GIFTS-
|
|
|
4265
4265
|
<CapCheckbox
|
|
4266
4266
|
checked={false}
|
|
4267
4267
|
disabled={false}
|
|
4268
|
-
key=".
|
|
4268
|
+
key=".3"
|
|
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=".5"
|
|
4331
4331
|
style={
|
|
4332
4332
|
Object {
|
|
4333
4333
|
"marginBottom": "100px",
|
|
@@ -15243,7 +15243,7 @@ FREE GIFTS-
|
|
|
15243
15243
|
<CapCheckbox
|
|
15244
15244
|
checked={false}
|
|
15245
15245
|
disabled={false}
|
|
15246
|
-
key=".
|
|
15246
|
+
key=".3"
|
|
15247
15247
|
labelType="h4"
|
|
15248
15248
|
onChange={[Function]}
|
|
15249
15249
|
>
|
|
@@ -15305,7 +15305,7 @@ FREE GIFTS-
|
|
|
15305
15305
|
</div>
|
|
15306
15306
|
</CapCheckbox>
|
|
15307
15307
|
<div
|
|
15308
|
-
key=".
|
|
15308
|
+
key=".5"
|
|
15309
15309
|
style={
|
|
15310
15310
|
Object {
|
|
15311
15311
|
"marginBottom": "100px",
|
|
@@ -26251,7 +26251,7 @@ FREE GIFTS-
|
|
|
26251
26251
|
<CapCheckbox
|
|
26252
26252
|
checked={false}
|
|
26253
26253
|
disabled={false}
|
|
26254
|
-
key=".
|
|
26254
|
+
key=".3"
|
|
26255
26255
|
labelType="h4"
|
|
26256
26256
|
onChange={[Function]}
|
|
26257
26257
|
>
|
|
@@ -26313,7 +26313,7 @@ FREE GIFTS-
|
|
|
26313
26313
|
</div>
|
|
26314
26314
|
</CapCheckbox>
|
|
26315
26315
|
<div
|
|
26316
|
-
key=".
|
|
26316
|
+
key=".5"
|
|
26317
26317
|
style={
|
|
26318
26318
|
Object {
|
|
26319
26319
|
"marginBottom": "100px",
|
|
@@ -36,6 +36,7 @@ const SmsWrapper = (props) => {
|
|
|
36
36
|
handleTestAndPreview,
|
|
37
37
|
handleCloseTestAndPreview,
|
|
38
38
|
isTestAndPreviewMode,
|
|
39
|
+
onValidationFail,
|
|
39
40
|
} = props;
|
|
40
41
|
|
|
41
42
|
const smsProps = {
|
|
@@ -58,6 +59,7 @@ const SmsWrapper = (props) => {
|
|
|
58
59
|
handleTestAndPreview,
|
|
59
60
|
handleCloseTestAndPreview,
|
|
60
61
|
isTestAndPreviewMode,
|
|
62
|
+
onValidationFail,
|
|
61
63
|
};
|
|
62
64
|
const isTraiDlt = isTraiDLTEnable(isFullMode, smsRegister);
|
|
63
65
|
return <>
|
|
@@ -29,11 +29,18 @@ import FTP from '../FTP';
|
|
|
29
29
|
import Gallery from '../Assets/Gallery';
|
|
30
30
|
import withStyles from '../../hoc/withStyles';
|
|
31
31
|
import styles, { CapTabStyle } from './TemplatesV2.style';
|
|
32
|
-
import { CREATIVES_UI_VIEW, LOYALTY, WHATSAPP, RCS, LINE, EMAIL, ASSETS, JP_LOCALE_HIDE_FEATURE, ZALO, INAPP, WEBPUSH } from '../App/constants';
|
|
32
|
+
import { CREATIVES_UI_VIEW, FTP as FTP_CHANNEL, LOYALTY, WHATSAPP, RCS, LINE, EMAIL, ASSETS, JP_LOCALE_HIDE_FEATURE, ZALO, INAPP, WEBPUSH } from '../App/constants';
|
|
33
33
|
import AccessForbidden from '../../v2Components/AccessForbidden';
|
|
34
34
|
import { getObjFromQueryParams } from '../../utils/v2common';
|
|
35
35
|
import { makeSelectAuthenticated, selectCurrentOrgDetails } from "../../v2Containers/Cap/selectors";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
CALL_TASK,
|
|
38
|
+
COMMON_CHANNELS,
|
|
39
|
+
LOYALTY_SUPPORTED_ACTION,
|
|
40
|
+
MOBILE_PUSH,
|
|
41
|
+
NORMALIZED_CHANNEL_ALIASES,
|
|
42
|
+
SMS,
|
|
43
|
+
} from "../CreativesContainer/constants";
|
|
37
44
|
|
|
38
45
|
const {CapCustomCardList} = CapCustomCard;
|
|
39
46
|
|
|
@@ -95,8 +102,16 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
95
102
|
return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[^a-zA-Z0-9]+/g, '_').toLowerCase();
|
|
96
103
|
};
|
|
97
104
|
|
|
98
|
-
const
|
|
99
|
-
|
|
105
|
+
const buildChannelSet = (channelList) => {
|
|
106
|
+
const normalized = (channelList || []).map((c) => normalizeChannel(c));
|
|
107
|
+
const withAliases = normalized.flatMap((norm) => {
|
|
108
|
+
const canonical = NORMALIZED_CHANNEL_ALIASES[norm];
|
|
109
|
+
return canonical ? [norm, canonical] : [norm];
|
|
110
|
+
});
|
|
111
|
+
return new Set(withAliases);
|
|
112
|
+
};
|
|
113
|
+
const normalizedChannelsToHideSet = buildChannelSet(channelsToHide);
|
|
114
|
+
const normalizedChannelsToDisableSet = buildChannelSet(channelsToDisable);
|
|
100
115
|
|
|
101
116
|
// Build filtered panes by examining each pane's `key` and checking against normalized hide set
|
|
102
117
|
let filteredPanes = Object.keys(defaultPanes).map((k) => defaultPanes[k]).filter((pane) => {
|
|
@@ -108,12 +123,12 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
108
123
|
filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.gallery), key: 'assets' });
|
|
109
124
|
} else {
|
|
110
125
|
// Add special-mode panes only when not hidden (use normalized checks)
|
|
111
|
-
if (!normalizedChannelsToHideSet.has(
|
|
112
|
-
filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.callTask), key:
|
|
126
|
+
if (!normalizedChannelsToHideSet.has(CALL_TASK.toLowerCase())) {
|
|
127
|
+
filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.callTask), key: CALL_TASK.toLowerCase() });
|
|
113
128
|
}
|
|
114
|
-
if (!normalizedChannelsToHideSet.has(
|
|
115
|
-
filteredPanes.push({ content: <></>, tab: intl.formatMessage(messages.FTP), key:
|
|
116
|
-
defaultChannel =
|
|
129
|
+
if (!normalizedChannelsToHideSet.has(FTP_CHANNEL.toLowerCase())) {
|
|
130
|
+
filteredPanes.push({ content: <></>, tab: intl.formatMessage(messages.FTP), key: FTP_CHANNEL.toLowerCase() });
|
|
131
|
+
defaultChannel = FTP_CHANNEL;
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
// Create a local copy of COMMON_CHANNELS to avoid mutating the imported array
|
|
@@ -163,15 +178,15 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
|
|
|
163
178
|
|
|
164
179
|
// If audience is anonymous, prefer mobilepush as default (if not hidden)
|
|
165
180
|
if (isAnonymousType) {
|
|
166
|
-
const mobilePushNorm = normalizeChannel(
|
|
181
|
+
const mobilePushNorm = normalizeChannel(MOBILE_PUSH.toLowerCase());
|
|
167
182
|
if (!normalizedChannelsToHideSet.has(mobilePushNorm)) {
|
|
168
|
-
defaultChannel =
|
|
183
|
+
defaultChannel = MOBILE_PUSH.toLowerCase();
|
|
169
184
|
}
|
|
170
185
|
}
|
|
171
186
|
|
|
172
|
-
const
|
|
187
|
+
const defaultChannelOrder = [SMS.toLowerCase(), EMAIL, MOBILE_PUSH.toLowerCase(), LINE, CALL_TASK.toLowerCase()];
|
|
173
188
|
if (normalizedChannelsToDisableSet.size > 0) {
|
|
174
|
-
|
|
189
|
+
defaultChannelOrder.some((ch) => {
|
|
175
190
|
if (!normalizedChannelsToDisableSet.has(ch)) {
|
|
176
191
|
defaultChannel = ch;
|
|
177
192
|
return true;
|
|
@@ -105,12 +105,10 @@ export const useTagManagement = ({
|
|
|
105
105
|
const validationConfig = useMemo(
|
|
106
106
|
() => ({
|
|
107
107
|
tagsParam: tags,
|
|
108
|
-
injectedTagsParams: injectedTags,
|
|
109
108
|
location,
|
|
110
109
|
tagModule: getDefaultTags,
|
|
111
|
-
eventContextTags,
|
|
112
110
|
}),
|
|
113
|
-
[tags,
|
|
111
|
+
[tags, location, getDefaultTags],
|
|
114
112
|
);
|
|
115
113
|
|
|
116
114
|
return {
|
|
@@ -528,26 +528,19 @@ 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
|
-
|
|
534
531
|
const { result } = renderHook(() =>
|
|
535
532
|
useTagManagement({
|
|
536
533
|
location: defaultLocation,
|
|
537
534
|
globalActions: mockGlobalActions,
|
|
538
535
|
metaEntities: defaultMetaEntities,
|
|
539
|
-
injectedTags,
|
|
540
|
-
eventContextTags,
|
|
541
536
|
getDefaultTags: 'custom',
|
|
542
537
|
})
|
|
543
538
|
);
|
|
544
539
|
|
|
545
540
|
expect(result.current.validationConfig).toEqual({
|
|
546
541
|
tagsParam: defaultMetaEntities.tags.standard,
|
|
547
|
-
injectedTagsParams: injectedTags,
|
|
548
542
|
location: defaultLocation,
|
|
549
543
|
tagModule: 'custom',
|
|
550
|
-
eventContextTags,
|
|
551
544
|
});
|
|
552
545
|
});
|
|
553
546
|
|
|
@@ -295,8 +295,8 @@ const WebPushCreate = ({
|
|
|
295
295
|
const validateTemplateName = useCallback((value) => validateTemplateNameUtil(value), []);
|
|
296
296
|
|
|
297
297
|
const validateTitle = useCallback(
|
|
298
|
-
(value) => validateTitleUtil(value, formatMessage, messages, restrictPersonalization),
|
|
299
|
-
[formatMessage, restrictPersonalization],
|
|
298
|
+
(value) => validateTitleUtil(value, formatMessage, messages, restrictPersonalization, validationConfig, isFullMode),
|
|
299
|
+
[formatMessage, restrictPersonalization, validationConfig, isFullMode],
|
|
300
300
|
);
|
|
301
301
|
|
|
302
302
|
const validateUrl = useCallback(
|
|
@@ -11,19 +11,34 @@ import { hasPersonalizationTags } from '../../../../utils/commonUtils';
|
|
|
11
11
|
export const validateTemplateName = (value) => !value || value.trim() === '';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Validates notification title
|
|
14
|
+
* Validates notification title (required and optional tag validation)
|
|
15
15
|
* @param {string} value - The title value
|
|
16
16
|
* @param {Function} formatMessage - i18n format message function
|
|
17
17
|
* @param {Object} messages - Message definitions
|
|
18
|
+
* @param {Object} [validationConfig] - Optional config for tag validation
|
|
19
|
+
* @param {boolean} [isFullMode] - Optional; when set with validationConfig, runs tag validation
|
|
18
20
|
* @returns {string} Error message if invalid, empty string if valid
|
|
19
21
|
*/
|
|
20
|
-
export const validateTitle = (value, formatMessage, messages, restrictPersonalization) => {
|
|
22
|
+
export const validateTitle = (value, formatMessage, messages, restrictPersonalization, validationConfig, isFullMode) => {
|
|
21
23
|
if (!value || value.trim() === '') {
|
|
22
24
|
return formatMessage(messages.titleRequired);
|
|
23
25
|
}
|
|
24
26
|
if (restrictPersonalization && hasPersonalizationTags(value)) {
|
|
25
27
|
return formatMessage(messages.personalizationTokensErrorMessage);
|
|
26
28
|
}
|
|
29
|
+
|
|
30
|
+
if (validationConfig != null) {
|
|
31
|
+
const validationResponse = validateTags({
|
|
32
|
+
content: value,
|
|
33
|
+
...validationConfig,
|
|
34
|
+
isFullMode,
|
|
35
|
+
}) || {};
|
|
36
|
+
|
|
37
|
+
if (validationResponse?.isBraceError) {
|
|
38
|
+
return formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
return '';
|
|
28
43
|
};
|
|
29
44
|
|
|
@@ -65,12 +80,6 @@ export const validateMessageContent = (value, formatMessage, messages, validatio
|
|
|
65
80
|
isFullMode,
|
|
66
81
|
}) || {};
|
|
67
82
|
|
|
68
|
-
if (validationResponse?.unsupportedTags?.length) {
|
|
69
|
-
return formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
70
|
-
unsupportedTags: validationResponse.unsupportedTags.join(', '),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
83
|
if (validationResponse?.isBraceError) {
|
|
75
84
|
return formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
76
85
|
}
|
|
@@ -127,6 +127,40 @@ describe('validation', () => {
|
|
|
127
127
|
expect(result).toBe('Personalization tags are not supported for anonymous customers');
|
|
128
128
|
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.personalizationTokensErrorMessage);
|
|
129
129
|
});
|
|
130
|
+
|
|
131
|
+
it('should return brace error when validationConfig is provided and validateTags returns isBraceError', () => {
|
|
132
|
+
const validationConfig = { tagsParam: [], location: {}, tagModule: '' };
|
|
133
|
+
validateTags.mockReturnValue({ isBraceError: true });
|
|
134
|
+
const result = validateTitle(
|
|
135
|
+
'Valid Title',
|
|
136
|
+
mockFormatMessage,
|
|
137
|
+
mockMessages,
|
|
138
|
+
false,
|
|
139
|
+
validationConfig,
|
|
140
|
+
false
|
|
141
|
+
);
|
|
142
|
+
expect(result).toBe('Unbalanced curly braces');
|
|
143
|
+
expect(mockFormatMessage).toHaveBeenCalledWith(globalMessages.unbalanacedCurlyBraces);
|
|
144
|
+
expect(validateTags).toHaveBeenCalledWith({
|
|
145
|
+
content: 'Valid Title',
|
|
146
|
+
...validationConfig,
|
|
147
|
+
isFullMode: false,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should not run tag validation when validationConfig is null', () => {
|
|
152
|
+
validateTags.mockClear();
|
|
153
|
+
const result = validateTitle('Valid Title', mockFormatMessage, mockMessages, false, null);
|
|
154
|
+
expect(result).toBe('');
|
|
155
|
+
expect(validateTags).not.toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should not run tag validation when validationConfig is undefined', () => {
|
|
159
|
+
validateTags.mockClear();
|
|
160
|
+
const result = validateTitle('Valid Title', mockFormatMessage, mockMessages, false, undefined);
|
|
161
|
+
expect(result).toBe('');
|
|
162
|
+
expect(validateTags).not.toHaveBeenCalled();
|
|
163
|
+
});
|
|
130
164
|
});
|
|
131
165
|
|
|
132
166
|
describe('validateUrl', () => {
|
|
@@ -183,10 +217,8 @@ describe('validation', () => {
|
|
|
183
217
|
describe('validateMessageContent', () => {
|
|
184
218
|
const mockValidationConfig = {
|
|
185
219
|
tagsParam: [],
|
|
186
|
-
injectedTagsParams: [],
|
|
187
220
|
location: {},
|
|
188
221
|
tagModule: '',
|
|
189
|
-
eventContextTags: [],
|
|
190
222
|
};
|
|
191
223
|
|
|
192
224
|
beforeEach(() => {
|
|
@@ -225,17 +257,6 @@ describe('validation', () => {
|
|
|
225
257
|
});
|
|
226
258
|
});
|
|
227
259
|
|
|
228
|
-
it('should return error message for unsupported tags', () => {
|
|
229
|
-
validateTags.mockReturnValue({
|
|
230
|
-
unsupportedTags: ['tag1', 'tag2'],
|
|
231
|
-
});
|
|
232
|
-
const result = validateMessageContent('Message with {tag1} and {tag2}', mockFormatMessage, mockMessages, mockValidationConfig);
|
|
233
|
-
expect(result).toBe('Unsupported tags: tag1, tag2');
|
|
234
|
-
expect(mockFormatMessage).toHaveBeenCalledWith(globalMessages.unsupportedTagsValidationError, {
|
|
235
|
-
unsupportedTags: 'tag1, tag2',
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
260
|
it('should return error message for unbalanced curly braces', () => {
|
|
240
261
|
validateTags.mockReturnValue({
|
|
241
262
|
isBraceError: true,
|
|
@@ -245,22 +266,11 @@ describe('validation', () => {
|
|
|
245
266
|
expect(mockFormatMessage).toHaveBeenCalledWith(globalMessages.unbalanacedCurlyBraces);
|
|
246
267
|
});
|
|
247
268
|
|
|
248
|
-
it('should return error message for both unsupported tags and brace error (unsupported tags takes precedence)', () => {
|
|
249
|
-
validateTags.mockReturnValue({
|
|
250
|
-
unsupportedTags: ['tag1'],
|
|
251
|
-
isBraceError: true,
|
|
252
|
-
});
|
|
253
|
-
const result = validateMessageContent('Message with {tag1}', mockFormatMessage, mockMessages, mockValidationConfig);
|
|
254
|
-
expect(result).toBe('Unsupported tags: tag1');
|
|
255
|
-
});
|
|
256
|
-
|
|
257
269
|
it('should pass validation config to validateTags', () => {
|
|
258
270
|
const customConfig = {
|
|
259
271
|
tagsParam: [{ id: 1, name: 'Tag1' }],
|
|
260
|
-
injectedTagsParams: [{ id: 2, name: 'Tag2' }],
|
|
261
272
|
location: { query: { type: 'test' } },
|
|
262
273
|
tagModule: 'custom',
|
|
263
|
-
eventContextTags: [{ id: 3, name: 'Tag3' }],
|
|
264
274
|
};
|
|
265
275
|
validateTags.mockReturnValue({});
|
|
266
276
|
validateMessageContent('Valid message', mockFormatMessage, mockMessages, customConfig);
|
|
@@ -306,6 +316,16 @@ describe('validation', () => {
|
|
|
306
316
|
expect(result).toBe('Personalization tags are not supported for anonymous customers');
|
|
307
317
|
expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.personalizationTokensErrorMessage);
|
|
308
318
|
});
|
|
319
|
+
|
|
320
|
+
it('should pass isFullMode to validateTags when provided', () => {
|
|
321
|
+
validateTags.mockReturnValue({});
|
|
322
|
+
validateMessageContent('Valid message', mockFormatMessage, mockMessages, mockValidationConfig, true);
|
|
323
|
+
expect(validateTags).toHaveBeenCalledWith({
|
|
324
|
+
content: 'Valid message',
|
|
325
|
+
...mockValidationConfig,
|
|
326
|
+
isFullMode: true,
|
|
327
|
+
});
|
|
328
|
+
});
|
|
309
329
|
});
|
|
310
330
|
});
|
|
311
331
|
|