@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.
Files changed (65) hide show
  1. package/constants/unified.js +1 -0
  2. package/initialState.js +2 -0
  3. package/package.json +1 -1
  4. package/utils/common.js +8 -5
  5. package/utils/commonUtils.js +111 -2
  6. package/utils/tagValidations.js +222 -84
  7. package/utils/tests/commonUtil.test.js +118 -147
  8. package/utils/tests/tagValidations.test.js +358 -280
  9. package/v2Components/CapTagList/index.js +7 -2
  10. package/v2Components/CapTagListWithInput/index.js +4 -0
  11. package/v2Components/ErrorInfoNote/index.js +5 -2
  12. package/v2Components/FormBuilder/index.js +187 -74
  13. package/v2Components/FormBuilder/messages.js +12 -0
  14. package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
  15. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  16. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
  17. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +2 -1
  18. package/v2Containers/Cap/mockData.js +14 -0
  19. package/v2Containers/Cap/reducer.js +55 -3
  20. package/v2Containers/Cap/tests/reducer.test.js +102 -0
  21. package/v2Containers/CreativesContainer/SlideBoxContent.js +20 -0
  22. package/v2Containers/CreativesContainer/SlideBoxFooter.js +40 -6
  23. package/v2Containers/CreativesContainer/constants.js +6 -0
  24. package/v2Containers/CreativesContainer/index.js +36 -5
  25. package/v2Containers/CreativesContainer/messages.js +12 -0
  26. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +339 -0
  27. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +18 -0
  28. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +37 -0
  29. package/v2Containers/Email/index.js +5 -1
  30. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +62 -10
  31. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +115 -12
  32. package/v2Containers/FTP/index.js +51 -2
  33. package/v2Containers/FTP/messages.js +4 -0
  34. package/v2Containers/InApp/index.js +96 -1
  35. package/v2Containers/InApp/tests/index.test.js +6 -17
  36. package/v2Containers/InappAdvance/index.js +103 -2
  37. package/v2Containers/Line/Container/Text/index.js +1 -0
  38. package/v2Containers/MobilePush/Create/index.js +37 -1
  39. package/v2Containers/MobilePush/Create/messages.js +4 -0
  40. package/v2Containers/MobilePush/Edit/index.js +37 -2
  41. package/v2Containers/MobilePush/Edit/messages.js +4 -0
  42. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +36 -12
  43. package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +68 -27
  44. package/v2Containers/MobilePushNew/index.js +92 -5
  45. package/v2Containers/MobilePushNew/messages.js +8 -0
  46. package/v2Containers/MobilepushWrapper/index.js +7 -1
  47. package/v2Containers/Rcs/index.js +37 -12
  48. package/v2Containers/Sms/Create/index.js +3 -31
  49. package/v2Containers/Sms/Create/messages.js +0 -4
  50. package/v2Containers/Sms/Edit/index.js +3 -29
  51. package/v2Containers/Sms/commonMethods.js +6 -6
  52. package/v2Containers/SmsTrai/Edit/index.js +47 -6
  53. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  54. package/v2Containers/TagList/index.js +17 -1
  55. package/v2Containers/TagList/messages.js +4 -0
  56. package/v2Containers/TemplatesV2/index.js +43 -23
  57. package/v2Containers/Viber/index.js +1 -0
  58. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
  59. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
  60. package/v2Containers/WebPush/Create/index.js +25 -6
  61. package/v2Containers/WebPush/Create/messages.js +8 -1
  62. package/v2Containers/WebPush/Create/utils/validation.js +20 -22
  63. package/v2Containers/WebPush/Create/utils/validation.test.js +52 -0
  64. package/v2Containers/Whatsapp/index.js +17 -9
  65. 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 (!nextProps.isFullMode && nextProps.isGetFormData) {
139
- this.setState({ startValidation: true, pendingGetFormData: true });
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({ isFormValid, errorData }, () => {
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/CapNotification';
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 err0 = errorData[0] || {};
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=".3"
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=".5"
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=".3"
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=".5"
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=".3"
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=".5"
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={this.props.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
- let filteredPanes = Object.keys(defaultPanes)
93
- .filter((key) => !channelsToHide.includes(key)).reduce((obj = [], key) => {
94
- obj.push(defaultPanes[key]);
95
- return obj;
96
- }, []);
97
-
98
- if (isFullMode ) {
99
- filteredPanes.push({content: <div></div>, tab: intl.formatMessage(messages.gallery), key: 'assets'});
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
- if (!channelsToHide.includes('callTask')) {
102
- filteredPanes.push({content: <div></div>, tab: intl.formatMessage(messages.callTask), key: 'call_task'});
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 (!channelsToHide.includes('FTP')) {
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 which other than COMMON_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 channel = item.key;
120
- if (!channels.includes(channel)) {
121
- return enableNewChannels.includes(channel.toUpperCase());
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( (pane) => {
129
- if (channelsToDisable.includes(pane.key)) {
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 (!isEmpty(channelsToDisable)) {
173
+ if (normalizedChannelsToDisableSet.size > 0) {
156
174
  channel.some((ch) => {
157
- if (!channelsToDisable.includes(ch)) {
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 = {
@@ -234,6 +234,7 @@ export const Viber = (props) => {
234
234
  const { valid, isBraceError } = validateTags({
235
235
  content: value,
236
236
  tagsParam: tags,
237
+ injectedTagsParams: injectedTags,
237
238
  location,
238
239
  tagModule: 'outbound',
239
240
  isFullMode,
@@ -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, validationConfig, isFullMode),
289
- [formatMessage, validationConfig, isFullMode],
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
-