@capillarytech/creatives-library 8.0.309 → 8.0.310

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 (79) hide show
  1. package/constants/unified.js +1 -5
  2. package/initialState.js +2 -0
  3. package/package.json +1 -1
  4. package/services/api.js +0 -17
  5. package/services/tests/api.test.js +0 -85
  6. package/utils/common.js +8 -5
  7. package/utils/commonUtils.js +93 -46
  8. package/utils/tagValidations.js +223 -83
  9. package/utils/tests/commonUtil.test.js +124 -316
  10. package/utils/tests/tagValidations.test.js +358 -441
  11. package/v2Components/CommonTestAndPreview/SendTestMessage.js +49 -78
  12. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -134
  13. package/v2Components/CommonTestAndPreview/actions.js +0 -10
  14. package/v2Components/CommonTestAndPreview/constants.js +1 -15
  15. package/v2Components/CommonTestAndPreview/index.js +19 -80
  16. package/v2Components/CommonTestAndPreview/messages.js +0 -94
  17. package/v2Components/CommonTestAndPreview/reducer.js +0 -10
  18. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +0 -53
  19. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
  20. package/v2Components/CommonTestAndPreview/tests/index.test.js +0 -36
  21. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
  22. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +0 -377
  23. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
  24. package/v2Components/ErrorInfoNote/index.js +5 -2
  25. package/v2Components/FormBuilder/index.js +203 -137
  26. package/v2Components/FormBuilder/messages.js +8 -0
  27. package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
  28. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  29. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
  30. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +2 -1
  31. package/v2Containers/Cap/mockData.js +14 -0
  32. package/v2Containers/Cap/reducer.js +55 -3
  33. package/v2Containers/Cap/tests/reducer.test.js +102 -0
  34. package/v2Containers/CreativesContainer/SlideBoxContent.js +1 -5
  35. package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -13
  36. package/v2Containers/CreativesContainer/constants.js +0 -6
  37. package/v2Containers/CreativesContainer/index.js +7 -47
  38. package/v2Containers/Email/index.js +5 -1
  39. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +70 -23
  40. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +120 -20
  41. package/v2Containers/FTP/index.js +51 -2
  42. package/v2Containers/FTP/messages.js +4 -0
  43. package/v2Containers/InApp/index.js +107 -35
  44. package/v2Containers/InApp/tests/index.test.js +6 -17
  45. package/v2Containers/InappAdvance/index.js +112 -4
  46. package/v2Containers/InappAdvance/tests/index.test.js +0 -2
  47. package/v2Containers/Line/Container/Text/index.js +1 -0
  48. package/v2Containers/MobilePush/Create/index.js +19 -59
  49. package/v2Containers/MobilePush/Edit/index.js +20 -48
  50. package/v2Containers/MobilePushNew/index.js +32 -12
  51. package/v2Containers/MobilepushWrapper/index.js +1 -3
  52. package/v2Containers/Rcs/index.js +37 -12
  53. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1276 -1408
  54. package/v2Containers/Sms/Create/index.js +3 -39
  55. package/v2Containers/Sms/Create/messages.js +0 -4
  56. package/v2Containers/Sms/Edit/index.js +3 -35
  57. package/v2Containers/Sms/commonMethods.js +6 -3
  58. package/v2Containers/SmsTrai/Edit/index.js +47 -11
  59. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +294 -327
  60. package/v2Containers/SmsWrapper/index.js +0 -2
  61. package/v2Containers/TemplatesV2/index.js +13 -28
  62. package/v2Containers/Viber/index.js +1 -0
  63. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
  64. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
  65. package/v2Containers/WebPush/Create/index.js +2 -2
  66. package/v2Containers/WebPush/Create/utils/validation.js +8 -17
  67. package/v2Containers/WebPush/Create/utils/validation.test.js +24 -44
  68. package/v2Containers/Whatsapp/index.js +17 -9
  69. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5246
  70. package/v2Containers/Zalo/index.js +11 -3
  71. package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
  72. package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -284
  73. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -72
  74. package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
  75. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -657
  76. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +0 -172
  77. package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -466
  78. package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
  79. package/v2Containers/Sms/tests/commonMethods.test.js +0 -122
@@ -36,7 +36,6 @@ const SmsWrapper = (props) => {
36
36
  handleTestAndPreview,
37
37
  handleCloseTestAndPreview,
38
38
  isTestAndPreviewMode,
39
- onValidationFail,
40
39
  } = props;
41
40
 
