@capillarytech/creatives-library 8.0.353-alpha.5 → 8.0.353-alpha.6

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 (136) hide show
  1. package/constants/unified.js +29 -0
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +35 -20
  4. package/utils/commonUtils.js +19 -1
  5. package/utils/rcsPayloadUtils.js +92 -0
  6. package/utils/templateVarUtils.js +201 -0
  7. package/utils/tests/rcsPayloadUtils.test.js +226 -0
  8. package/utils/tests/templateVarUtils.test.js +204 -0
  9. package/v2Components/CapActionButton/constants.js +7 -0
  10. package/v2Components/CapActionButton/index.js +166 -108
  11. package/v2Components/CapActionButton/index.scss +157 -6
  12. package/v2Components/CapActionButton/messages.js +19 -3
  13. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  14. package/v2Components/CapTagList/index.js +10 -0
  15. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +72 -49
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +213 -21
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  22. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  23. package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +0 -17
  24. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +157 -15
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +346 -146
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +138 -48
  27. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  28. package/v2Components/CommonTestAndPreview/constants.js +38 -4
  29. package/v2Components/CommonTestAndPreview/index.js +691 -235
  30. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  31. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  32. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  33. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  34. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  35. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  36. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  37. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  38. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  39. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +0 -159
  40. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  41. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -256
  42. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -2
  43. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -198
  44. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  45. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +36 -26
  46. package/v2Components/FormBuilder/index.js +11 -6
  47. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  48. package/v2Components/SmsFallback/constants.js +73 -0
  49. package/v2Components/SmsFallback/index.js +956 -0
  50. package/v2Components/SmsFallback/index.scss +265 -0
  51. package/v2Components/SmsFallback/messages.js +78 -0
  52. package/v2Components/SmsFallback/smsFallbackUtils.js +119 -0
  53. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  54. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  55. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  56. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +223 -0
  57. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  58. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  59. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  60. package/v2Components/TemplatePreview/_templatePreview.scss +38 -23
  61. package/v2Components/TemplatePreview/constants.js +2 -0
  62. package/v2Components/TemplatePreview/index.js +143 -31
  63. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  64. package/v2Components/TestAndPreviewSlidebox/index.js +15 -3
  65. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  66. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  67. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  68. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  69. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  70. package/v2Containers/App/constants.js +0 -3
  71. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  72. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  73. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  74. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  75. package/v2Containers/CreativesContainer/constants.js +9 -0
  76. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
  77. package/v2Containers/CreativesContainer/index.js +322 -103
  78. package/v2Containers/CreativesContainer/index.scss +51 -1
  79. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  80. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -34
  81. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  82. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  83. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  84. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  85. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  86. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  87. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  88. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  89. package/v2Containers/Rcs/constants.js +119 -10
  90. package/v2Containers/Rcs/index.js +2445 -813
  91. package/v2Containers/Rcs/index.scss +280 -8
  92. package/v2Containers/Rcs/messages.js +34 -3
  93. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  94. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
  95. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  96. package/v2Containers/Rcs/tests/index.test.js +152 -121
  97. package/v2Containers/Rcs/tests/mockData.js +38 -0
  98. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  99. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  100. package/v2Containers/Rcs/utils.js +478 -11
  101. package/v2Containers/Sms/Create/index.js +106 -40
  102. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  103. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  104. package/v2Containers/SmsTrai/Create/index.js +9 -4
  105. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  106. package/v2Containers/SmsTrai/Edit/index.js +640 -130
  107. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  108. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  109. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  110. package/v2Containers/SmsWrapper/index.js +37 -8
  111. package/v2Containers/TagList/index.js +6 -0
  112. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  113. package/v2Containers/Templates/_templates.scss +166 -9
  114. package/v2Containers/Templates/actions.js +11 -0
  115. package/v2Containers/Templates/constants.js +2 -0
  116. package/v2Containers/Templates/index.js +122 -120
  117. package/v2Containers/Templates/sagas.js +56 -12
  118. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  119. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1062 -1017
  120. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  121. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  122. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  123. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  124. package/v2Containers/TemplatesV2/index.js +86 -23
  125. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  126. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  127. package/v2Containers/WebPush/Create/index.js +8 -91
  128. package/v2Containers/WebPush/Create/index.scss +0 -7
  129. package/v2Containers/Whatsapp/index.js +3 -20
  130. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
  131. package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +0 -169
  132. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +0 -522
  133. package/v2Containers/App/tests/constants.test.js +0 -61
  134. package/v2Containers/Templates/tests/webpush.test.js +0 -375
  135. package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +0 -338
  136. package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +0 -325
