@capillarytech/creatives-library 8.0.326 → 8.0.327

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 (45) hide show
  1. package/package.json +1 -1
  2. package/utils/tests/tagValidations.test.js +34 -0
  3. package/v2Components/CapTagList/index.js +14 -22
  4. package/v2Components/CapTagList/style.scss +48 -0
  5. package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  6. package/v2Components/CapTagListWithInput/index.js +4 -0
  7. package/v2Components/CapWhatsappCTA/index.js +2 -0
  8. package/v2Components/FormBuilder/index.js +7 -0
  9. package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
  10. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  11. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
  12. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
  13. package/v2Containers/BeeEditor/index.js +3 -0
  14. package/v2Containers/CreativesContainer/SlideBoxContent.js +28 -1
  15. package/v2Containers/CreativesContainer/index.js +3 -0
  16. package/v2Containers/Email/index.js +1 -0
  17. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +7 -1
  18. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  19. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
  20. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  21. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -0
  22. package/v2Containers/EmailWrapper/index.js +4 -0
  23. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  24. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
  25. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
  26. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
  27. package/v2Containers/InAppWrapper/index.js +3 -0
  28. package/v2Containers/MobilePush/Create/index.js +2 -0
  29. package/v2Containers/MobilePush/Edit/index.js +2 -0
  30. package/v2Containers/MobilepushWrapper/index.js +3 -1
  31. package/v2Containers/Rcs/index.js +1 -0
  32. package/v2Containers/Sms/Create/index.js +2 -0
  33. package/v2Containers/Sms/Edit/index.js +2 -0
  34. package/v2Containers/SmsTrai/Edit/index.js +2 -0
  35. package/v2Containers/SmsWrapper/index.js +2 -0
  36. package/v2Containers/TagList/index.js +41 -2
  37. package/v2Containers/TagList/messages.js +4 -0
  38. package/v2Containers/TagList/tests/TagList.test.js +122 -20
  39. package/v2Containers/TagList/tests/mockdata.js +17 -0
  40. package/v2Containers/Viber/index.js +5 -0
  41. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
  42. package/v2Containers/WebPush/Create/index.js +9 -1
  43. package/v2Containers/Whatsapp/index.js +5 -0
  44. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +20 -0
  45. package/v2Containers/Zalo/index.js +2 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.326",
4
+ "version": "8.0.327",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -242,6 +242,7 @@ describe("validateTags", () => {
242
242
  tagsParam,
243
243
  location,
244
244
  tagModule,
245
+ waitEventContextTags: {},
245
246
  });
246
247
 
247
248
  expect(result.valid).toEqual(true);
@@ -273,6 +274,7 @@ describe("validateTags", () => {
273
274
  tagsParam: tagsParamLocal,
274
275
  location,
275
276
  tagModule,
277
+ waitEventContextTags: {},
276
278
  });
277
279
 
278
280
  expect(result.valid).toEqual(true);
@@ -310,6 +312,7 @@ describe("validateTags", () => {
310
312
  tagsParam: tagsParamLocal,
311
313
  location,
312
314
  tagModule,
315
+ waitEventContextTags: {},
313
316
  });
314
317
 
315
318
  expect(result.valid).toEqual(false);
@@ -335,6 +338,7 @@ describe("validateTags", () => {
335
338
  tagsParam: tagsParamUnsubscribe,
336
339
  location,
337
340
  tagModule,
341
+ waitEventContextTags: {},
338
342
  });
339
343
  expect(resultMissing.missingTags).toContain("unsubscribe");
340
344
  expect(resultMissing.valid).toBe(false);
@@ -345,6 +349,7 @@ describe("validateTags", () => {
345
349
  tagsParam: tagsParamUnsubscribe,
346
350
  location,
347
351
  tagModule,
352
+ waitEventContextTags: {},
348
353
  });
349
354
  expect(resultSkipped.missingTags).not.toContain("unsubscribe");
350
355
  expect(resultSkipped.valid).toBe(true);
@@ -360,6 +365,35 @@ describe("validateTags", () => {
360
365
  expect(resultWhitespace.valid).toBe(true);
361
366
  expect(resultWhitespace.unsupportedTags ?? []).toEqual([]);
362
367
  });
