@capillarytech/creatives-library 8.0.329 → 8.0.330-alpha.1

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 (89) hide show
  1. package/constants/unified.js +4 -0
  2. package/package.json +1 -1
  3. package/utils/commonUtils.js +19 -1
  4. package/utils/templateVarUtils.js +35 -6
  5. package/utils/tests/tagValidations.test.js +20 -0
  6. package/utils/tests/templateVarUtils.test.js +44 -0
  7. package/v2Components/CapActionButton/constants.js +7 -0
  8. package/v2Components/CapActionButton/index.js +167 -109
  9. package/v2Components/CapActionButton/index.scss +157 -6
  10. package/v2Components/CapActionButton/messages.js +19 -3
  11. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  12. package/v2Components/CapTagList/index.js +28 -23
  13. package/v2Components/CapTagList/style.scss +29 -0
  14. package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  15. package/v2Components/CapTagListWithInput/index.js +4 -0
  16. package/v2Components/CapWhatsappCTA/index.js +2 -0
  17. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +1 -0
  18. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
  19. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
  20. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +323 -77
  21. package/v2Components/CommonTestAndPreview/index.js +49 -57
  22. package/v2Components/CommonTestAndPreview/messages.js +8 -0
  23. package/v2Components/CommonTestAndPreview/reducer.js +3 -1
  24. package/v2Components/CommonTestAndPreview/sagas.js +2 -1
  25. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  26. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  27. package/v2Components/FormBuilder/index.js +1 -0
  28. package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
  29. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  30. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
  31. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
  32. package/v2Components/SmsFallback/smsFallbackUtils.js +14 -3
  33. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +16 -0
  34. package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
  35. package/v2Components/TemplatePreview/constants.js +2 -0
  36. package/v2Components/TemplatePreview/index.js +143 -28
  37. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  38. package/v2Components/TestAndPreviewSlidebox/index.js +5 -0
  39. package/v2Components/mockdata.js +1 -0
  40. package/v2Containers/BeeEditor/index.js +19 -1
  41. package/v2Containers/CreativesContainer/SlideBoxContent.js +28 -1
  42. package/v2Containers/CreativesContainer/index.js +9 -3
  43. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +5 -0
  44. package/v2Containers/Email/index.js +78 -39
  45. package/v2Containers/Email/reducer.js +2 -2
  46. package/v2Containers/Email/sagas.js +3 -1
  47. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -2
  48. package/v2Containers/Email/tests/sagas.test.js +230 -0
  49. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +6 -1
  50. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  51. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
  52. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  53. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -1
  54. package/v2Containers/EmailWrapper/index.js +4 -0
  55. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  56. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
  57. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +1 -0
  58. package/v2Containers/MobilePush/Create/index.js +2 -0
  59. package/v2Containers/MobilePush/Edit/index.js +2 -0
  60. package/v2Containers/MobilepushWrapper/index.js +3 -1
  61. package/v2Containers/Rcs/constants.js +85 -7
  62. package/v2Containers/Rcs/index.js +1592 -156
  63. package/v2Containers/Rcs/index.js.rej +1336 -0
  64. package/v2Containers/Rcs/index.scss +191 -0
  65. package/v2Containers/Rcs/index.scss.rej +74 -0
  66. package/v2Containers/Rcs/messages.js +28 -2
  67. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +20 -0
  68. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +69178 -117691
  69. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
  70. package/v2Containers/Rcs/tests/index.test.js +132 -94
  71. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +67 -0
  72. package/v2Containers/Rcs/tests/utils.test.js +276 -38
  73. package/v2Containers/Rcs/utils.js +130 -7
  74. package/v2Containers/Sms/Edit/index.js +2 -0
  75. package/v2Containers/SmsTrai/Edit/index.js +27 -0
  76. package/v2Containers/SmsTrai/Edit/messages.js +5 -0
  77. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  78. package/v2Containers/SmsWrapper/index.js +2 -0
  79. package/v2Containers/TagList/index.js +73 -20
  80. package/v2Containers/TagList/messages.js +4 -0
  81. package/v2Containers/TagList/tests/TagList.test.js +124 -20
  82. package/v2Containers/TagList/tests/mockdata.js +17 -0
  83. package/v2Containers/Templates/_templates.scss +99 -0
  84. package/v2Containers/Templates/index.js +29 -14
  85. package/v2Containers/Viber/index.js +3 -0
  86. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
  87. package/v2Containers/WebPush/Create/index.js +10 -2
  88. package/v2Containers/Whatsapp/index.js +5 -0
  89. package/v2Containers/Zalo/index.js +2 -0
