@capillarytech/creatives-library 9.0.55 → 9.0.56-alpha.0

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 (30) hide show
  1. package/constants/unified.js +0 -1
  2. package/package.json +1 -1
  3. package/services/api.js +12 -0
  4. package/utils/common.js +0 -4
  5. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
  6. package/v2Components/CommonTestAndPreview/constants.js +2 -0
  7. package/v2Components/CommonTestAndPreview/index.js +1 -1
  8. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
  9. package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
  10. package/v2Containers/CommunicationFlow/CommunicationFlow.js +109 -56
  11. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +1 -1
  12. package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +60 -27
  13. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +441 -51
  14. package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +40 -4
  15. package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
  16. package/v2Containers/CommunicationFlow/constants.js +63 -4
  17. package/v2Containers/CommunicationFlow/index.js +15 -20
  18. package/v2Containers/CommunicationFlow/messages.js +7 -3
  19. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +0 -4
  20. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +2 -2
  21. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +6 -0
  22. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +25 -0
  23. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
  24. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +19 -5
  25. package/v2Containers/CreativesContainer/index.js +1 -1
  26. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +5 -5
  27. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -12
  28. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +3 -3
  29. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -34
  30. package/.vscode/settings.json +0 -3
@@ -108,6 +108,22 @@ describe('CommunicationFlowCard', () => {
108
108
  fireEvent.click(screen.getByText('Add creatives'));
109
109
  expect(screen.getByTestId('comm-flow-mock')).toHaveAttribute('data-mode', 'create');
110
110
  });
111
+
112
+ it('disables the Add creatives button when disabled prop is true', () => {
113
+ renderCard({ disabled: true, disabledTooltip: 'Enter an Alert name to add content' });
114
+ expect(screen.getByText('Add creatives').closest('button')).toBeDisabled();
115
+ });
116
+
117
+ it('does not open SlideBox when Add creatives is clicked while disabled', () => {
118
+ renderCard({ disabled: true, disabledTooltip: 'Enter an Alert name to add content' });
119
+ fireEvent.click(screen.getByText('Add creatives'));
120
+ expect(screen.queryByTestId('slide-box')).not.toBeInTheDocument();
121
+ });
122
+
123
+ it('Add creatives button is enabled by default (disabled prop absent)', () => {
124
+ renderCard();
125
+ expect(screen.getByText('Add creatives').closest('button')).not.toBeDisabled();
126
+ });
111
127
  });
112
128
 
113
129
  describe('configured state (initialData provided)', () => {
@@ -186,6 +202,26 @@ describe('CommunicationFlowCard', () => {
186
202
  renderCard({ initialData: makeSavedData() });
187
203
  expect(screen.getByText('Other controls')).toBeInTheDocument();
188
204
  });
205
+
206
+ it('hides the Other controls column when config.features.showDynamicControls is false', () => {
207
+ renderCard({
208
+ config: { ...baseConfig, features: { showDynamicControls: false } },
209
+ initialData: makeSavedData({
210
+ dynamicControls: {
211
+ sendToControlCustomers: false, sendToBrandPocs: false, useTinyUrl: false, overrideDailyLimit: false,
212
+ },
213
+ }),
214
+ });
215
+ expect(screen.queryByText('Other controls')).not.toBeInTheDocument();
216
+ });
217
+
218
+ it('still shows the Other controls column when showDynamicControls is unset (Engagement Module default)', () => {
219
+ renderCard({
220
+ config: { ...baseConfig, features: {} },
221
+ initialData: makeSavedData(),
222
+ });
223
+ expect(screen.getByText('Other controls')).toBeInTheDocument();
224
+ });
189
225
  });
190
226
 
