@capillarytech/creatives-library 9.0.14 → 9.0.15-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 (175) hide show
  1. package/constants/unified.js +32 -0
  2. package/global-styles.js +1 -1
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +35 -20
  5. package/utils/common.js +8 -0
  6. package/utils/commonUtils.js +19 -1
  7. package/utils/rcsPayloadUtils.js +92 -0
  8. package/utils/templateVarUtils.js +201 -0
  9. package/utils/tests/rcsPayloadUtils.test.js +226 -0
  10. package/utils/tests/templateVarUtils.test.js +204 -0
  11. package/v2Components/CapActionButton/constants.js +7 -0
  12. package/v2Components/CapActionButton/index.js +168 -109
  13. package/v2Components/CapActionButton/index.scss +156 -5
  14. package/v2Components/CapActionButton/messages.js +19 -3
  15. package/v2Components/CapActionButton/tests/index.test.js +42 -18
  16. package/v2Components/CapImageUpload/index.js +2 -2
  17. package/v2Components/CapTagList/index.js +10 -0
  18. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  24. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +60 -11
  25. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +201 -23
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +355 -76
  28. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
  29. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  30. package/v2Components/CommonTestAndPreview/constants.js +38 -2
  31. package/v2Components/CommonTestAndPreview/index.js +761 -222
  32. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  33. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  34. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  35. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
  36. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -267
  37. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  38. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  39. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  40. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  41. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  42. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  43. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  44. package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
  45. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  46. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
  47. package/v2Components/CommonTestAndPreview/utils.js +84 -0
  48. package/v2Components/FormBuilder/Classic.js +4487 -0
  49. package/v2Components/FormBuilder/Functional/FormBuilderShell.js +366 -0
  50. package/v2Components/FormBuilder/Functional/channels/registry.js +17 -0
  51. package/v2Components/FormBuilder/Functional/channels/sms/buildSubmitPayload.js +9 -0
  52. package/v2Components/FormBuilder/Functional/channels/sms/config.js +30 -0
  53. package/v2Components/FormBuilder/Functional/channels/sms/getEditorErrorDescriptor.js +46 -0
  54. package/v2Components/FormBuilder/Functional/channels/sms/getLiquidContent.js +13 -0
  55. package/v2Components/FormBuilder/Functional/channels/sms/index.js +22 -0
  56. package/v2Components/FormBuilder/Functional/channels/sms/tests/getEditorErrorDescriptor.test.js +52 -0
  57. package/v2Components/FormBuilder/Functional/channels/sms/tests/getLiquidContent.test.js +25 -0
  58. package/v2Components/FormBuilder/Functional/channels/sms/tests/validate.test.js +87 -0
  59. package/v2Components/FormBuilder/Functional/channels/sms/validate.js +89 -0
  60. package/v2Components/FormBuilder/Functional/constants.js +42 -0
  61. package/v2Components/FormBuilder/Functional/core/schema/fieldRegistry.js +38 -0
  62. package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +85 -0
  63. package/v2Components/FormBuilder/Functional/core/store/formReducer.js +81 -0
  64. package/v2Components/FormBuilder/Functional/core/store/selectors.js +30 -0
  65. package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +91 -0
  66. package/v2Components/FormBuilder/Functional/index.js +26 -0
  67. package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +59 -0
  68. package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +31 -0
  69. package/v2Components/FormBuilder/Functional/layout/Section.js +116 -0
  70. package/v2Components/FormBuilder/Functional/renderers/smsRenderers.js +258 -0
  71. package/v2Components/FormBuilder/Functional/tests/channelRegistry.test.js +21 -0
  72. package/v2Components/FormBuilder/Functional/tests/fieldRegistry.test.js +65 -0
  73. package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +97 -0
  74. package/v2Components/FormBuilder/Functional/tests/fixtures/smsParityCases.js +192 -0
  75. package/v2Components/FormBuilder/Functional/tests/formReducer.test.js +129 -0
  76. package/v2Components/FormBuilder/Functional/tests/initializeFormState.test.js +132 -0
  77. package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +40 -0
  78. package/v2Components/FormBuilder/Functional/tests/section.test.js +99 -0
  79. package/v2Components/FormBuilder/Functional/tests/selectors.test.js +67 -0
  80. package/v2Components/FormBuilder/Functional/tests/sms.crossFlowParity.test.js +155 -0
  81. package/v2Components/FormBuilder/Functional/tests/sms.liquid.test.js +172 -0
  82. package/v2Components/FormBuilder/Functional/tests/sms.rollout.test.js +122 -0
  83. package/v2Components/FormBuilder/Functional/tests/sms.shell.parity.test.js +329 -0
  84. package/v2Components/FormBuilder/Functional/tests/smsRenderers.test.js +160 -0
  85. package/v2Components/FormBuilder/Functional/tests/toLegacyFormData.test.js +95 -0
  86. package/v2Components/FormBuilder/_formBuilder.scss +5 -0
  87. package/v2Components/FormBuilder/index.js +41 -4479
  88. package/v2Components/FormBuilder/tests/__snapshots__/sms.characterization.test.js.snap +114 -0
  89. package/v2Components/FormBuilder/tests/entryGate.test.js +106 -0
  90. package/v2Components/FormBuilder/tests/sms.characterization.test.js +336 -0
  91. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  92. package/v2Components/SmsFallback/constants.js +94 -0
  93. package/v2Components/SmsFallback/index.js +958 -0
  94. package/v2Components/SmsFallback/index.scss +266 -0
  95. package/v2Components/SmsFallback/messages.js +78 -0
  96. package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
  97. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  98. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  99. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  100. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
  101. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  102. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  103. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  104. package/v2Components/TemplatePreview/_templatePreview.scss +41 -22
  105. package/v2Components/TemplatePreview/constants.js +2 -0
  106. package/v2Components/TemplatePreview/index.js +143 -31
  107. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  108. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  109. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  110. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  111. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  112. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  113. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  114. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  115. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
  116. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
  117. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  118. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +17 -0
  119. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  120. package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
  121. package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
  122. package/v2Containers/CreativesContainer/constants.js +11 -0
  123. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
  124. package/v2Containers/CreativesContainer/index.js +323 -104
  125. package/v2Containers/CreativesContainer/index.scss +83 -1
  126. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  127. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +79 -34
  128. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  129. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  130. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +333 -90
  131. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  132. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  133. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  134. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  135. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  136. package/v2Containers/Rcs/constants.js +131 -11
  137. package/v2Containers/Rcs/index.js +2602 -807
  138. package/v2Containers/Rcs/index.scss +289 -8
  139. package/v2Containers/Rcs/messages.js +34 -3
  140. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +227 -0
  141. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +73535 -38537
  142. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -8
  143. package/v2Containers/Rcs/tests/index.test.js +147 -36
  144. package/v2Containers/Rcs/tests/mockData.js +38 -0
  145. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  146. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  147. package/v2Containers/Rcs/utils.js +478 -11
  148. package/v2Containers/Sms/Create/index.js +115 -48
  149. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  150. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  151. package/v2Containers/SmsTrai/Create/index.js +9 -4
  152. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  153. package/v2Containers/SmsTrai/Edit/index.js +678 -169
  154. package/v2Containers/SmsTrai/Edit/index.scss +126 -0
  155. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  156. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
  157. package/v2Containers/SmsWrapper/index.js +37 -8
  158. package/v2Containers/TagList/index.js +6 -0
  159. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  160. package/v2Containers/Templates/_templates.scss +171 -12
  161. package/v2Containers/Templates/actions.js +11 -0
  162. package/v2Containers/Templates/constants.js +2 -0
  163. package/v2Containers/Templates/index.js +125 -55
  164. package/v2Containers/Templates/sagas.js +57 -13
  165. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  166. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
  167. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  168. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  169. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  170. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  171. package/v2Containers/TemplatesV2/index.js +86 -23
  172. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  173. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  174. package/v2Containers/Whatsapp/index.js +3 -20
  175. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -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`,
@@ -5,7 +5,7 @@ import { render, screen, fireEvent } from '../../../utils/test-utils';
5
5
  import { waitFor } from '@testing-library/react';
6
6
  import { CapActionButton } from '../index';
7
7
  import { BTN_MAX_LENGTH, PHONE_NUMBER_MAX_LENGTH, URL_MAX_LENGTH } from '../constants';
8
- import { RCS_BUTTON_TYPES } from '../../../v2Containers/Rcs/constants';
8
+ import { RCS_BUTTON_TYPES, HOST_ICS } from '../../../v2Containers/Rcs/constants';
9
9
 
10
10
  const updateHandler = jest.fn();
11
11
  const deleteHandler = jest.fn();
@@ -13,6 +13,7 @@ const initializeComponent = (
13
13
  data,
14
14
  isEditFlow = false,
15
15
  maxButtons = 3,
16
+ host = '',
16
17
  ) => {
17
18
  // Normalize legacy test data shape to match component props
18
19
  const normalizeType = (legacyType) => {
@@ -38,6 +39,7 @@ const initializeComponent = (
38
39
  isEditFlow={isEditFlow}
39
40
  maxButtons={maxButtons}
40
41
  isFullMode={true}
42
+ host={host}
41
43
  />,
42
44
  );
43
45
  };
@@ -60,7 +62,7 @@ describe('CapActionButton', () => {
60
62
  isSaved: false,
61
63
  },
62
64
  ]);
63
- expect(screen.getByText('Type of action')).toBeInTheDocument();
65
+ expect(screen.getByText('Button type')).toBeInTheDocument();
64
66
  expect(screen.getByText('Button text')).toBeInTheDocument();
65
67
  expect(screen.getAllByText('Phone number')[1]).toBeInTheDocument();
66
68
  expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
@@ -123,7 +125,7 @@ describe('CapActionButton', () => {
123
125
  id: 1,
124
126
  },
125
127
  };
126
- const urlInput = await screen.getByPlaceholderText('Enter website URL');
128
+ const urlInput = await screen.getByPlaceholderText('Enter URL');
127
129
  fireEvent.change(urlInput, urlEvent);
128
130
  expect(updateHandler).toHaveBeenCalledWith(
129
131
  {
@@ -163,6 +165,27 @@ describe('CapActionButton', () => {
163
165
  );
164
166
  });
165
167
 
168
+ it('should show delete for quick reply index 0 for non-ICS hosts but hide it for ICS (stop button)', () => {
169
+ const button = {
170
+ index: 0,
171
+ type: RCS_BUTTON_TYPES.QUICK_REPLY,
172
+ text: 'Reply',
173
+ phoneNumber: '',
174
+ url: '',
175
+ postback: 'Reply',
176
+ isSaved: false,
177
+ };
178
+
179
+ // Non-ICS (Infobip/others): delete should be available
180
+ const { unmount } = initializeComponent([button], false, 3, 'rcsinfobipbulk');
181
+ expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument();
182
+ unmount();
183
+
184
+ // ICS: index 0 is reserved; delete should be hidden
185
+ initializeComponent([button], false, 3, HOST_ICS);
186
+ expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument();
187
+ });
188
+
166
189
  it('should respect character length limits', async () => {
167
190
  const initialData = {
168
191
  index: 0,
@@ -201,7 +224,7 @@ describe('CapActionButton', () => {
201
224
  isSaved: false,
202
225
  };
203
226
  initializeComponent([initialData]);
204
- const urlInput = await screen.getByPlaceholderText('Enter website URL');
227
+ const urlInput = await screen.getByPlaceholderText('Enter URL');
205
228
  const longUrl = 'https://example.com/' + 'a'.repeat(URL_MAX_LENGTH + 1);
206
229
  fireEvent.change(urlInput, { target: { value: longUrl, id: 0 } });
207
230
  expect(urlInput.value.length).toBeLessThanOrEqual(URL_MAX_LENGTH);
@@ -248,7 +271,8 @@ describe('CapActionButton', () => {
248
271
  { index: 0, ctaType: RCS_BUTTON_TYPES.QUICK_REPLY, displayText: 'stop', phoneNumber: '', url: '', postback: 'stop', isSaved: true },
249
272
  { index: 1, ctaType: RCS_BUTTON_TYPES.QUICK_REPLY, displayText: 'Saved', phoneNumber: '', url: '', postback: 'Saved', isSaved: true },
250
273
  ];
251
- const { container } = initializeComponent(suggestions);
274
+ // Use ICS host so index 0 (STOP) stays non-deletable and we only get delete icon for index 1
275
+ const { container } = initializeComponent(suggestions, false, 3, HOST_ICS);
252
276
  const deleteIcons = container.querySelectorAll('.rcs-saved-cta-delete-icon');
253
277
  expect(deleteIcons.length).toBeGreaterThan(0);
254
278
  fireEvent.click(deleteIcons[0]);
@@ -306,7 +330,8 @@ describe('CapActionButton', () => {
306
330
  postback: 'stop',
307
331
  isSaved: true,
308
332
  };
309
- const { container } = initializeComponent([stopButton]);
333
+ // Only ICS treats index 0 as a mandatory STOP quick reply
334
+ const { container } = initializeComponent([stopButton], false, 3, HOST_ICS);
310
335
  expect(container.querySelector('.rcs-saved-cta-delete-icon')).not.toBeInTheDocument();
311
336
  });
312
337
 
@@ -427,7 +452,7 @@ describe('CapActionButton', () => {
427
452
  isSaved: false,
428
453
  };
429
454
  initializeComponent([button]);
430
- expect(screen.getByPlaceholderText(/enter website url/i)).toBeInTheDocument();
455
+ expect(screen.getByPlaceholderText(/enter url/i)).toBeInTheDocument();
431
456
  });
432
457
 
433
458
  it('should update both displayText and postback when button text changes (updateDisplayAndPostback)', () => {
@@ -507,7 +532,7 @@ describe('CapActionButton', () => {
507
532
  isSaved: false,
508
533
  };
509
534
  initializeComponent([initial]);
510
- const urlInput = screen.getByPlaceholderText('Enter website URL');
535
+ const urlInput = screen.getByPlaceholderText('Enter URL');
511
536
  fireEvent.change(urlInput, { target: { value: 'http://localhost:3030/creatives/ui/v2', id: 1 } });
512
537
  expect(screen.getByText(/url is not valid/i)).toBeInTheDocument();
513
538
  });
@@ -607,12 +632,12 @@ describe('CapActionButton', () => {
607
632
  );
608
633
  };
609
634
  render(<StatefulWrapper />);
610
- let urlInput = screen.getByPlaceholderText('Enter website URL');
635
+ let urlInput = screen.getByPlaceholderText('Enter URL');
611
636
  // Enter invalid URL
612
637
  fireEvent.change(urlInput, { target: { value: 'badurl', id: 0 } });
613
638
  expect(await screen.findByText(/url is not valid/i)).toBeInTheDocument();
614
639
  // Re-grab input after re-render and enter valid URL
615
- urlInput = screen.getByPlaceholderText('Enter website URL');
640
+ urlInput = screen.getByPlaceholderText('Enter URL');
616
641
  fireEvent.change(urlInput, { target: { value: 'https://good.com', id: 0 } });
617
642
  await waitFor(() => expect(screen.queryByText(/url is not valid/i)).not.toBeInTheDocument());
618
643
  });
@@ -867,15 +892,14 @@ describe('CapActionButton function logic', () => {
867
892
  expect(renderCtaOptions(label, tooltipLabel, false)).toBe('Test');
868
893
  });
869
894
 
870
- it('renderLength returns correct CapHeading', () => {
871
- const CapHeading = ({ type, className, children }) => <div className={className}>{children}</div>;
872
- const formatMessage = (msg, values) => `${values.currentLength}/${values.maxLength}`;
873
- const renderLength = (len, max) => (
874
- <CapHeading type="label1" className="rcs-render-btn-length">
875
- {formatMessage({}, { currentLength: len, maxLength: max })}
876
- </CapHeading>
895
+ it('inner char count uses current/max format', () => {
896
+ const formatMessage = (msg, values) => `${values.current}/${values.max}`;
897
+ const renderInnerCharCount = (len, max) => (
898
+ <span className="rcs-cta-inner-char-count">
899
+ {formatMessage({}, { current: len, max })}
900
+ </span>
877
901
  );
878
- const result = renderLength(5, 25);
902
+ const result = renderInnerCharCount(5, 25);
879
903
  expect(result.props.children).toBe('5/25');
880
904
  });
881
905
  });
@@ -5,7 +5,7 @@
5
5
  *
6
6
  */
7
7
 
8
- import React, { useState, useEffect, useCallback } from 'react';
8
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
9
9
  import PropTypes from "prop-types";
10
10
 
11
11
  import LabelHOC from '@capillarytech/cap-ui-library/assets/HOCs/ComponentWithLabelHOC';
@@ -92,7 +92,7 @@ function CapImageUpload(props) {
92
92
  [imageSrc],
93
93
  );
94
94
 
95
- const WithLabel = LabelHOC(ImageComponent);
95
+ const WithLabel = useMemo(() => LabelHOC(ImageComponent), [ImageComponent]);
96
96
 
97
97
  const uploadImages = useCallback((e, { files }) => {
98
98
  if (e) {
@@ -346,6 +346,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
346
346
  render() {
347
347
  const {
348
348
  hidePopover = false, intl = {}, moduleFilterEnabled, label, modalProps, channel, fetchingSchemaError = false,
349
+ overlayStyle,
350
+ overlayClassName,
351
+ getPopupContainer,
349
352
  } = this.props;
350
353
  const {formatMessage} = intl;
351
354
  const {
@@ -439,6 +442,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
439
442
  classNames={{
440
443
  container: 'tag-list-popover-container'
441
444
  }}
445
+ overlayStyle={overlayStyle}
446
+ overlayClassName={overlayClassName}
447
+ getPopupContainer={getPopupContainer}
442
448
  >
443
449
  <CapTooltip
444
450
  title={
@@ -510,6 +516,10 @@ CapTagList.propTypes = {
510
516
  disableTooltipMsg: PropTypes.string,
511
517
  fetchingSchemaError: PropTypes.bool,
512
518
  popoverPlacement: PropTypes.string,
519
+ overlayStyle: PropTypes.object,
520
+ overlayClassName: PropTypes.string,
521
+ /** e.g. () => document.body — avoids overflow/stacking issues inside slideboxes */
522
+ getPopupContainer: PropTypes.func,
513
523
  };
514
524
 
515
525
  CapTagList.defaultValue = {
@@ -8,6 +8,8 @@ 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';
12
+ import { getPersonalizationTagColumnLabel } from './utils';
11
13
 
12
14
  const CustomValuesEditor = ({
13
15
  isExtractingTags,
@@ -16,9 +18,7 @@ const CustomValuesEditor = ({
16
18
  setShowJSON,
17
19
  customValues,
18
20
  handleJSONTextChange,
19
- extractedTags,
20
- requiredTags,
21
- optionalTags,
21
+ sections,
22
22
  handleCustomValueChange,
23
23
  handleDiscardCustomValues,
24
24
  handleUpdatePreview,
@@ -77,51 +77,70 @@ const CustomValuesEditor = ({
77
77
  </CapRow>
78
78
  ) : (
79
79
  <>
80
- {extractedTags?.length > 0 && (
81
- <CapRow className="values-table">
82
- <CapRow className="table-header" align='middle' justify='space-between'>
83
- <CapLabel type="label31" className="header-cell">
84
- <FormattedMessage {...messages.personalizationTags} />
80
+ {(sections || []).filter((tagsSection) =>
81
+ (tagsSection?.requiredTags?.length || 0) + (tagsSection?.optionalTags?.length || 0) > 0).map((section) => (
82
+ <React.Fragment key={section.key || section.title?.id || section.title || CUSTOM_VALUES_EDITOR_SECTION_FALLBACK_KEY}>
83
+ {section.title ? (
84
+ <CapLabel type="label2" className="tags-section-title">
85
+ {typeof section.title === 'string' ? section.title : <FormattedMessage {...section.title} />}
85
86
  </CapLabel>
86
- <CapLabel type="label31" className="header-cell">
87
- <FormattedMessage {...messages.customValues} />
88
- </CapLabel>
89
- </CapRow>
90
- {requiredTags.map((tag) => (
91
- <CapRow key={tag.fullPath} className="value-row" align='middle' justify='start'>
92
- <CapRow className="tag-name" align='top' justify='start'>
93
- {`${tag.fullPath}*`}
94
- </CapRow>
95
- <CapRow className="tag-input">
96
- <CapInput
97
- type="text"
98
- isRequired
99
- className="tag-input-field"
100
- value={customValues[tag.fullPath] || ''}
101
- onChange={(e) => handleCustomValueChange(tag.fullPath, e.target.value)}
102
- placeholder={formatMessage(messages.enterValue)}
103
- size="small"
104
- />
105
- </CapRow>
87
+ ) : null}
88
+ <CapRow className="values-table">
89
+ <CapRow type="flex" className="table-header">
90
+ <CapLabel type="label31" className="header-cell">
91
+ <FormattedMessage {...messages.personalizationTags} />
92
+ </CapLabel>
93
+ <CapLabel type="label31" className="header-cell">
94
+ <FormattedMessage {...messages.customValues} />
95
+ </CapLabel>
106
96
  </CapRow>
107
- ))}
108
- {optionalTags?.map((tag) => (
109
- <CapRow key={tag.fullPath} className="value-row" align='middle' justify='start'>
110
- <CapRow className="tag-name" justify='start'>{tag.fullPath}</CapRow>
111
- <CapRow className="tag-input">
112
- <CapInput
113
- type="text"
114
- className="tag-input-field"
115
- value={customValues[tag.fullPath] || ''}
116
- onChange={(e) => handleCustomValueChange(tag.fullPath, e.target.value)}
117
- placeholder={formatMessage(messages.enterValue)}
118
- size="small"
119
- />
97
+ {(section?.requiredTags || []).map((tag, tagIndex) => {
98
+ const personalizationTagColumnText = getPersonalizationTagColumnLabel(tag);
99
+ const tagKey = tag?.fullPath ?? `required-${tagIndex}`;
100
+ return (
101
+ <CapRow key={tagKey} type="flex" noWrap className="value-row">
102
+ <CapRow type="flex" className="tag-name">
103
+ {personalizationTagColumnText}
104
+ <span className="required-tag-indicator" aria-hidden="true">*</span>
105
+ </CapRow>
106
+ <CapRow type="flex" className="tag-input">
107
+ <CapInput
108
+ type="text"
109
+ isRequired
110
+ className="tag-input-field"
111
+ value={customValues?.[tagKey] ?? ''}
112
+ onChange={(e) => handleCustomValueChange(tagKey, e.target.value)}
113
+ placeholder={formatMessage(messages.enterValue)}
114
+ size="small"
115
+ />
116
+ </CapRow>
120
117
  </CapRow>
121
- </CapRow>
122
- ))}
123
- </CapRow>
124
- )}
118
+ );
119
+ })}
120
+ {(section?.optionalTags || []).map((tag, tagIndex) => {
121
+ const personalizationTagColumnText = getPersonalizationTagColumnLabel(tag);
122
+ const tagKey = tag?.fullPath ?? `optional-${tagIndex}`;
123
+ return (
124
+ <CapRow key={tagKey} type="flex" noWrap className="value-row">
125
+ <CapRow type="flex" className="tag-name">
126
+ {personalizationTagColumnText}
127
+ </CapRow>
128
+ <CapRow type="flex" className="tag-input">
129
+ <CapInput
130
+ type="text"
131
+ className="tag-input-field"
132
+ value={customValues?.[tagKey] ?? ''}
133
+ onChange={(e) => handleCustomValueChange(tagKey, e.target.value)}
134
+ placeholder={formatMessage(messages.enterValue)}
135
+ size="small"
136
+ />
137
+ </CapRow>
138
+ </CapRow>
139
+ );
140
+ })}
141
+ </CapRow>
142
+ </React.Fragment>
143
+ ))}
125
144
  </>
126
145
  )}
127
146
  <CapRow type='flex' align='middle' justify='space-between' className="editor-actions">
@@ -155,9 +174,12 @@ CustomValuesEditor.propTypes = {
155
174
  setShowJSON: PropTypes.func.isRequired,
156
175
  customValues: PropTypes.object.isRequired,
157
176
  handleJSONTextChange: PropTypes.func.isRequired,
158
- extractedTags: PropTypes.array.isRequired,
159
- requiredTags: PropTypes.array.isRequired,
160
- optionalTags: PropTypes.array.isRequired,
177
+ sections: PropTypes.arrayOf(PropTypes.shape({
178
+ key: PropTypes.string,
179
+ title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
180
+ requiredTags: PropTypes.array,
181
+ optionalTags: PropTypes.array,
182
+ })).isRequired,
161
183
  handleCustomValueChange: PropTypes.func.isRequired,
162
184
  handleDiscardCustomValues: PropTypes.func.isRequired,
163
185
  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 {