368
+
369
+ it('should treat tags from waitEventContextTags as supported', () => {
370
+ const content = 'Hello {{waitEvent.orderId}}';
371
+ const tagsParam = [];
372
+ const injectedTagsParams = [];
373
+ const location = { query: { module: 'DEFAULT' } };
374
+ const tagModule = null;
375
+ const waitEventContextTags = {
376
+ block1: {
377
+ eventName: 'Order Placed',
378
+ blockName: 'Wait Block',
379
+ tags: [{ tagName: 'waitEvent.orderId', label: 'Order ID' }],
380
+ },
381
+ };
382
+
383
+ const result = validateTags({
384
+ content,
385
+ tagsParam,
386
+ injectedTagsParams,
387
+ location,
388
+ tagModule,
389
+ eventContextTags: [],
390
+ waitEventContextTags,
391
+ });
392
+
393
+ expect(result.valid).toEqual(true);
394
+ expect(result.missingTags).toEqual([]);
395
+ expect(result.isBraceError).toEqual(false);
396
+ });
363
397
  });
364
398
 
365
399
  describe('validateTags wrapper (v2 consumers)', () => {
@@ -207,6 +207,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
207
207
  } else {
208
208
  this.props.onSelect(selectedKeys, info);
209
209
  this.setState({visible: false});
210
+ this.setState({expandedKeys: []})
210
211
  }
211
212
  } else if (info && info.selectedNodes && info.selectedNodes.length > 0 && !info.selectedNodes[0].props.isLeaf) {
212
213
  this.handleOnExpand(selectedKeys[0]);
@@ -233,6 +234,13 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
233
234
  }
234
235
  };
235
236
 
237
+ /** Single-line ellipsis within popover width; full label on hover via CapTooltip. */
238
+ wrapTreeTitle = (displayNode, text) => (
239
+ <CapTooltip title={displayNode}>
240
+ {text || displayNode}
241
+ </CapTooltip>
242
+ );
243
+
236
244
  renderDynamicTagFlow = () => {
237
245
  this.setState({showModal: true, visible: false});
238
246
  };
@@ -280,7 +288,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
280
288
  if (temp?.length) {
281
289
  const tagValue = (
282
290
  <CapTreeNode
283
- title={disabled ? <CapTooltip title={loyaltyAttrDisableText}>{val?.name}</CapTooltip> : val?.name}
291
+ title={disabled ? this.wrapTreeTitle(loyaltyAttrDisableText, val?.name) : this.wrapTreeTitle(val?.name)}
284
292
  tag={val}
285
293
  key={val?.incentiveSeriesId ? `${key}(${val?.incentiveSeriesId})` : `${key}`}
286
294
  disabled={disabled}
@@ -303,17 +311,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
303
311
  <CapTreeNode
304
312
  title={
305
313
  childDisabled ? (
306
- <CapTooltip
307
- title={
308
- key === CUSTOMER_BARCODE_TAG
309
- ? customerBarcodeDisableText
310
- : loyaltyAttrDisableText
311
- }
312
- >
313
- {val?.desc || val?.name}
314
- </CapTooltip>
314
+ this.wrapTreeTitle(key === CUSTOMER_BARCODE_TAG ? customerBarcodeDisableText : loyaltyAttrDisableText, val?.desc || val?.name)
315
315
  ) : (
316
- val?.desc || val?.name
316
+ this.wrapTreeTitle(val?.desc || val?.name)
317
317
  )
318
318
  }
319
319
  tag={val}
@@ -339,17 +339,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
339
339
  <CapTreeNode
340
340
  title={
341
341
  childDisabled ? (
342
- <CapTooltip
343
- title={
344
- key === CUSTOMER_BARCODE_TAG
345
- ? customerBarcodeDisableText
346
- : loyaltyAttrDisableText
347
- }
348
- >
349
- {val?.desc || val?.name}
350
- </CapTooltip>
342
+ this.wrapTreeTitle(key === CUSTOMER_BARCODE_TAG ? customerBarcodeDisableText : loyaltyAttrDisableText, val?.desc || val?.name)
351
343
  ) : (
352
- val?.desc || val?.name
344
+ this.wrapTreeTitle(val?.desc || val?.name)
353
345
  )
354
346
  }
355
347
  tag={val}
@@ -407,7 +399,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
407
399
  },
408
400
  ];