191
227
  describe('channel config resolution', () => {
@@ -231,11 +267,11 @@ describe('CommunicationFlowCard', () => {
231
267
  expect(screen.getByText(/Sender ID/)).toBeInTheDocument();
232
268
  });
233
269
 
234
- it('shows senderEmail for EMAIL with Sender ID label', () => {
270
+ it('shows senderId for EMAIL with Sender ID label', () => {
235
271
  renderCard({
236
272
  initialData: makeSavedData({
237
273
  contentItems: [{ channel: 'EMAIL', templateData: {} }],
238
- deliverySetting: { channelSetting: { EMAIL: { senderEmail: 'test@example.com' } } },
274
+ deliverySetting: { channelSetting: { EMAIL: { senderId: 'test@example.com' } } },
239
275
  }),
240
276
  });
241
277
  expect(screen.getByText(/test@example.com/)).toBeInTheDocument();
@@ -545,10 +581,10 @@ describe('CommunicationFlowCard', () => {
545
581
  });
546
582
 
547
583
  describe('SlideBox header', () => {
548
- it('renders Add message title in the slidebox header', () => {
584
+ it('renders Add content title in the slidebox header', () => {
549
585
  renderCard();
550
586
  fireEvent.click(screen.getByText('Add creatives'));
551
- expect(screen.getByText('Add message')).toBeInTheDocument();
587
+ expect(screen.getByText('Add content')).toBeInTheDocument();
552
588
  });
553
589
  });
554
590
 
@@ -0,0 +1,85 @@
1
+ import {
2
+ CCS_CONTENT_TRANSFORMS,
3
+ CCS_CHANNEL_NAME_MAP,
4
+ CCS_CHANNEL_CONTENT_KEY_MAP,
5
+ CCS_CHANNEL_DELIVERY_KEY_MAP,
6
+ } from '../constants';
7
+
8
+ describe('CCS_CONTENT_TRANSFORMS', () => {
9
+ describe('EMAIL', () => {
10
+ it('prefers emailBody over emailHtml when both are present', () => {
11
+ expect(
12
+ CCS_CONTENT_TRANSFORMS.EMAIL({
13
+ emailSubject: 'Subject', emailBody: '<p>Body</p>', emailHtml: '<p>Html</p>',
14
+ }),
15
+ ).toEqual({ messageSubject: 'Subject', messageBody: '<p>Body</p>' });
16
+ });
17
+
18
+ it('falls back to emailHtml when emailBody is absent', () => {
19
+ expect(
20
+ CCS_CONTENT_TRANSFORMS.EMAIL({ emailSubject: 'Subject', emailHtml: '<p>Html</p>' }),
21
+ ).toEqual({ messageSubject: 'Subject', messageBody: '<p>Html</p>' });
22
+ });
23
+
24
+ it('defaults templateData to {} when called with no argument', () => {
25
+ expect(CCS_CONTENT_TRANSFORMS.EMAIL()).toEqual({
26
+ messageSubject: undefined, messageBody: undefined,
27
+ });
28
+ });
29
+ });
30
+
31
+ describe('SMS', () => {
32
+ it('uses messageBody when present', () => {
33
+ expect(CCS_CONTENT_TRANSFORMS.SMS({ messageBody: 'Hello' })).toEqual({ message: 'Hello' });
34
+ });
35
+
36
+ it('falls back to an empty string when messageBody is absent', () => {
37
+ expect(CCS_CONTENT_TRANSFORMS.SMS({})).toEqual({ message: '' });
38
+ });
39
+
40
+ it('defaults templateData to {} when called with no argument', () => {
41
+ expect(CCS_CONTENT_TRANSFORMS.SMS()).toEqual({ message: '' });
42
+ });
43
+ });
44
+
45
+ describe('WEBPUSH', () => {
46
+ it('extracts fields from the nested messageContent.content wrapper', () => {
47
+ expect(
48
+ CCS_CONTENT_TRANSFORMS.WEBPUSH({
49
+ messageContent: {
50
+ content: {
51
+ messageSubject: 'Subject', accountId: 'acc_1', content: 'Body', offers: [{ id: 1 }],
52
+ },
53
+ },
54
+ }),
55
+ ).toEqual({ messageSubject: 'Subject', accountId: 'acc_1', content: 'Body' });
56
+ });
57
+
58
+ it('falls back to the flat templateData when messageContent.content is absent', () => {
59
+ expect(
60
+ CCS_CONTENT_TRANSFORMS.WEBPUSH({
61
+ messageSubject: 'Subject', accountId: 'acc_1', content: 'Body',
62
+ }),
63
+ ).toEqual({ messageSubject: 'Subject', accountId: 'acc_1', content: 'Body' });
64
+ });
65
+
66
+ it('defaults templateData to {} when called with no argument', () => {
67
+ expect(CCS_CONTENT_TRANSFORMS.WEBPUSH()).toEqual({
68
+ messageSubject: undefined, accountId: undefined, content: undefined,
69
+ });
70
+ });
71
+ });
72
+ });
73
+
74
+ describe('CCS_CHANNEL_NAME_MAP', () => {
75
+ it('normalizes the UI\'s MOBILEPUSH identifier to CCS\'s PUSH enum value', () => {
76
+ expect(CCS_CHANNEL_NAME_MAP.MOBILEPUSH).toBe('PUSH');
77
+ });
78
+ });
79
+
80
+ describe('CCS_CHANNEL_CONTENT_KEY_MAP / CCS_CHANNEL_DELIVERY_KEY_MAP', () => {
81
+ it('maps the PUSH channel to its mpush-prefixed content and delivery keys', () => {
82
+ expect(CCS_CHANNEL_CONTENT_KEY_MAP.PUSH).toBe('mpushMessageContent');
83
+ expect(CCS_CHANNEL_DELIVERY_KEY_MAP.PUSH).toBe('mpushDeliverySettings');
84
+ });
85
+ });
@@ -38,9 +38,68 @@ export const CHANNEL_PRIORITY = 'CHANNEL_PRIORITY';
38
38
  export const AB_TEST = 'AB_TEST';
39
39
  export const SINGLE_TEMPLATE = 'SINGLE_TEMPLATE';
40
40
 
41
- // Channel config - single source of truth for CreativesContainer and TemplatesV2
42
- // paneKey: TemplatesV2 defaultPanes object key (for channelsToHide)
43
- // channelProp: CreativesContainer channel prop (must match pane.key for tab to be active)
41
+ // CCS CommDefinition strategyType distinct from SINGLE_TEMPLATE above, which is this UI's own communicationStrategy value. Only SINGLE is supported this phase; CCS create is skipped entirely for CHANNEL_PRIORITY/AB_TEST.
42
+ export const CCS_STRATEGY_TYPE_SINGLE = 'SINGLE';
43
+
44
+ // Mirrors CCS SingleChannelStrategy field names exactly (per Cap Notify API design samples and real create responses); kept separate from the legacy messageMeta CHANNEL_CONTENT_KEY_MAP/CHANNEL_DELIVERY_KEY_MAP.
45
+ export const CCS_CHANNEL_CONTENT_KEY_MAP = {
46
+ SMS: 'smsMessageContent',
47
+ EMAIL: 'emailMessageContent',
48
+ WHATSAPP: 'whatsappMessageContent',
49
+ // CCS's channel enum value for mobile push is 'PUSH' (verified against real create responses), even though the content/delivery keys stay 'mpush*'.
50
+ PUSH: 'mpushMessageContent',
51
+ INAPP: 'inAppMessageContent',
52
+ ANDROID: 'androidMessageContent',
53
+ IOS: 'iosMessageContent',
54
+ ZALO: 'zaloMessageContent',
55
+ VIBER: 'viberMessageContent',
56
+ LINE: 'lineMessageContent',
57
+ WEBPUSH: 'webPushMessageContent',
58
+ RCS: 'rcsMessageContent',
59
+ };
60
+
61
+ // contentItem.templateData uses legacy messageMeta field names, so transform mismatched channel fields before sending to CCS; for EMAIL, map legacy `emailSubject`/`emailBody` to CCS's `messageSubject`/`messageBody`.
62
+ export const CCS_CONTENT_TRANSFORMS = {
63
+ EMAIL: (templateData = {}) => ({
64
+ messageSubject: templateData.emailSubject,
65
+ messageBody: templateData.emailBody || templateData.emailHtml,
66
+ }),
67
+ // SMS: legacy stores text as `messageBody`, while CCS expects `message`; map the field explicitly to avoid CCS returning smsMessageContent.message as null.
68
+ SMS: (templateData = {}) => ({
69
+ message: templateData.messageBody || '',
70
+ }),
71
+ // WEBPUSH: the legacy editor nests data under messageContent.content; flatten it to CCS's { messageSubject, accountId, content } shape and drop the legacy-only `offers` field.
72
+ WEBPUSH: (templateData = {}) => {
73
+ const inner = templateData.messageContent?.content || templateData;
74
+ return {
75
+ messageSubject: inner.messageSubject,
76
+ accountId: inner.accountId,
77
+ content: inner.content,
78
+ };
79
+ },
80
+ };
81
+
82
+ export const CCS_CHANNEL_DELIVERY_KEY_MAP = {
83
+ SMS: 'smsDeliverySettings',
84
+ EMAIL: 'emailDeliverySettings',
85
+ WHATSAPP: 'whatsappDeliverySettings',
86
+ PUSH: 'mpushDeliverySettings',
87
+ INAPP: 'inAppDeliverySettings',
88
+ ANDROID: 'androidDeliverySettings',
89
+ IOS: 'iosDeliverySettings',
90
+ ZALO: 'zaloDeliverySettings',
91
+ VIBER: 'viberDeliverySettings',
92
+ LINE: 'lineDeliverySettings',
93
+ WEBPUSH: 'webPushDeliverySettings',
94
+ RCS: 'rcsDeliverySettings',
95
+ };
96
+
97
+ // The UI uses `MOBILEPUSH`, while CCS uses `PUSH` for the channel enum (verified against real create responses); normalize this value before building the CCS payload, while retaining the `mpush*` content/delivery keys. All other channel identifiers pass through unchanged.
98
+ export const CCS_CHANNEL_NAME_MAP = {
99
+ MOBILEPUSH: 'PUSH',
100
+ };
101
+
102
+ // Channel config — single source of truth for CreativesContainer and TemplatesV2; paneKey is the TemplatesV2 defaultPanes key used by channelsToHide, and channelProp is the CreativesContainer channel prop that must match pane.key for the tab to be active.
44
103
  export const CHANNELS = [
45
104
  {
46
105
  value: 'SMS',
@@ -232,4 +291,4 @@ export const INCENTIVE_TYPES = [
232
291
  {
233
292
  value: 'badges', label: <FormattedMessage {...messages.badges} />, isActive: false, disabled: false,
234
293
  },
235
- ];
294
+ ];
@@ -9,7 +9,6 @@
9
9
  import React from 'react';
10
10
  import PropTypes from 'prop-types';
11
11
  import CommunicationFlow from './CommunicationFlow';
12
- import { hasSupportEngagementModule } from '../../utils/common';
13
12
 
14
13
  const CommunicationFlowContainer = ({
15
14
  config,
@@ -18,22 +17,16 @@ const CommunicationFlowContainer = ({
18
17
  onCancel,
19
18
  onChange,
20
19
  ...otherProps
21
- }) => {
22
- if (!hasSupportEngagementModule()) {
23
- return null;
24
- }
25
-
26
- return (
27
- <CommunicationFlow
28
- config={config}
29
- initialData={initialData}
30
- onSave={onSave}
31
- onCancel={onCancel}
32
- onChange={onChange}
33
- {...otherProps}
34
- />
35
- );
36
- };
20
+ }) => (
21
+ <CommunicationFlow
22
+ config={config}
23
+ initialData={initialData}
24
+ onSave={onSave}
25
+ onCancel={onCancel}
26
+ onChange={onChange}
27
+ {...otherProps}
28
+ />
29
+ );
37
30
 
38
31
  CommunicationFlowContainer.propTypes = {
39
32
  config: PropTypes.shape({
@@ -87,11 +80,13 @@ CommunicationFlowContainer.propTypes = {
87
80
  controls: PropTypes.array,
88
81
  }),
89
82
  }),
90
- context: PropTypes.object, // ouId, campaignId, programId, etc.
91
- useCCS: PropTypes.bool, // If false, skips CCS bulk-claim-approve on save. Defaults to true.
83
+ context: PropTypes.object, // ouId, campaignId, programId; name (required for CCS create),
84
+ // referenceId (optional, auto-generated from name if absent), description (optional).
85
+ useCCS: PropTypes.bool, // If false, skips the CCS createCommDefinition call on save. Defaults to true.
92
86
  }).isRequired,
93
87
  initialData: PropTypes.object, // for edit/preview mode
94
- onSave: PropTypes.func.isRequired, // (data) => void - called when user saves
88
+ onSave: PropTypes.func.isRequired, // (data) => void - called when user saves; data.ccsCommDefinition
89
+ // ({id, referenceId, version, status}) is present when the CCS create succeeded
95
90
  onCancel: PropTypes.func.isRequired, // () => void - called when user cancels
96
91
  onChange: PropTypes.func, // (data) => void - optional, called on data changes
97
92
  };
@@ -64,9 +64,9 @@ export default {
64
64
  },
65
65
 
66
66
  // Slidebox header
67
- addMessage: {
68
- id: `${prefix}.addMessage`,
69
- defaultMessage: 'Add message',
67
+ addContent: {
68
+ id: `${prefix}.addContent`,
69
+ defaultMessage: 'Add content',
70
70
  },
71
71
 
72
72
  // Step prompts
@@ -363,4 +363,8 @@ export default {
363
363
  id: `${prefix}.senderNotConfiguredError`,
364
364
  defaultMessage: 'Selected domain gateway id is not correct. Please change the domain id or contact the gateway team to register them with Capillary.',
365
365
  },
366
+ duplicateReferenceIdError: {
367
+ id: `${prefix}.duplicateReferenceIdError`,
368
+ defaultMessage: 'This reference ID is already in use in your organization. Please use a different reference ID.',
369
+ },
366
370
  };
@@ -407,10 +407,6 @@ const ChannelSelectionStep = ({
407
407
  return (
408
408
  <>
409
409
  <CapRow className="channel-selection-step">
410
- <CapHeading type="h3" className="margin-b-8">
411
- {formatMessage(messages.contentTemplate)}
412
- </CapHeading>
413
-
414
410
  <CapRow
415
411
  useLegacy
416
412
  className={`content-template-container ${contentItems?.length > 0 ? 'content-template-container--has-content' : ''}`}
@@ -94,7 +94,7 @@ describe('ChannelSelectionStep', () => {
94
94
  console.log.mockRestore();
95
95
  });
96
96
 
97
- it('empty state: shows content template heading and add-template entry point', () => {
97
+ it('empty state: shows the Channel heading and add-template entry point', () => {
98
98
  const onChange = jest.fn();
99
99
  renderStep(
100
100
  <ChannelSelectionStep
@@ -104,7 +104,7 @@ describe('ChannelSelectionStep', () => {
104
104
  />,
105
105
  );
106
106
 
107
- expect(screen.getAllByText('Content template').length).toBeGreaterThanOrEqual(1);
107
+ expect(screen.getAllByText('Channel').length).toBeGreaterThanOrEqual(1);
108
108
  expect(screen.getByRole('button', { name: /add creative/i })).toBeInTheDocument();
109
109
  expect(screen.queryByTestId('creatives-mock')).not.toBeInTheDocument();
110
110
  });
@@ -29,6 +29,7 @@ import {
29
29
  getEmailDefaultsForDomainId,
30
30
  getSmsDefaultsForDomainId,
31
31
  getRcsAccountName,
32
+ getEmailDomainGatewayMapIdByDomainId,
32
33
  } from './deliverySettingsConfig';
33
34
  import messages from '../../messages';
34
35
  import './SenderDetails.scss';
@@ -151,6 +152,11 @@ const SenderDetails = ({
151
152
  saveValues.whatsappDomainId = matchedDomain?.domainProperties?.id ?? matchedDomain?.domainId ?? matchedDomain?.id;
152
153
  }
153
154
  }
155
+ // Include EMAIL domainGatewayMapId derived from the selected domain for persistence -
156
+ // CCS needs it alongside domainId, but it's not a user-facing field.
157
+ if (entity && fieldValues.emailDomain != null && fieldValues.emailDomain !== '') {
158
+ saveValues.emailDomainGatewayMapId = getEmailDomainGatewayMapIdByDomainId(entity, fieldValues.emailDomain);
159
+ }
154
160
  onSave(saveValues);
155
161
  }
156
162
  onClose();
@@ -83,6 +83,7 @@ const ENTITIES = {
83
83
  emailTwoDomains: {
84
84
  EMAIL: [
85
85
  {
86
+ id: 'gwmap-a',
86
87
  domainProperties: {
87
88
  id: 'em-a',
88
89
  domainName: 'Mail Alpha',
@@ -101,6 +102,7 @@ const ENTITIES = {
101
102
  },
102
103
  },
103
104
  {
105
+ id: 'gwmap-b',
104
106
  domainProperties: {
105
107
  id: 'em-b',
106
108
  domainName: 'Mail Beta',
@@ -322,6 +324,29 @@ describe('SenderDetails', () => {
322
324
  });
323
325
  });
324
326
 
327
+ it('persists emailDomainGatewayMapId derived from the selected email domain on save', async () => {
328
+ const { onSave } = renderSenderDetails({
329
+ channels: ['EMAIL'],
330
+ preloadedDomainProperties: ENTITIES.emailTwoDomains,
331
+ });
332
+ const root = document.querySelector('.sender-details');
333
+ await waitFor(() => expect(within(root).getByText('Mail Alpha')).toBeInTheDocument());
334
+
335
+ await openSelectAndChoose(root, 0, 'Mail Beta');
336
+ await waitFor(() => expect(within(root).getByText('beta@other.com')).toBeInTheDocument());
337
+
338
+ const saveBtn = within(root).getByRole('button', { name: /save changes/i });
339
+ await waitFor(() => expect(saveBtn).not.toBeDisabled());
340
+ fireEvent.click(saveBtn);
341
+
342
+ expect(onSave).toHaveBeenCalledWith(
343
+ expect.objectContaining({
344
+ emailDomain: 'em-b',
345
+ emailDomainGatewayMapId: 'gwmap-b',
346
+ }),
347
+ );
348
+ });
349
+
325
350
  it('resets email domain row back to the first gateway', async () => {
326
351
  renderSenderDetails({
327
352
  channels: ['EMAIL'],
@@ -438,6 +438,7 @@ describe('deliverySettingsConfig — user-facing flows', () => {
438
438
  smsDomain: 'd1',
439
439
  smsSenderId: 'g1',
440
440
  emailDomain: 'ed',
441
+ emailDomainGatewayMapId: 'gwmap-1',
441
442
  emailSenderId: 'mail@x.com',
442
443
  emailSenderName: 'N',
443
444
  emailReplyToId: 'r@x.com',
@@ -455,9 +456,10 @@ describe('deliverySettingsConfig — user-facing flows', () => {
455
456
  SMS: { domainId: 'd1', gsmSenderId: 'g1' },
456
457
  EMAIL: {
457
458
  domainId: 'ed',
458
- senderEmail: 'mail@x.com',
459
+ domainGatewayMapId: 'gwmap-1',
460
+ senderId: 'mail@x.com',
459
461
  senderLabel: 'N',
460
- senderReplyTo: 'r@x.com',
462
+ replyToId: 'r@x.com',
461
463
  },
462
464
  WHATSAPP: { domainId: 'wd', senderMobNum: '+w' },
463
465
  RCS: { senderMobNum: '+r', rcsSender: '+r' },
@@ -472,9 +474,10 @@ describe('deliverySettingsConfig — user-facing flows', () => {
472
474
  SMS: { domainId: 'd', gsmSenderId: 'g' },
473
475
  EMAIL: {
474
476
  domainId: 'ed',
475
- senderEmail: 'e@e.com',
477
+ domainGatewayMapId: 'gwmap-2',
478
+ senderId: 'e@e.com',
476
479
  senderLabel: 'L',
477
- senderReplyTo: 'r@e.com',
480
+ replyToId: 'r@e.com',
478
481
  },
479
482
  WHATSAPP: { senderMobNum: '+w', domainId: 'wid' },
480
483
  RCS: { rcsSender: '+r' },
@@ -486,6 +489,10 @@ describe('deliverySettingsConfig — user-facing flows', () => {
486
489
  smsDomain: 'd',
487
490
  smsSenderId: 'g',
488
491
  emailDomain: 'ed',
492
+ emailDomainGatewayMapId: 'gwmap-2',
493
+ emailSenderId: 'e@e.com',
494
+ emailSenderName: 'L',
495
+ emailReplyToId: 'r@e.com',
489
496
  whatsappDomainId: 'wid',
490
497
  viberSenderId: 'vx',
491
498
  });
@@ -181,6 +181,12 @@ const getEmailDomainValue = (entity) => {
181
181
  return emailData.domainProperties?.id ?? emailData.domainId ?? emailData.id ?? '';
182
182
  };
183
183
 
184
+ // CCS's domainGatewayMapId is the domainProperties API's outer `id`, distinct from domainProperties.id (the numeric domainId); it identifies the domain-gateway mapping for the selected domain.
185
+ const getEmailDomainGatewayMapIdByDomainId = (entity, domainId) => {
186
+ const domainEntry = getEmailDomainById(entity, domainId);
187
+ return domainEntry?.id ?? '';
188
+ };
189
+
184
190
  /** Domain name for summary display (parseEntityForDisplay) */
185
191
  const getEmailDomainNameForDisplay = (entity) => {
186
192
  const emailData = getEmailData(entity);
@@ -262,6 +268,8 @@ const getEmailDefaultsForDomain = (domainData) => {
262
268
  * context.fieldValues has current form state (e.g. selected emailDomain)
263
269
  * Aligned with adiona-ui/cap-campaigns-v2: default = contactInfo item with default:true, or first
264
270
  */
271
+ export { getEmailDomainGatewayMapIdByDomainId };
272
+
265
273
  export const getDefaultValueForField = (fieldKey, entity, context = {}) => {
266
274
  if (!entity) return '';
267
275
  const fieldValues = context?.fieldValues || {};
@@ -574,7 +582,10 @@ export const parseEntityForDisplay = (entity, context = {}) => {
574
582
  senderDetails: entity,
575
583
  smsSenderId: getSmsSenderIdValue(entity),
576
584
  smsDomain: getSmsDomainValue(entity),
585
+ // emailDomain here is the domain NAME (for display only) - buildChannelSettingFromFieldValues needs the numeric domainId/domainGatewayMapId, sourced separately below.
577
586
  emailDomain: getEmailDomainNameForDisplay(entity),
587
+ emailDomainId: getEmailDomainValue(entity),
588
+ emailDomainGatewayMapId: getEmailDomainGatewayMapIdByDomainId(entity, getEmailDomainValue(entity)),
578
589
  emailSenderId: getEmailSenderIdValue(entity),
579
590
  viberSenderId: getSenderIdFromContactInfo(entity, 'VIBER'),
580
591
  zaloSenderId: getSenderIdFromContactInfo(entity, 'ZALO'),
@@ -600,10 +611,12 @@ export const buildChannelSettingFromFieldValues = (fieldValues = {}) => {
600
611
  }
601
612
  if (fieldValues.emailDomain != null || fieldValues.emailSenderId != null || fieldValues.emailSenderName != null || fieldValues.emailReplyToId != null) {
602
613
  channelSetting.EMAIL = {
603
- domainId: fieldValues.emailDomain,
604
- senderEmail: fieldValues.emailSenderId,
614
+ // emailDomainId (numeric, from auto-save defaults via parseEntityForDisplay) takes precedence over emailDomain, which may be a numeric ID in SenderDetails state or a display name from parseEntityForDisplay.
615
+ domainId: fieldValues.emailDomainId ?? fieldValues.emailDomain,
616
+ domainGatewayMapId: fieldValues.emailDomainGatewayMapId,
617
+ senderId: fieldValues.emailSenderId,
605
618
  senderLabel: fieldValues.emailSenderName,
606
- senderReplyTo: fieldValues.emailReplyToId,
619
+ replyToId: fieldValues.emailReplyToId,
607
620
  };
608
621
  }
609
622
  if (fieldValues.whatsappSenderNumber != null || fieldValues.whatsappAccount != null) {
@@ -673,9 +686,10 @@ export const parseChannelSettingForDisplay = (channelSetting = {}) => {
673
686
  }
674
687
  if (channelSetting.EMAIL) {
675
688
  out.emailDomain = channelSetting.EMAIL.domainId;
676
- out.emailSenderId = channelSetting.EMAIL.senderEmail;
689
+ out.emailDomainGatewayMapId = channelSetting.EMAIL.domainGatewayMapId;
690
+ out.emailSenderId = channelSetting.EMAIL.senderId;
677
691
  out.emailSenderName = channelSetting.EMAIL.senderLabel;
678
- out.emailReplyToId = channelSetting.EMAIL.senderReplyTo;
692
+ out.emailReplyToId = channelSetting.EMAIL.replyToId;
679
693
  }
680
694
  if (channelSetting.WHATSAPP) {
681
695
  out.whatsappSenderNumber = channelSetting.WHATSAPP.senderMobNum;
@@ -2462,7 +2462,7 @@ export class Creatives extends React.Component {
2462
2462
  )}
2463
2463
  handleClose={this.handleCloseSlideBox}
2464
2464
  show={showSlideBox}
2465
- size="size-xl"
2465
+ size="size-l"
2466
2466
  />
2467
2467
  </SlideBoxWrapper>
2468
2468
  );
@@ -89,7 +89,7 @@ exports[`Test SlideBoxContent container campaign message, add creative click rcs
89
89
  />
90
90
  }
91
91
  show={true}
92
- size="size-xl"
92
+ size="size-l"
93
93
  />
94
94
  </CreativesSlideBoxWrapper>
95
95
  `;
@@ -183,7 +183,7 @@ exports[`Test SlideBoxContent container campaign message, add creative click wha
183
183
  />
184
184
  }
185
185
  show={true}
186
- size="size-xl"
186
+ size="size-l"
187
187
  />
188
188
  </CreativesSlideBoxWrapper>
189
189
  `;
@@ -320,7 +320,7 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit all data
320
320
  />
321
321
  }
322
322
  show={false}
323
- size="size-xl"
323
+ size="size-l"
324
324
  />
325
325
  </CreativesSlideBoxWrapper>
326
326
  `;
@@ -457,7 +457,7 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit min data
457
457
  />
458
458
  }
459
459
  show={false}
460
- size="size-xl"
460
+ size="size-l"
461
461
  />
462
462
  </CreativesSlideBoxWrapper>
463
463
  `;
@@ -594,7 +594,7 @@ exports[`Test SlideBoxContent container it should clear the url, on channel chan
594
594
  />
595
595
  }
596
596
  show={false}
597
- size="size-xl"
597
+ size="size-l"
598
598
  />
599
599
  </CreativesSlideBoxWrapper>
600
600
  `;