@@ -1102,6 +1102,33 @@ export const SmsTraiEdit = (props) => {
1102
1102
  </CapLabelInline>
1103
1103
  </TraiEditTemplateDetails>
1104
1104
  )}
1105
+ {isRcsSmsFallback &&
1106
+ traiDataRef.current &&
1107
+ !isEmpty(traiDataRef.current) &&
1108
+ !loading && (
1109
+ <TraiEditTemplateDetails className="sms-trai-edit-rcs-fallback__template-meta">
1110
+ <CapLabelInline type="label1">
1111
+ {formatMessage(messages.templateNameLabel)}
1112
+ </CapLabelInline>
1113
+ <CapLabelInline type="label2">
1114
+ {get(traiDataRef.current, 'versions.base.template_name', '')
1115
+ || get(traiDataRef.current, 'name', '')}
1116
+ </CapLabelInline>
1117
+ {traiDltEnabled ? (
1118
+ <>
1119
+ <CapLabelInline type="label1">
1120
+ {formatMessage(messages.traiEditSeperator)}
1121
+ </CapLabelInline>
1122
+ <CapLabelInline type="label1">
1123
+ {formatMessage(messages.senderIdlabel)}
1124
+ </CapLabelInline>
1125
+ <CapLabelInline type="label2">
1126
+ {[...get(traiDataRef.current, 'versions.base.header', [])].join(', ')}
1127
+ </CapLabelInline>
1128
+ </>
1129
+ ) : null}
1130
+ </TraiEditTemplateDetails>
1131
+ )}
1105
1132
  <CapColumn span={shouldShowPreview ? 14 : 24}>
1106
1133
  <CapRow>
1107
1134
  <CapHeader