@@ -12,7 +12,7 @@ export default defineMessages({
12
12
  },
13
13
  ctaQr: {
14
14
  id: `${prefix}.ctaQr`,
15
- defaultMessage: 'Quick Reply',
15
+ defaultMessage: 'Quick reply',
16
16
  },
17
17
  ctaPhoneNo: {
18
18
  id: `${prefix}.ctaPhoneNo`,
@@ -47,9 +47,17 @@ export default defineMessages({
47
47
  id: `${prefix}.templateMessageLength`,
48
48
  defaultMessage: 'Characters count: {currentLength}/{maxLength}',
49
49
  },
50
+ ctaFieldCharCountInline: {
51
+ id: `${prefix}.ctaFieldCharCountInline`,
52
+ defaultMessage: '{current}/{max}',
53
+ },
50
54
  ctaType: {
51
55
  id: `${prefix}.ctaType`,
52
- defaultMessage: 'Type of action',
56
+ defaultMessage: 'Button type',
57
+ },
58
+ ctaUrlRadio: {
59
+ id: `${prefix}.ctaUrlRadio`,
60
+ defaultMessage: 'URL',
53
61
  },
54
62
  templateButtonTextPlaceholder: {
55
63
  id: `${prefix}.templateButtonTextPlaceholder`,
@@ -145,7 +153,15 @@ export default defineMessages({
145
153
  },
146
154
  ctaWebsiteType: {
147
155
  id: `${prefix}.ctaWebsiteType`,
148
- defaultMessage: 'URL Type',
156
+ defaultMessage: 'URL type',
157
+ },
158
+ ctaUrlField: {
159
+ id: `${prefix}.ctaUrlField`,
160
+ defaultMessage: 'URL',
161
+ },
162
+ ctaEnterUrlPlaceholder: {
163
+ id: `${prefix}.ctaEnterUrlPlaceholder`,
164
+ defaultMessage: 'Enter URL',
149
165
  },
150
166
  ctaWebsiteTypeStatic: {
151
167
  id: `${prefix}.ctaWebsiteTypeStatic`,
@@ -4,7 +4,7 @@ import '@testing-library/jest-dom';
4
4
  import { render, screen, fireEvent } from '../../../utils/test-utils';
5
5
  import { CapActionButton } from '../index';
6
6
  import { BTN_MAX_LENGTH, PHONE_NUMBER_MAX_LENGTH, URL_MAX_LENGTH } from '../constants';
7
- import { RCS_BUTTON_TYPES } from '../../../v2Containers/Rcs/constants';
7
+ import { RCS_BUTTON_TYPES, HOST_ICS } from '../../../v2Containers/Rcs/constants';
8
8
 
9
9
  const updateHandler = jest.fn();
10
10
  const deleteHandler = jest.fn();
@@ -12,6 +12,7 @@ const initializeComponent = (
12
12
  data,
13
13
  isEditFlow = false,
14
14
  maxButtons = 3,
15
+ host = '',
15
16
  ) => {
16
17
  // Normalize legacy test data shape to match component props
17
18
  const normalizeType = (legacyType) => {
@@ -37,6 +38,7 @@ const initializeComponent = (
37
38
  isEditFlow={isEditFlow}
38
39
  maxButtons={maxButtons}
39
40
  isFullMode={true}
41
+ host={host}
40
42
  />,
41
43
  );
42
44
  };
@@ -59,7 +61,7 @@ describe('CapActionButton', () => {
59
61
  isSaved: false,
60
62
  },
61
63
  ]);
62
- expect(screen.getByText('Type of action')).toBeInTheDocument();
64
+ expect(screen.getByText('Button type')).toBeInTheDocument();
63
65
  expect(screen.getByText('Button text')).toBeInTheDocument();
64
66
  expect(screen.getAllByText('Phone number')[1]).toBeInTheDocument();
65
67
  expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
@@ -122,7 +124,7 @@ describe('CapActionButton', () => {
122
124
  id: 1,
123
125
  },
124
126
  };
125
- const urlInput = await screen.getByPlaceholderText('Enter website URL');
127
+ const urlInput = await screen.getByPlaceholderText('Enter URL');
126
128
  fireEvent.change(urlInput, urlEvent);
127
129
  expect(updateHandler).toHaveBeenCalledWith(
128
130
  {
@@ -162,6 +164,27 @@ describe('CapActionButton', () => {
162
164
  );
163
165
  });
164
166
 
167
+ it('should show delete for quick reply index 0 for non-ICS hosts but hide it for ICS (stop button)', () => {
168
+ const button = {
169
+ index: 0,
170
+ type: RCS_BUTTON_TYPES.QUICK_REPLY,
171
+ text: 'Reply',
172
+ phoneNumber: '',
173
+ url: '',
174
+ postback: 'Reply',
175
+ isSaved: false,
176
+ };
177
+
178
+ // Non-ICS (Infobip/others): delete should be available
179
+ const { unmount } = initializeComponent([button], false, 3, 'rcsinfobipbulk');
180
+ expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument();
181
+ unmount();
182
+
183
+ // ICS: index 0 is reserved; delete should be hidden
184
+ initializeComponent([button], false, 3, HOST_ICS);
185
+ expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument();
186
+ });
187
+
165
188
  it('should respect character length limits', async () => {
166
189
  const initialData = {
167
190
  index: 0,
@@ -198,7 +221,7 @@ describe('CapActionButton', () => {
198
221
  isSaved: false,
199
222
  };
200
223
  initializeComponent([initialData]);
201
- const urlInput = await screen.getByPlaceholderText('Enter website URL');
224
+ const urlInput = await screen.getByPlaceholderText('Enter URL');
202
225
  const longUrl = 'https://example.com/' + 'a'.repeat(URL_MAX_LENGTH + 1);
203
226
  fireEvent.change(urlInput, { target: { value: longUrl, id: 0 } });
204
227
  expect(urlInput.value.length).toBeLessThanOrEqual(URL_MAX_LENGTH);
@@ -245,7 +268,8 @@ describe('CapActionButton', () => {
245
268
  { index: 0, ctaType: RCS_BUTTON_TYPES.QUICK_REPLY, displayText: 'stop', phoneNumber: '', url: '', postback: 'stop', isSaved: true },
246
269
  { index: 1, ctaType: RCS_BUTTON_TYPES.QUICK_REPLY, displayText: 'Saved', phoneNumber: '', url: '', postback: 'Saved', isSaved: true },
247
270
  ];
248
- const { container } = initializeComponent(suggestions);
271
+ // Use ICS host so index 0 (STOP) stays non-deletable and we only get delete icon for index 1
272
+ const { container } = initializeComponent(suggestions, false, 3, HOST_ICS);
249
273
  const deleteIcons = container.querySelectorAll('.rcs-saved-cta-delete-icon');
250
274
  expect(deleteIcons.length).toBeGreaterThan(0);
251
275
  fireEvent.click(deleteIcons[0]);
@@ -303,7 +327,8 @@ describe('CapActionButton', () => {
303
327
  postback: 'stop',
304
328
  isSaved: true,
305
329
  };
306
- const { container } = initializeComponent([stopButton]);
330
+ // Only ICS treats index 0 as a mandatory STOP quick reply
331
+ const { container } = initializeComponent([stopButton], false, 3, HOST_ICS);
307
332
  expect(container.querySelector('.rcs-saved-cta-delete-icon')).not.toBeInTheDocument();
308
333
  });
309
334
 
@@ -424,7 +449,7 @@ describe('CapActionButton', () => {
424
449
  isSaved: false,
425
450
  };
426
451
  initializeComponent([button]);
427
- expect(screen.getByPlaceholderText(/enter website url/i)).toBeInTheDocument();
452
+ expect(screen.getByPlaceholderText(/enter url/i)).toBeInTheDocument();
428
453
  });
429
454
 
430
455
  it('should update both displayText and postback when button text changes (updateDisplayAndPostback)', () => {
@@ -504,7 +529,7 @@ describe('CapActionButton', () => {
504
529
  isSaved: false,
505
530
  };
506
531
  initializeComponent([initial]);
507
- const urlInput = screen.getByPlaceholderText('Enter website URL');
532
+ const urlInput = screen.getByPlaceholderText('Enter URL');
508
533
  fireEvent.change(urlInput, { target: { value: 'http://localhost:3030/creatives/ui/v2', id: 1 } });
509
534
  expect(screen.getByText(/url is not valid/i)).toBeInTheDocument();
510
535
  });
@@ -590,7 +615,7 @@ describe('CapActionButton', () => {
590
615
  isSaved: false,
591
616
  };
592
617
  initializeComponent([initial]);
593
- const urlInput = screen.getByPlaceholderText('Enter website URL');
618
+ const urlInput = screen.getByPlaceholderText('Enter URL');
594
619
  // Enter invalid URL
595
620
  fireEvent.change(urlInput, { target: { value: 'badurl', id: 0 } });
596
621
  expect(screen.getByText(/url is not valid/i)).toBeInTheDocument();
@@ -849,15 +874,14 @@ describe('CapActionButton function logic', () => {
849
874
  expect(renderCtaOptions(label, tooltipLabel, false)).toBe('Test');
850
875
  });
851
876
 
852
- it('renderLength returns correct CapHeading', () => {
853
- const CapHeading = ({ type, className, children }) => <div className={className}>{children}</div>;
854
- const formatMessage = (msg, values) => `${values.currentLength}/${values.maxLength}`;
855
- const renderLength = (len, max) => (
856
- <CapHeading type="label1" className="rcs-render-btn-length">
857
- {formatMessage({}, { currentLength: len, maxLength: max })}
858
- </CapHeading>
877
+ it('inner char count uses current/max format', () => {
878
+ const formatMessage = (msg, values) => `${values.current}/${values.max}`;
879
+ const renderInnerCharCount = (len, max) => (
880
+ <span className="rcs-cta-inner-char-count">
881
+ {formatMessage({}, { current: len, max })}
882
+ </span>
859
883
  );
860
- const result = renderLength(5, 25);
884
+ const result = renderInnerCharCount(5, 25);
861
885
  expect(result.props.children).toBe('5/25');
862
886
  });
863
887
  });
@@ -386,6 +386,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
386
386
  render() {
387
387
  const {
388
388
  hidePopover = false, intl = {}, moduleFilterEnabled, label, modalProps, channel, fetchingSchemaError = false,
389
+ overlayStyle,
390
+ overlayClassName,
391
+ getPopupContainer,
389
392
  } = this.props;
390
393
  const {formatMessage} = intl;
391
394
  const {
@@ -478,6 +481,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
478
481
  content={contentSection}
479
482
  trigger="click"
480
483
  placement={this.props.popoverPlacement || (channel === EMAIL.toUpperCase() ? "leftTop" : "rightTop")}
484
+ overlayStyle={overlayStyle}
485
+ overlayClassName={overlayClassName}
486
+ getPopupContainer={getPopupContainer}
481
487
  >
482
488
  <CapTooltip
483
489
  title={
@@ -549,6 +555,10 @@ CapTagList.propTypes = {
549
555
  disableTooltipMsg: PropTypes.string,
550
556
  fetchingSchemaError: PropTypes.bool,
551
557
  popoverPlacement: PropTypes.string,
558
+ overlayStyle: PropTypes.object,
559
+ overlayClassName: PropTypes.string,
560
+ /** e.g. () => document.body — avoids overflow/stacking issues inside slideboxes */
561
+ getPopupContainer: PropTypes.func,
552
562
  };
553
563
 
554
564
  CapTagList.defaultValue = {
@@ -8,6 +8,7 @@ import CapButton from '@capillarytech/cap-ui-library/CapButton';
8
8
  import CapInput from '@capillarytech/cap-ui-library/CapInput';
9
9
  import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
10
10
  import messages from './messages';
11
+ import { CUSTOM_VALUES_EDITOR_SECTION_FALLBACK_KEY } from './constants';
11
12
 
12
13
  const CustomValuesEditor = ({
13
14
  isExtractingTags,
@@ -16,15 +17,16 @@ const CustomValuesEditor = ({
16
17
  setShowJSON,
17
18
  customValues,
18
19
  handleJSONTextChange,
19
- extractedTags,
20
- requiredTags,
21
- optionalTags,
20
+ sections,
22
21
  handleCustomValueChange,
23
22
  handleDiscardCustomValues,
24
23
  handleUpdatePreview,
25
24
  isUpdatingPreview,
26
25
  formatMessage,
27
26
  }) => {
27
+ /** Same as SMS Test & Preview: show token path from extract-tags (fullPath or name). */
28
+ const getPersonalizationTagColumnLabel = (tagNode) => tagNode?.fullPath ?? tagNode?.name ?? '';
29
+
28
30
  if (isExtractingTags) {
29
31
  return (
30
32
  <CapRow className="loading-container">
@@ -77,52 +79,70 @@ const CustomValuesEditor = ({
77
79
  </CapRow>
78
80
  ) : (
79
81
  <>
80
- {extractedTags?.length > 0 && (
81
- <CapRow className="values-table">
82
- <CapRow className="table-header">
83
- <CapLabel type="label31" className="header-cell">
84
- <FormattedMessage {...messages.personalizationTags} />
85
- </CapLabel>
86
- <CapLabel type="label31" className="header-cell">
87
- <FormattedMessage {...messages.customValues} />
82
+ {(sections || []).filter((tagsSection) =>
83
+ (tagsSection?.requiredTags?.length || 0) + (tagsSection?.optionalTags?.length || 0) > 0).map((section) => (
84
+ <React.Fragment key={section.key || section.title?.id || section.title || CUSTOM_VALUES_EDITOR_SECTION_FALLBACK_KEY}>
85
+ {section.title ? (
86
+ <CapLabel type="label2" className="tags-section-title">
87
+ {typeof section.title === 'string' ? section.title : <FormattedMessage {...section.title} />}
88
88
  </CapLabel>
89
- </CapRow>
90
- {requiredTags.map((tag) => (
91
- <CapRow key={tag.fullPath} className="value-row">
92
- <CapRow className="tag-name">
93
- {tag.fullPath}
94
- <span className="required-tag-indicator">*</span>
95
- </CapRow>
96
- <CapRow className="tag-input">
97
- <CapInput
98
- type="text"
99
- isRequired
100
- className="tag-input-field"
101
- value={customValues[tag.fullPath] || ''}
102
- onChange={(e) => handleCustomValueChange(tag.fullPath, e.target.value)}
103
- placeholder={formatMessage(messages.enterValue)}
104
- size="small"
105
- />
106
- </CapRow>
89
+ ) : null}
90
+ <CapRow className="values-table">
91
+ <CapRow className="table-header">
92
+ <CapLabel type="label31" className="header-cell">
93
+ <FormattedMessage {...messages.personalizationTags} />
94
+ </CapLabel>
95
+ <CapLabel type="label31" className="header-cell">
96
+ <FormattedMessage {...messages.customValues} />
97
+ </CapLabel>
107
98
  </CapRow>
108
- ))}
109
- {optionalTags?.map((tag) => (
110
- <CapRow key={tag.fullPath} className="value-row">
111
- <CapRow className="tag-name">{tag.fullPath}</CapRow>
112
- <CapRow className="tag-input">
113
- <CapInput
114
- type="text"
115
- className="tag-input-field"
116
- value={customValues[tag.fullPath] || ''}
117
- onChange={(e) => handleCustomValueChange(tag.fullPath, e.target.value)}
118
- placeholder={formatMessage(messages.enterValue)}
119
- size="small"
120
- />
99
+ {(section?.requiredTags || []).map((tag, tagIndex) => {
100
+ const personalizationTagColumnText = getPersonalizationTagColumnLabel(tag);
101
+ const tagKey = tag?.fullPath ?? `required-${tagIndex}`;
102
+ return (
103
+ <CapRow key={tagKey} className="value-row">
104
+ <CapRow className="tag-name">
105
+ {personalizationTagColumnText}
106
+ <span className="required-tag-indicator">*</span>
107
+ </CapRow>
108
+ <CapRow className="tag-input">
109
+ <CapInput
110
+ type="text"
111
+ isRequired
112
+ className="tag-input-field"
113
+ value={customValues?.[tagKey] ?? ''}
114
+ onChange={(e) => handleCustomValueChange(tagKey, e.target.value)}
115
+ placeholder={formatMessage(messages.enterValue)}
116
+ size="small"
117
+ />
118
+ </CapRow>
121
119
  </CapRow>
122
- </CapRow>
123
- ))}
124
- </CapRow>
125
- )}
120
+ );
121
+ })}
122
+ {(section?.optionalTags || []).map((tag, tagIndex) => {
123
+ const personalizationTagColumnText = getPersonalizationTagColumnLabel(tag);
124
+ const tagKey = tag?.fullPath ?? `optional-${tagIndex}`;
125
+ return (
126
+ <CapRow key={tagKey} className="value-row">
127
+ <CapRow className="tag-name">
128
+ {personalizationTagColumnText}
129
+ </CapRow>
130
+ <CapRow className="tag-input">
131
+ <CapInput
132
+ type="text"
133
+ className="tag-input-field"
134
+ value={customValues?.[tagKey] ?? ''}
135
+ onChange={(e) => handleCustomValueChange(tagKey, e.target.value)}
136
+ placeholder={formatMessage(messages.enterValue)}
137
+ size="small"
138
+ />
139
+ </CapRow>
140
+ </CapRow>
141
+ );
142
+ })}
143
+ </CapRow>
144
+ </React.Fragment>
145
+ ))}
126
146
  </>
127
147
  )}
128
148
  <CapRow className="editor-actions">
@@ -156,9 +176,12 @@ CustomValuesEditor.propTypes = {
156
176
  setShowJSON: PropTypes.func.isRequired,
157
177
  customValues: PropTypes.object.isRequired,
158
178
  handleJSONTextChange: PropTypes.func.isRequired,
159
- extractedTags: PropTypes.array.isRequired,
160
- requiredTags: PropTypes.array.isRequired,
161
- optionalTags: PropTypes.array.isRequired,
179
+ sections: PropTypes.arrayOf(PropTypes.shape({
180
+ key: PropTypes.string,
181
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
182
+ requiredTags: PropTypes.array,
183
+ optionalTags: PropTypes.array,
184
+ })).isRequired,
162
185
  handleCustomValueChange: PropTypes.func.isRequired,
163
186
  handleDiscardCustomValues: PropTypes.func.isRequired,
164
187
  handleUpdatePreview: PropTypes.func.isRequired,
@@ -18,11 +18,17 @@
18
18
  }
19
19
 
20
20
  &__summary-entry {
21
+ display: flex;
22
+ align-items: baseline;
23
+ gap: 0;
21
24
  margin-right: $CAP_SPACE_18;
22
25
  }
23
26
 
24
- &__summary-key {
25
- line-height: $CAP_SPACE_16;
27
+ &__summary-key,
28
+ &__summary-value {
29
+ line-height: 1.4;
30
+ margin-top: 0;
31
+ margin-bottom: 0;
26
32
  }
27
33
 
28
34
  &__edit-icon {