42
41
  const smsProps = {
@@ -59,7 +58,6 @@ const SmsWrapper = (props) => {
59
58
  handleTestAndPreview,
60
59
  handleCloseTestAndPreview,
61
60
  isTestAndPreviewMode,
62
- onValidationFail,
63
61
  };
64
62
  const isTraiDlt = isTraiDLTEnable(isFullMode, smsRegister);
65
63
  return <>
@@ -29,18 +29,11 @@ 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, FTP as FTP_CHANNEL, LOYALTY, WHATSAPP, RCS, LINE, EMAIL, ASSETS, JP_LOCALE_HIDE_FEATURE, ZALO, INAPP, WEBPUSH } from '../App/constants';
32
+ import { CREATIVES_UI_VIEW, 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 {
37
- CALL_TASK,
38
- COMMON_CHANNELS,
39
- LOYALTY_SUPPORTED_ACTION,
40
- MOBILE_PUSH,
41
- NORMALIZED_CHANNEL_ALIASES,
42
- SMS,
43
- } from "../CreativesContainer/constants";
36
+ import { LOYALTY_SUPPORTED_ACTION, COMMON_CHANNELS } from "../CreativesContainer/constants";
44
37
 
45
38
  const {CapCustomCardList} = CapCustomCard;
46
39
 
@@ -102,16 +95,8 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
102
95
  return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[^a-zA-Z0-9]+/g, '_').toLowerCase();
103
96
  };
104
97
 
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);
98
+ const normalizedChannelsToHideSet = new Set((channelsToHide || []).map((c) => normalizeChannel(c)));
99
+ const normalizedChannelsToDisableSet = new Set((channelsToDisable || []).map((c) => normalizeChannel(c)));
115
100
 
116
101
  // Build filtered panes by examining each pane's `key` and checking against normalized hide set
117
102
  let filteredPanes = Object.keys(defaultPanes).map((k) => defaultPanes[k]).filter((pane) => {
@@ -123,12 +108,12 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
123
108
  filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.gallery), key: 'assets' });
124
109
  } else {
125
110
  // Add special-mode panes only when not hidden (use normalized checks)
126
- if (!normalizedChannelsToHideSet.has(CALL_TASK.toLowerCase())) {
127
- filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.callTask), key: CALL_TASK.toLowerCase() });
111
+ if (!normalizedChannelsToHideSet.has('call_task')) {
112
+ filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.callTask), key: 'call_task' });
128
113
  }