@@ -48,6 +48,11 @@ export default defineMessages({
48
48
  id: `${prefix}.templateLabel`,
49
49
  defaultMessage: 'Template',
50
50
  },
51
+ /** RCS → SMS fallback slidebox: label above selected template metadata. */
52
+ templateNameLabel: {
53
+ id: `${prefix}.templateNameLabel`,
54
+ defaultMessage: 'Template name',
55
+ },
51
56
  traiEditSeperator: {
52
57
  id: `${prefix}.traiEditSeperator`,
53
58
  defaultMessage: '|',
@@ -3292,7 +3292,7 @@ FREE GIFTS-
3292
3292
  </div>
3293
3293
  </Edit__TraiEditTemplateDetails>
3294
3294
  <CapColumn
3295
- key=".1"
3295
+ key=".2"
3296
3296
  span={14}
3297
3297
  >
3298
3298
  <Col
@@ -4552,7 +4552,7 @@ FREE GIFTS-
4552
4552
  </Col>
4553
4553
  </CapColumn>
4554
4554
  <CapColumn
4555
- key=".2"
4555
+ key=".3"
4556
4556
  offset={1}
4557
4557
  span={8}
4558
4558
  >
@@ -14962,7 +14962,7 @@ FREE GIFTS-
14962
14962
  </div>
14963
14963
  </Edit__TraiEditTemplateDetails>
14964
14964
  <CapColumn
14965
- key=".1"
14965
+ key=".2"
14966
14966
  span={14}
14967
14967
  >
14968
14968
  <Col
@@ -16222,7 +16222,7 @@ FREE GIFTS-
16222
16222
  </Col>
16223
16223
  </CapColumn>
16224
16224
  <CapColumn
16225
- key=".2"
16225
+ key=".3"
16226
16226
  offset={1}
16227
16227
  span={8}
16228
16228
  >
@@ -26632,7 +26632,7 @@ FREE GIFTS-
26632
26632
  </div>
26633
26633
  </Edit__TraiEditTemplateDetails>
26634
26634
  <CapColumn
26635
- key=".1"
26635
+ key=".2"
26636
26636
  span={14}
26637
26637
  >
26638
26638
  <Col
@@ -27892,7 +27892,7 @@ FREE GIFTS-
27892
27892
  </Col>
27893
27893
  </CapColumn>
27894
27894
  <CapColumn
27895
- key=".2"
27895
+ key=".3"
27896
27896
  offset={1}
27897
27897
  span={8}
27898
27898
  >
@@ -32,6 +32,7 @@ const SmsWrapper = (props) => {
32
32
  smsRegister,
33
33
  onShowTemplates,
34
34
  eventContextTags,
35
+ waitEventContextTags,
35
36
  showLiquidErrorInFooter,
36
37
  getLiquidTags,
37
38
  showTestAndPreviewSlidebox,
@@ -73,6 +74,7 @@ const SmsWrapper = (props) => {
73
74
  onPreviewContentClicked,
74
75
  onTestContentClicked,
75
76
  eventContextTags,
77
+ waitEventContextTags,
76
78
  showLiquidErrorInFooter,
77
79
  getLiquidTags,
78
80
  showTestAndPreviewSlidebox,
@@ -22,7 +22,7 @@ import messages, { scope } from './messages';
22
22
  // import styled from styled-components;
23
23
  import CapTagList from '../../v2Components/CapTagList';
24
24
  import './_tagList.scss';
25
- import { selectCurrentOrgDetails, makeSelectFetchingSchemaError } from '../Cap/selectors';
25
+ import { selectCurrentOrgDetails, makeSelectFetchingSchemaError, makeSelectFetchingSchema } from '../Cap/selectors';
26
26
  import {
27
27
  handleInjectedData, hasGiftVoucherFeature, hasPromoFeature, hasBadgesFeature, transformBadgeTags,
28
28
  } from '../../utils/common';
@@ -35,12 +35,14 @@ const {TreeNode} = Tree;
35
35
  export class TagList extends React.Component { // eslint-disable-line react/prefer-stateless-function
36
36
  constructor(props) {
37
37
  super(props);
38
+ const { tags, injectedTags } = props;
39
+ const hasInitialData = (tags && tags.length > 0) || !_.isEmpty(injectedTags);
38
40
  this.state = {
39
41
  loading: false,
40
42
  tags: [],
41
43
  tagsError: false,
42
44
  currentContext: null, // Track current context to detect changes
43
- hasTriggeredInitialApiCall: false, // Track if we've triggered API call when popover opens
45
+ hasTriggeredInitialApiCall: hasInitialData, // Seed from initial props to avoid duplicate fetch on popover open
44
46
  };
45
47
  this.renderTags = this.renderTags.bind(this);
46
48
  this.populateTags = this.populateTags.bind(this);
@@ -52,14 +54,8 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
52
54
 
53
55
  componentDidMount() {
54
56
  this.generateTags(this.props);
55
- // Trigger initial API call if tags are empty (similar to Email/SMS behavior)
56
- const { tags, injectedTags, onContextChange } = this.props;
57
- const hasNoTags = (!tags || tags.length === 0) && _.isEmpty(injectedTags);
58
- if (hasNoTags && onContextChange) {
59
- // Trigger API call with default 'Outbound' context to match CapTagList default
60
- // This ensures tags are loaded when component mounts
61
- this.getTagsforContext('Outbound');
62
- }
57
+ // Initial schema fetch is the parent's responsibility (useTagManagement hook handles it)
58
+ // This avoids duplicate requests when both parent and child try to fetch on mount
63
59
  }
64
60
 
65
61
  componentWillReceiveProps(nextProps) {
@@ -85,9 +81,10 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
85
81
  if (!_.isEqual(nextTags, currentTags)) {
86
82
  this.setState({loading: false});
87
83
  this.clearLoadingTimeout();
88
- // Reset the flag when tags are received, so we can trigger API call again if needed
84
+ // Tags received (from prefetch or button-click API call) mark as triggered
85
+ // so handlePopoverVisibilityChange won't fire a duplicate call on popover open
89
86
  if (nextTags && nextTags.length > 0) {
90
- this.setState({ hasTriggeredInitialApiCall: false });
87
+ this.setState({ hasTriggeredInitialApiCall: true });
91
88
  }
92
89
  }
93
90
  if (fetchingSchemaError) {
@@ -97,10 +94,28 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
97
94
  }
98
95
 
99
96
  componentDidUpdate(prevProps) {
100
- const { tags, injectedTags, selectedOfferDetails } = this.props;
101
- const { tags: prevTags, injectedTags: prevInjectedTags, selectedOfferDetails: prevSelectedOfferDetails } = prevProps;
102
-
103
- if (tags !== prevTags || injectedTags !== prevInjectedTags || selectedOfferDetails !== prevSelectedOfferDetails) {
97
+ const {
98
+ tags,
99
+ injectedTags,
100
+ selectedOfferDetails,
101
+ eventContextTags,
102
+ waitEventContextTags,
103
+ } = this.props;
104
+ const {
105
+ tags: prevTags,
106
+ injectedTags: prevInjectedTags,
107
+ selectedOfferDetails: prevSelectedOfferDetails,
108
+ eventContextTags: prevEventContextTags,
109
+ waitEventContextTags: prevWaitEventContextTags,
110
+ } = prevProps;
111
+
112
+ if (
113
+ tags !== prevTags
114
+ || injectedTags !== prevInjectedTags
115
+ || selectedOfferDetails !== prevSelectedOfferDetails
116
+ || !_.isEqual(eventContextTags, prevEventContextTags)
117
+ || !_.isEqual(waitEventContextTags, prevWaitEventContextTags)
118
+ ) {
104
119
  this.generateTags(this.props);
105
120
  }
106
121
  }
@@ -155,9 +170,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
155
170
  if ((hasNoTags || hasNoStateTags || hasNotTriggeredApiCall)) {
156
171
  // Mark that we've triggered the API call
157
172
  this.setState({ hasTriggeredInitialApiCall: true });
158
- // Trigger API call with default 'Outbound' context to match CapTagList default
159
- // This will call onContextChange which triggers handleOnTagsContextChange in InApp
160
- this.getTagsforContext('Outbound');
173
+ this.getTagsforContext('Outbound'); // Default to Outbound context
161
174
  }
162
175
  }
163
176
  };
@@ -167,7 +180,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
167
180
  let injectedTags = {};
168
181
  const eventContextTagsObj = {};
169
182
 
170
- const {selectedOfferDetails, eventContextTags } = props;
183
+ const {selectedOfferDetails, eventContextTags, waitEventContextTags } = props;
171
184
  if (props.injectedTags && !_.isEmpty(props.injectedTags)) {
172
185
  const formattedInjectedTags = handleInjectedData(
173
186
  props.injectedTags,
@@ -219,6 +232,43 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
219
232
  };
220
233
  });
221
234
  }
235
+ // Wait event context tags should be displayed in the Add Labels when node is next to Event based wait node.
236
+ if (waitEventContextTags && Object.keys(waitEventContextTags)?.length) {
237
+
238
+ Object.keys(waitEventContextTags).forEach((blockId) => {
239
+ const WAIT_EVENT_HEADER_MSG_LABEL = `${waitEventContextTags[blockId].eventName} (${waitEventContextTags[blockId].blockName})`;
240
+ eventContextTagsObj[blockId] = {
241
+ "name": WAIT_EVENT_HEADER_MSG_LABEL,
242
+ "desc": WAIT_EVENT_HEADER_MSG_LABEL,
243
+ "resolved": true,
244
+ 'tag-header': true,
245
+ "subtags": {},
246
+ };
247
+
248
+ waitEventContextTags?.[blockId]?.tags?.forEach((tag) => {
249
+ const {
250
+ tagName, label, profileId, profileName, blockName, eventName
251
+ } = tag || {};
252
+ if (!profileId || !tagName || !label || !profileName) return;
253
+ // Initializing the tags profile if it doesn't exist
254
+ if (!eventContextTagsObj?.[blockId]?.subtags?.[profileId]) {
255
+ eventContextTagsObj[blockId].subtags[profileId] = {
256
+ "name": profileName,
257
+ "desc": profileName,
258
+ "resolved": true,
259
+ 'tag-header': true,
260
+ "subtags": {},
261
+ };
262
+ }
263
+ // Adding the current tag to the profile group
264
+ eventContextTagsObj[blockId].subtags[profileId].subtags[tagName] = {
265
+ name: label,
266
+ desc: label,
267
+ resolved: true,
268
+ };
269
+ });
270
+ });
271
+ }
222
272
  this.setState({tags: _.merge( {}, tags, injectedTags, eventContextTagsObj )});
223
273
  }
224
274
 
@@ -440,6 +490,7 @@ TagList.defaultProps = {
440
490
  isNewVersionFlow: false,
441
491
  userLocale: 'en',
442
492
  eventContextTags: [],
493
+ waitEventContextTags: {},
443
494
  };
444
495
 
445
496
  TagList.propTypes = {
@@ -460,6 +511,7 @@ TagList.propTypes = {
460
511
  disabled: PropTypes.bool,
461
512
  fetchingSchemaError: PropTypes.bool,
462
513
  eventContextTags: PropTypes.array,
514
+ waitEventContextTags: PropTypes.object,
463
515
  popoverPlacement: PropTypes.string,
464
516
  // message to show when Add Label button is disabled (e.g. personalization restriction)
465
517
  disableTooltipMsg: PropTypes.string,
@@ -477,6 +529,7 @@ const mapStateToProps = createStructuredSelector({
477
529
  TagList: makeSelectTagList(),
478
530
  currentOrgDetails: selectCurrentOrgDetails(),
479
531
  fetchingSchemaError: makeSelectFetchingSchemaError(),
532
+ fetchingSchema: makeSelectFetchingSchema(),
480
533
  });
481
534
 
482
535
  function mapDispatchToProps(dispatch) {
@@ -19,4 +19,8 @@ export default defineMessages({
19
19
  id: `${scope}.personalizationNotSupportedAnonymous`,
20
20
  defaultMessage: 'Personalization tags are not supported for anonymous customers',
21
21
  },
22
+ waitEvent: {
23
+ id: `${scope}.waitEvent`,
24
+ defaultMessage: 'Wait Event',
25
+ },
22
26
  });
@@ -5,28 +5,34 @@ import { initialReducer } from '../../../initialReducer';
5
5
  import { injectIntl } from "react-intl";
6
6
  import { fireEvent } from "@testing-library/react";
7
7
  import { TagList } from '../index';
8
- import { TagListData, eventContextTags } from './mockdata';
8
+ import { TagListData, eventContextTags, waitEventContextTags } from './mockdata';
9
+ import { OfferTag, badgesTags, offer } from '../../../utils/tests/common.mockdata';
10
+ import * as commonUtils from "../../../utils/common";
9
11
  import { Provider } from 'react-redux';
10
12
  import { screen, render } from '../../../utils/test-utils';
11
13
  import history from '../../../utils/history';
12
14
  const { getByText, queryByText } = screen;
13
15
 
16
+ const buildProps = (props = {}) => ({
17
+ ...TagListData,
18
+ onTagSelect: jest.fn(),
19
+ ...props,
20
+ });
14
21
 
15
- const initializeTagList = (props) => {
22
+ const initializeTagList = (props = {}) => {
16
23
  const store = configureStore({}, initialReducer, history);
17
24
  const Component = injectIntl(TagList);
18
-
19
- const propsObj = {
20
- ...TagListData,
21
- onTagSelect: jest.fn(),
22
- ...props,
25
+ const propsObj = buildProps(props);
26
+ return {
27
+ ...render(
28
+ <Provider store={store}>
29
+ <Component {...propsObj} />
30
+ </Provider>
31
+ ),
32
+ store,
33
+ Component,
34
+ propsObj,
23
35
  };
24
-
25
- return render(
26
- <Provider store={store}>
27
- <Component {...propsObj} />
28
- </Provider>
29
- );
30
36
  };
31
37
 
32
38
  const addLabelBtnAssertion = () => {
@@ -41,19 +47,117 @@ describe("TagList test : UNIT", () => {
41
47
  addLabelBtnAssertion();
42
48
  });
43
49
 
44
- it('should render event context tags correctly from generateTags and show tags under profile', () => {
45
- initializeTagList({eventContextTags});
50
+ it('should render event context tag section from generateTags', () => {
51
+ initializeTagList({ eventContextTags, moduleFilterEnabled: false });
46
52
  addLabelBtnAssertion();
47
53
  const EVENT_CONTEXT_TAG_HEADER = getByText(/Entry event/i);
48
54
  expect(EVENT_CONTEXT_TAG_HEADER).toBeInTheDocument();
49
- fireEvent.click(EVENT_CONTEXT_TAG_HEADER);
50
- // Customer profile tags
51
- const CUSTOMER_PROFILE = getByText(/Current Customer/i);
52
- fireEvent.click(CUSTOMER_PROFILE);
53
- expect(getByText(/lifetimePurchases/i)).toBeInTheDocument();
54
55
 
55
56
  // Behavioural event profile tags should not be visible as label and profile name is not present
56
57
  const BEHAVIOURAL_EVENT_PROFILE = queryByText(/Behavioural event/i);
57
58
  expect(BEHAVIOURAL_EVENT_PROFILE).not.toBeInTheDocument();
58
59
  });
60
+
61
+ it('should render wait event context section when waitEventContextTags is provided', () => {
62
+ initializeTagList({ waitEventContextTags, moduleFilterEnabled: false });
63
+ addLabelBtnAssertion();
64
+ const WAIT_EVENT_HEADER = getByText(/Order Placed \(Wait Block\)/i);
65
+ expect(WAIT_EVENT_HEADER).toBeInTheDocument();
66
+ });
67
+
68
+ it('should merge empty waitEventContextTags with entry event tags', () => {
69
+ initializeTagList({ eventContextTags, waitEventContextTags: {}, moduleFilterEnabled: false });
70
+ addLabelBtnAssertion();
71
+ expect(getByText(/Entry event/i)).toBeInTheDocument();
72
+ });
73
+
74
+ it('calls parent onContextChange with Outbound when tags and injectedTags are empty on popover open', () => {
75
+ const onContextChange = jest.fn();
76
+ initializeTagList({
77
+ tags: [],
78
+ injectedTags: {},
79
+ onContextChange,
80
+ onTagSelect: jest.fn(),
81
+ });
82
+ // Default fetch is triggered when the Add Label popover opens (not on mount)
83
+ addLabelBtnAssertion();
84
+ expect(onContextChange).toHaveBeenCalledWith('Outbound');
85
+ });
86
+
87
+ it('applies fetchingSchemaError from props via componentWillReceiveProps', () => {
88
+ const { rerender, Component, propsObj, store } = initializeTagList({ fetchingSchemaError: false });
89
+ addLabelBtnAssertion();
90
+ rerender(
91
+ <Provider store={store}>
92
+ <Component {...propsObj} fetchingSchemaError />
93
+ </Provider>
94
+ );
95
+ expect(screen.getByText(/add label/i)).toBeInTheDocument();
96
+ });
97
+
98
+ it('disables Add label when restrictPersonalization is true', () => {
99
+ initializeTagList({
100
+ restrictPersonalization: true,
101
+ disabled: false,
102
+ moduleFilterEnabled: false,
103
+ });
104
+ const btn = screen.getByText(/add label/i).closest('button');
105
+ expect(btn).toBeDisabled();
106
+ });
107
+
108
+ it('calls transformCouponTags when selectedOfferDetails and coupon tags are present', () => {
109
+ const spy = jest.spyOn(TagList.prototype, 'transformCouponTags');
110
+ initializeTagList({
111
+ tags: OfferTag,
112
+ injectedTags: {},
113
+ selectedOfferDetails: [{ id: 'c1', couponName: 'Promo Coupon', couponSeriesId: 'c1' }],
114
+ moduleFilterEnabled: false,
115
+ });
116
+ expect(spy).toHaveBeenCalled();
117
+ spy.mockRestore();
118
+ });
119
+
120
+ it('calls transformBadgeTags from common when badge offer and Badge tags are present', () => {
121
+ const spy = jest.spyOn(commonUtils, 'transformBadgeTags');
122
+ initializeTagList({
123
+ tags: badgesTags,
124
+ injectedTags: {},
125
+ selectedOfferDetails: offer,
126
+ moduleFilterEnabled: false,
127
+ });
128
+ expect(spy).toHaveBeenCalled();
129
+ spy.mockRestore();
130
+ });
131
+
132
+ it('unmounts without throwing', () => {
133
+ const { unmount } = initializeTagList();
134
+ expect(() => unmount()).not.toThrow();
135
+ });
136
+
137
+ it('regenerates tags when props.tags change (componentDidUpdate)', () => {
138
+ const { rerender, Component, store } = initializeTagList({ tags: TagListData.tags });
139
+ const extra = [
140
+ ...TagListData.tags,
141
+ {
142
+ _id: 'extra-tag',
143
+ type: 'TAG',
144
+ definition: {
145
+ label: { en: 'Extra' },
146
+ value: 'extra_value',
147
+ subtags: [],
148
+ 'tag-header': false,
149
+ supportedModules: [{ context: 'default', layout: 'sms', mandatory: false }],
150
+ },
151
+ scope: { tag: 'STANDARD', orgId: -1, verticals: [] },
152
+ isActive: true,
153
+ },
154
+ ];
155
+ rerender(
156
+ <Provider store={store}>
157
+ <Component {...buildProps({ tags: extra })} />
158
+ </Provider>
159
+ );
160
+ addLabelBtnAssertion();
161
+ expect(screen.getByText(/add label/i)).toBeInTheDocument();
162
+ });
59
163
  });
@@ -149,3 +149,20 @@ export const eventContextTags = [
149
149
  "isDynamicFact": false
150
150
  }
151
151
  ];
152
+
153
+ export const waitEventContextTags = {
154
+ block1: {
155
+ eventName: 'Order Placed',
156
+ blockName: 'Wait Block',
157
+ tags: [
158
+ {
159
+ tagName: 'waitEvent.orderId',
160
+ label: 'Order ID',
161
+ profileId: 'ORDER_PROFILE',
162
+ profileName: 'Order Profile',
163
+ blockName: 'Wait Block',
164
+ eventName: 'Order Placed',
165
+ },
166
+ ],
167
+ },
168
+ };
@@ -2,10 +2,13 @@
2
2
 
3
3
  .ant-tabs-content{
4
4
  margin-top: 16px;
5
+ // .creatives-templates-list.full-mode{
6
+ .v2-pagination-container, .v2-pagination-container-half {
5
7
  .ant-tabs-tabpane-active{
6
8
  padding: unset;
7
9
  }
8
10
  }
11
+ }
9
12
 
10
13
  @media screen and (max-width: 1172px) {
11
14
  .creatives-templates-list.full-mode{
@@ -20,8 +23,11 @@
20
23
  }
21
24
  }
22
25
  }
26
+ // }
23
27
 
24
28
  @media screen and (min-width: 1172px) {
29
+ .creatives-templates-list.full-mode{
30
+ .v2-pagination-container, .v2-pagination-container-half {
25
31
  .creatives-templates-list.full-mode{
26
32
  .v2-pagination-container {
27
33
  .cap-custom-card-list-row {
@@ -33,16 +39,21 @@
33
39
  }
34
40
  }
35
41
  }
42
+ }
43
+ }
36
44
  }
37
45
 
46
+
38
47
  .creatives-templates-list {
39
48
 
40
49
  .delete-template-confirm {
41
50
  .ant-modal-footer {
42
51
  padding: $CAP_SPACE_16 $CAP_SPACE_24 $CAP_SPACE_24 $CAP_SPACE_24;
43
52
  }
53
+ }
44
54
 
45
55
  .ant-modal-header {
56
+ .v2-pagination-container, .v2-pagination-container-half {
46
57
  padding: $CAP_SPACE_24 $CAP_SPACE_24 $CAP_SPACE_08 $CAP_SPACE_24;
47
58
  }
48
59
 
@@ -181,10 +192,13 @@
181
192
  .whatsapp-container {
182
193
  background-color: $CAP_WHITE;
183
194
  padding: $CAP_SPACE_12;
195
+ overflow-y: hidden;
184
196
  }
185
197
  .scroll-container {
186
198
  overflow-x: auto;
187
199
  display: flex;
200
+ flex-wrap: nowrap;
201
+ width: 100%;
188
202
  padding-top: $CAP_SPACE_06;
189
203
  padding-right: $CAP_SPACE_06;
190
204
  white-space: nowrap;
@@ -218,6 +232,91 @@
218
232
  }
219
233
  }
220
234
 
235
+ // RCS template listing preview: match WhatsApp carousel "peek" behavior
236
+ .RCS {
237
+ .cap-custom-card {
238
+ .ant-card-body {
239
+ .ant-card-meta {
240
+ background-color: $CAP_G09;
241
+ padding: 0;
242
+ .ant-card-meta-description {
243
+ .whatsapp-container,.cap-rcs-creatives {
244
+ background-color: $CAP_WHITE;
245
+ padding: $CAP_SPACE_12;
246
+ border-radius: 0.25rem;
247
+ .cap-divider-v2{
248
+ margin: $CAP_SPACE_12 0;
249
+ }
250
+ }
251
+ .scroll-container {
252
+ overflow-x: auto;
253
+ display: flex;
254
+ padding-top: $CAP_SPACE_06;
255
+ padding-right: $CAP_SPACE_06;
256
+ white-space: nowrap;
257
+ scrollbar-width: none; // Hide scrollbar in Firefox
258
+ &::-webkit-scrollbar {
259
+ display: none; // Hide scrollbar in Chrome/Safari/Opera
260
+ }
261
+ overflow-y: hidden;
262
+ .whatsapp-carousel-container {
263
+ padding: $CAP_SPACE_04 0px $CAP_SPACE_08;
264
+ border-radius: $CAP_SPACE_06;
265
+ background-color: $CAP_WHITE;
266
+ width: 80%;
267
+ flex-shrink: 0;
268
+ margin-right: $CAP_SPACE_04;
269
+ white-space: pre-wrap;
270
+ word-break: break-word;
271
+ overflow: auto;
272
+ text-align: left;
273
+ .whatsapp-carousel-card {
274
+ margin: $CAP_SPACE_02 $CAP_SPACE_06 $CAP_SPACE_01 $CAP_SPACE_08;
275
+ .whatsapp-carousel-body {
276
+ margin-bottom: $CAP_SPACE_08;
277
+ white-space: pre-wrap;
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ // RCS CTA buttons in listing (reuse WhatsApp-ish look)
284
+ .rcs-cta-preview {
285
+ margin: $CAP_SPACE_12 0;
286
+ display: flex;
287
+ justify-content: center;
288
+ font-size: $FONT_SIZE_M;
289
+ align-items: center;
290
+ color: #1970DA;
291
+ svg {
292
+ margin-right: $CAP_SPACE_04;
293
+ }
294
+ }
295
+
296
+ .rcs-video-preview-placeholder {
297
+ background: #f5f5f5;
298
+ display: flex;
299
+ align-items: center;
300
+ justify-content: center;
301
+ }
302
+
303
+ .rcs-video-preview-label {
304
+ color: #7a7a7a;
305
+ }
306
+
307
+ .rcs-listing-title {
308
+ font-weight: 600;
309
+ }
310
+
311
+ .whatsapp-divider {
312
+ margin: 0;
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+
221
320
  .MOBILEPUSH {
222
321
  .ant-card-body {
223
322
  padding: 0;