409
401
  const contentSection = (
410
- <CapRow>
402
+ <CapRow className="cap-tag-list-popover-inner">
411
403
  <CapSpin tip={formatMessage(messages.gettingTags)} spinning={shouldShowLoading}>
412
404
  <Search
413
405
  style={{ marginBottom: 8, width: '250px'}}
@@ -1,5 +1,53 @@
1
1
  @import "~@capillarytech/cap-ui-library/styles/_variables";
2
2
 
3
+ // Tag list popover: keep overlay width aligned with search (250px); tree rows ellipsis + tooltip for full text
4
+ .cap-tag-list-popover-overlay.ant-popover {
5
+ .ant-popover-inner-content {
6
+ max-width: 20rem;
7
+ box-sizing: border-box;
8
+ overflow: hidden;
9
+ }
10
+ }
11
+
12
+ .cap-tag-list-popover-inner {
13
+ max-width: 20rem;
14
+ min-width: 0;
15
+ box-sizing: border-box;
16
+
17
+ .ant-tree.cap-tree-v2.ant-tree-icon-hide {
18
+ width: 100%;
19
+ max-width: 100%;
20
+
21
+ ul {
22
+ max-width: 100%;
23
+ }
24
+
25
+ li {
26
+ overflow: hidden;
27
+ max-width: 100%;
28
+ }
29
+
30
+ li .ant-tree-node-content-wrapper {
31
+ width: calc(100% - 3.5rem); // leave room for switcher (~24px)
32
+ max-width: calc(100% - 3.5rem);
33
+ overflow: hidden;
34
+ vertical-align: top;
35
+ box-sizing: border-box;
36
+ }
37
+
38
+ .cap-tag-list-tree-title-wrap {
39
+ display: inline-block;
40
+ width: 100%;
41
+ overflow: hidden;
42
+ text-overflow: ellipsis;
43
+ white-space: nowrap;
44
+ max-width: 100%;
45
+ vertical-align: top;
46
+ margin-top: 0.5rem;
47
+ }
48
+ }
49
+ }
50
+
3
51
  @media (max-height: 25rem) {
4
52
  .ant-tree.cap-tree-v2.ant-tree-icon-hide {
5
53
  height: 8.5714rem;
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { IntlProvider } from 'react-intl';
5
+ import CapTagListWithInput from '../index';
6
+
7
+ const capturedTagListProps = { current: null };
8
+
9
+ jest.mock('../../../v2Containers/TagList', () => {
10
+ const React = require('react');
11
+ const Mock = (props) => {
12
+ capturedTagListProps.current = props;
13
+ return <div data-testid="mock-tag-list">TagList</div>;
14
+ };
15
+ return Mock;
16
+ });
17
+
18
+ jest.mock('@capillarytech/cap-ui-library/CapRow', () => ({ children }) => <div>{children}</div>);
19
+ jest.mock('@capillarytech/cap-ui-library/CapColumn', () => ({ children }) => <div>{children}</div>);
20
+ jest.mock('@capillarytech/cap-ui-library/CapHeading', () => () => null);
21
+ jest.mock('@capillarytech/cap-ui-library/CapInput', () => () => <input data-testid="cap-input" />);
22
+
23
+ const waitMap = {
24
+ b1: { eventName: 'Order Placed', blockName: 'Wait', tags: [] },
25
+ };
26
+
27
+ describe('CapTagListWithInput', () => {
28
+ beforeEach(() => {
29
+ capturedTagListProps.current = null;
30
+ });
31
+
32
+ it('forwards waitEventContextTags to TagList', () => {
33
+ render(
34
+ <IntlProvider locale="en" messages={{}}>
35
+ <CapTagListWithInput
36
+ inputId="test-url"
37
+ inputOnChange={jest.fn()}
38
+ waitEventContextTags={waitMap}
39
+ onTagSelect={jest.fn()}
40
+ onContextChange={jest.fn()}
41
+ />
42
+ </IntlProvider>
43
+ );
44
+
45
+ expect(screen.getByTestId('mock-tag-list')).toBeInTheDocument();
46
+ expect(capturedTagListProps.current.waitEventContextTags).toEqual(waitMap);
47
+ });
48
+
49
+ it('uses default empty object for waitEventContextTags when omitted', () => {
50
+ render(
51
+ <IntlProvider locale="en" messages={{}}>
52
+ <CapTagListWithInput
53
+ inputId="test-url"
54
+ inputOnChange={jest.fn()}
55
+ onTagSelect={jest.fn()}
56
+ onContextChange={jest.fn()}
57
+ />
58
+ </IntlProvider>
59
+ );
60
+
61
+ expect(capturedTagListProps.current.waitEventContextTags).toEqual({});
62
+ });
63
+ });
@@ -27,6 +27,7 @@ export const CapTagListWithInput = (props) => {
27
27
  userLocale = 'en',
28
28
  eventContextTags = [],
29
29
  restrictPersonalization = false,
30
+ waitEventContextTags = {},
30
31
  // CapInput props
31
32
  inputId,
32
33
  inputValue = '',
@@ -77,6 +78,7 @@ export const CapTagListWithInput = (props) => {
77
78
  userLocale={userLocale}
78
79
  selectedOfferDetails={selectedOfferDetails}
79
80
  eventContextTags={eventContextTags}
81
+ waitEventContextTags={waitEventContextTags}
80
82
  style={tagListStyle}
81
83
  popoverPlacement={popoverPlacement}
82
84
  restrictPersonalization={restrictPersonalization}
@@ -116,6 +118,7 @@ CapTagListWithInput.propTypes = {
116
118
  userLocale: PropTypes.string,
117
119
  eventContextTags: PropTypes.array,
118
120
  restrictPersonalization: PropTypes.bool,
121
+ waitEventContextTags: PropTypes.object,
119
122
 
120
123
  // CapInput props
121
124
  inputId: PropTypes.string.isRequired,
@@ -154,6 +157,7 @@ CapTagListWithInput.defaultProps = {
154
157
  userLocale: 'en',
155
158
  eventContextTags: [],
156
159
  restrictPersonalization: false,
160
+ waitEventContextTags: {},
157
161
  inputValue: '',
158
162
  inputSize: 'default',
159
163
  inputRequired: false,
@@ -52,6 +52,7 @@ export const CapWhatsappCTA = (props) => {
52
52
  injectedTags = {},
53
53
  selectedOfferDetails = [],
54
54
  eventContextTags = [],
55
+ waitEventContextTags = {},
55
56
  } = props;
56
57
  const { formatMessage } = intl;
57
58
  const invalidVarRegex = /{{(.*?)}}/g;
@@ -283,6 +284,7 @@ export const CapWhatsappCTA = (props) => {
283
284
  injectedTags={injectedTags}
284
285
  selectedOfferDetails={selectedOfferDetails}
285
286
  eventContextTags={eventContextTags}
287
+ waitEventContextTags={waitEventContextTags}
286
288
  />
287
289
  </CapColumn>
288
290
  )}
@@ -2990,6 +2990,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2990
2990
  selectedOfferDetails={this.props.selectedOfferDetails}
2991
2991
  eventContextTags={this.props?.eventContextTags}
2992
2992
  restrictPersonalization={this.props.restrictPersonalization}
2993
+ waitEventContextTags={this.props?.waitEventContextTags}
2993
2994
  />
2994
2995
  </CapColumn>
2995
2996
  );
@@ -3019,6 +3020,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3019
3020
  userLocale={this.props.userLocale}
3020
3021
  selectedOfferDetails={this.props.selectedOfferDetails}
3021
3022
  eventContextTags={this.props?.eventContextTags}
3023
+ waitEventContextTags={this.props?.waitEventContextTags}
3022
3024
  moduleFilterEnabled={this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded'}
3023
3025
  containerStyle={val.style || {}}
3024
3026
  inputProps={val.inputProps || {}}
@@ -3658,6 +3660,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3658
3660
  channel={channel}
3659
3661
  eventContextTags={this.props?.eventContextTags}
3660
3662
  restrictPersonalization={this.props.restrictPersonalization}
3663
+ waitEventContextTags={this.props?.waitEventContextTags}
3661
3664
  getPopupContainer={this.props.tagListGetPopupContainer}
3662
3665
  popoverOverlayStyle={this.props.tagListPopoverOverlayStyle}
3663
3666
  popoverOverlayClassName={this.props.tagListPopoverOverlayClassName}
@@ -3707,6 +3710,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3707
3710
  userLocale={this.state.translationLang}
3708
3711
  selectedOfferDetails={this.props.selectedOfferDetails}
3709
3712
  eventContextTags={this.props?.eventContextTags}
3713
+ waitEventContextTags={this.props?.waitEventContextTags}
3710
3714
  moduleFilterEnabled={moduleFilterEnabledForCapTagList}
3711
3715
  containerStyle={val.style || {}}
3712
3716
  inputProps={val.inputProps || {}}
@@ -4001,6 +4005,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
4001
4005
  onContextChange={this.props.onContextChange}
4002
4006
  moduleFilterEnabled={isModuleFilterEnabled}
4003
4007
  eventContextTags={this.props?.eventContextTags}
4008
+ waitEventContextTags={this.props?.waitEventContextTags}
4004
4009
  />
4005
4010
  </CapColumn>
4006
4011
  );
@@ -4303,6 +4308,7 @@ FormBuilder.defaultProps = {
4303
4308
  userLocale: localStorage.getItem('jlocale') || 'en',
4304
4309
  showLiquidErrorInFooter: () => {},
4305
4310
  metaDataStatus: "",
4311
+ waitEventContextTags: {},
4306
4312
  isTestAndPreviewMode: false, // Default to false to maintain existing behavior
4307
4313
  };
4308
4314
 
@@ -4353,6 +4359,7 @@ FormBuilder.propTypes = {
4353
4359
  moduleType: PropTypes.string.isRequired,
4354
4360
  showLiquidErrorInFooter: PropTypes.func.isRequired,
4355
4361
  eventContextTags: PropTypes.array.isRequired,
4362
+ waitEventContextTags: PropTypes.object,
4356
4363
  forwardedTags: PropTypes.object.isRequired,
4357
4364
  isLoyaltyModule: PropTypes.bool.isRequired,
4358
4365
  isTestAndPreviewMode: PropTypes.bool, // Add new prop type
@@ -94,6 +94,7 @@ const HTMLEditor = forwardRef(({
94
94
  injectedTags = {},
95
95
  location,
96
96
  eventContextTags = [],
97
+ waitEventContextTags,
97
98
  selectedOfferDetails = [],
98
99
  channel,
99
100
  userLocale = 'en',
@@ -361,7 +362,7 @@ const HTMLEditor = forwardRef(({
361
362
  const issueCounts = calculateIssueCounts();
362
363
  const isContentEmpty = !currentContent || currentContent.trim() === '';
363
364
 
364
- // hasErrors = only Rule Group #1 (Input & Sanitization) gates Done/Update/Preview/Test
365
+ // hasErrors = only Rule Group #1 (Input & Sanitization) - gates Done/Update/Preview/Test
365
366
  const newState = {
366
367
  isContentEmpty,
367
368
  issueCounts,
@@ -663,6 +664,7 @@ const HTMLEditor = forwardRef(({
663
664
  injectedTags={injectedTags}
664
665
  location={location}
665
666
  eventContextTags={eventContextTags}
667
+ waitEventContextTags={waitEventContextTags}
666
668
  selectedOfferDetails={selectedOfferDetails}
667
669
  channel={channel}
668
670
  userLocale={userLocale}
@@ -734,6 +736,7 @@ const HTMLEditor = forwardRef(({
734
736
  injectedTags={injectedTags}
735
737
  location={location}
736
738
  eventContextTags={eventContextTags}
739
+ waitEventContextTags={waitEventContextTags}
737
740
  selectedOfferDetails={selectedOfferDetails}
738
741
  channel={channel}
739
742
  userLocale={userLocale}
@@ -772,6 +775,7 @@ HTMLEditor.propTypes = {
772
775
  injectedTags: PropTypes.object,
773
776
  location: PropTypes.object,
774
777
  eventContextTags: PropTypes.array,
778
+ waitEventContextTags: PropTypes.object,
775
779
  selectedOfferDetails: PropTypes.array,
776
780
  channel: PropTypes.string,
777
781
  userLocale: PropTypes.string,
@@ -805,6 +809,7 @@ HTMLEditor.defaultProps = {
805
809
  injectedTags: {},
806
810
  location: null,
807
811
  eventContextTags: [],
812
+ waitEventContextTags: {},
808
813
  selectedOfferDetails: [],
809
814
  channel: null,
810
815
  userLocale: 'en',
@@ -225,6 +225,7 @@ const defaultProps = {
225
225
  injectedTags: {},
226
226
  location: { query: {} },
227
227
  eventContextTags: [],
228
+ waitEventContextTags: {},
228
229
  selectedOfferDetails: [],
229
230
  channel: 'EMAIL',
230
231
  userLocale: 'en',