129
- if (!normalizedChannelsToHideSet.has(FTP_CHANNEL.toLowerCase())) {
130
- filteredPanes.push({ content: <></>, tab: intl.formatMessage(messages.FTP), key: FTP_CHANNEL.toLowerCase() });
131
- defaultChannel = FTP_CHANNEL;
114
+ if (!normalizedChannelsToHideSet.has('ftp')) {
115
+ filteredPanes.push({ content: <></>, tab: intl.formatMessage(messages.FTP), key: 'ftp' });
116
+ defaultChannel = 'FTP';
132
117
  }
133
118
 
134
119
  // Create a local copy of COMMON_CHANNELS to avoid mutating the imported array
@@ -178,15 +163,15 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
178
163
 
179
164
  // If audience is anonymous, prefer mobilepush as default (if not hidden)
180
165
  if (isAnonymousType) {
181
- const mobilePushNorm = normalizeChannel(MOBILE_PUSH.toLowerCase());
166
+ const mobilePushNorm = normalizeChannel('mobilepush');
182
167
  if (!normalizedChannelsToHideSet.has(mobilePushNorm)) {
183
- defaultChannel = MOBILE_PUSH.toLowerCase();
168
+ defaultChannel = 'mobilepush';
184
169
  }
185
170
  }
186
171
 
187
- const defaultChannelOrder = [SMS.toLowerCase(), EMAIL, MOBILE_PUSH.toLowerCase(), LINE, CALL_TASK.toLowerCase()];
172
+ const channel = ['sms', 'email', 'mobilepush', 'line', 'call_task'];
188
173
  if (normalizedChannelsToDisableSet.size > 0) {
189
- defaultChannelOrder.some((ch) => {
174
+ channel.some((ch) => {
190
175
  if (!normalizedChannelsToDisableSet.has(ch)) {
191
176
  defaultChannel = ch;
192
177
  return true;
@@ -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
 
@@ -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, validationConfig, isFullMode),
299
- [formatMessage, restrictPersonalization, validationConfig, isFullMode],
298
+ (value) => validateTitleUtil(value, formatMessage, messages, restrictPersonalization),
299
+ [formatMessage, restrictPersonalization],
300
300
  );
301
301
 
302
302
  const validateUrl = useCallback(
@@ -11,34 +11,19 @@ import { hasPersonalizationTags } from '../../../../utils/commonUtils';
11
11
  export const validateTemplateName = (value) => !value || value.trim() === '';
12
12
 
13
13
  /**
14
- * Validates notification title (required and optional tag validation)
14
+ * Validates notification title
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
20
18
  * @returns {string} Error message if invalid, empty string if valid
21
19
  */
22
- export const validateTitle = (value, formatMessage, messages, restrictPersonalization, validationConfig, isFullMode) => {
20
+ export const validateTitle = (value, formatMessage, messages, restrictPersonalization) => {
23
21
  if (!value || value.trim() === '') {
24
22
  return formatMessage(messages.titleRequired);
25
23
  }
26
24
  if (restrictPersonalization && hasPersonalizationTags(value)) {
27
25
  return formatMessage(messages.personalizationTokensErrorMessage);
28
26
  }
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
-
42
27
  return '';
43
28
  };
44
29
 
@@ -80,6 +65,12 @@ export const validateMessageContent = (value, formatMessage, messages, validatio
80
65
  isFullMode,
81
66
  }) || {};
82
67
 
68
+ if (validationResponse?.unsupportedTags?.length) {
69
+ return formatMessage(globalMessages.unsupportedTagsValidationError, {
70
+ unsupportedTags: validationResponse.unsupportedTags.join(', '),
71
+ });
72
+ }
73
+
83
74
  if (validationResponse?.isBraceError) {
84
75
  return formatMessage(globalMessages.unbalanacedCurlyBraces);
85
76
  }
@@ -127,40 +127,6 @@ 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
- });
164
130
  });
165
131
 
166
132
  describe('validateUrl', () => {
@@ -217,8 +183,10 @@ describe('validation', () => {
217
183
  describe('validateMessageContent', () => {
218
184
  const mockValidationConfig = {
219
185
  tagsParam: [],
186
+ injectedTagsParams: [],
220
187
  location: {},
221
188
  tagModule: '',
189
+ eventContextTags: [],
222
190
  };
223
191
 
224
192
  beforeEach(() => {
@@ -257,6 +225,17 @@ describe('validation', () => {
257
225
  });
258
226
  });
259
227
 
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
+
260
239
  it('should return error message for unbalanced curly braces', () => {
261
240
  validateTags.mockReturnValue({
262
241
  isBraceError: true,
@@ -266,11 +245,22 @@ describe('validation', () => {
266
245
  expect(mockFormatMessage).toHaveBeenCalledWith(globalMessages.unbalanacedCurlyBraces);
267
246
  });
268
247
 
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
+
269
257
  it('should pass validation config to validateTags', () => {
270
258
  const customConfig = {
271
259
  tagsParam: [{ id: 1, name: 'Tag1' }],
260
+ injectedTagsParams: [{ id: 2, name: 'Tag2' }],
272
261
  location: { query: { type: 'test' } },
273
262
  tagModule: 'custom',
263
+ eventContextTags: [{ id: 3, name: 'Tag3' }],
274
264
  };
275
265
  validateTags.mockReturnValue({});
276
266
  validateMessageContent('Valid message', mockFormatMessage, mockMessages, customConfig);
@@ -316,16 +306,6 @@ describe('validation', () => {
316
306
  expect(result).toBe('Personalization tags are not supported for anonymous customers');
317
307
  expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.personalizationTokensErrorMessage);
318
308
  });
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
- });
329
309
  });
330
310
  });
331
311
 
@@ -637,21 +637,22 @@ export const Whatsapp = (props) => {
637
637
  validateTags({
638
638
  content: contentData.join(""),
639
639
  tagsParam: tags,
640
+ injectedTagsParams: injectedTags,
640
641
  location,
641
642
  tagModule: getDefaultTags,
643
+ eventContextTags,
642
644
  isFullMode,
643
645
  }) || {};
646
+ const unsupportedTagsLengthCheck =
647
+ validationResponse?.unsupportedTags?.length > 0;
644
648
  if (type === HEADER_TEXT) {
645
649
  headerTagValidationResponse = validationResponse;
646
- updateIsHeaderTagValidationError(validationResponse.isBraceError);
650
+ updateIsHeaderTagValidationError(unsupportedTagsLengthCheck);
647
651
  } else if (type === CAROUSEL_TEXT) {
648
- return [
649
- { fieldName: "carouselTagValidationErrMessage", value: validationResponse.isBraceError ? validationResponse : {} },
650
- { fieldName: "carouselTagValidationErr", value: validationResponse.isBraceError },
651
- ];
652
+ return [{fieldName: "carouselTagValidationErrMessage", value: validationResponse}, {fieldName: "carouselTagValidationErr", value: unsupportedTagsLengthCheck}];
652
653
  } else {
653
654
  tagValidationResponse = validationResponse;
654
- updateIsTagValidationError(validationResponse.isBraceError);
655
+ updateIsTagValidationError(unsupportedTagsLengthCheck);
655
656
  }
656
657
  }
657
658
  };
@@ -2681,11 +2682,18 @@ const isAuthenticationTemplate = isEqual(templateCategory, WHATSAPP_CATEGORIES.a
2681
2682
  } else {
2682
2683
  validationResponse = tagValidationResponse;
2683
2684
  }
2684
- const { isBraceError } = validationResponse || {};
2685
+ const { unsupportedTags = [], isBraceError } = validationResponse;
2686
+
2687
+ let tagError = "";
2688
+ if (unsupportedTags.length > 0) {
2689
+ tagError = formatMessage(globalMessages.unsupportedTagsValidationError, {
2690
+ unsupportedTags,
2691
+ });
2692
+ }
2685
2693
  if (isBraceError) {
2686
- return <CapError>{formatMessage(globalMessages.unbalanacedCurlyBraces)}</CapError>;
2694
+ tagError = formatMessage(globalMessages.unbalanacedCurlyBraces);
2687
2695
  }
2688
- return null;
2696
+ return <CapError>{tagError}</CapError>;
2689
2697
  };
2690
2698
 
2691
2699
  const editModeContent = () => (