@capillarytech/creatives-library 9.0.50-alpha.0 → 9.0.50-alpha.2

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.
@@ -63,6 +63,7 @@ export const ENABLE_AI_SUGGESTIONS = 'ENABLE_AI_SUGGESTIONS';
63
63
  export const AI_CONTENT_BOT_DISABLED = 'AI_CONTENT_BOT_DISABLED';
64
64
  export const AI_DOCUMENTATION_BOT_ENABLED = 'AI_DOCUMENTATION_BOT_ENABLED';
65
65
  export const ENABLE_PRODUCT_SUPPORT_VIDEOS = 'ENABLE_PRODUCT_SUPPORT_VIDEOS';
66
+ export const SUPPORT_ENGAGEMENT_MODULE = 'SUPPORT_ENGAGEMENT_MODULE';
66
67
  export const EMBEDDED = 'embedded';
67
68
  // --- Tag/Validation Constants ---
68
69
  export const CARD_RELATED_TAGS = [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "9.0.50-alpha.0",
4
+ "version": "9.0.50-alpha.2",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -701,15 +701,6 @@ export const createCentralCommsMetaId = (payload, metaType = TRANSACTION) => {
701
701
  return request(url, getAPICallObject('POST', payload, false, false, false, true));
702
702
  };
703
703
 
704
- // CCS CommDefinition API (new Veyron /api/v1/commdefinition surface, proxied
705
- // through cap-creatives-api's /comm-definitions). Used by CommunicationFlow's
706
- // own Save action — separate from the legacy messageMeta functions above,
707
- // which remain in use by CreativesContainer's own save flow (Cap/sagas.js).
708
- export const createCommDefinition = (payload) => {
709
- const url = `${API_ENDPOINT}/comm-definitions`;
710
- return request(url, getAPICallObject('POST', payload, false, false, false, true));
711
- };
712
-
713
704
  export const getCentralCommsMetaIds = (metaIds, metaType = TRANSACTION) => {
714
705
  const url = `${API_ENDPOINT}/common/central-comms/meta-id/${metaType}?metaIds=${metaIds}`;
715
706
  return request(url, getAPICallObject('GET', null, false, false, false, true));
package/utils/common.js CHANGED
@@ -99,6 +99,10 @@ export const hasSupportCKEditor = Auth.hasFeatureAccess.bind(
99
99
  null,
100
100
  SUPPORT_CK_EDITOR,
101
101
  );
102
+ export const hasSupportEngagementModule = Auth.hasFeatureAccess.bind(
103
+ null,
104
+ SUPPORT_ENGAGEMENT_MODULE,
105
+ );
102
106
 
103
107
  export const hasGiftVoucherFeature = Auth.hasFeatureAccess.bind(
104
108
  null,
@@ -17,11 +17,10 @@ import { createStructuredSelector } from 'reselect';
17
17
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
18
18
  import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
19
19
  import CapButton from '@capillarytech/cap-ui-library/CapButton';
20
- import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
21
20
  // import injectSaga from '../../utils/injectSaga'; // cap-coupons flows disabled
22
21
  // import injectReducer from '../../utils/injectReducer';
23
22
  import { makeSelectAuthenticated } from '../Cap/selectors';
24
- import { createCommDefinition } from '../../services/api';
23
+ import { createCentralCommsMetaId, getCentralCommsMetaIds } from '../../services/api';
25
24
  import DynamicControlsStep from './steps/DynamicControlsStep';
26
25
  import MessageTypeStep from './steps/MessageTypeStep';
27
26
  import CommunicationStrategyStep from './steps/CommunicationStrategyStep';
@@ -36,9 +35,9 @@ import {
36
35
  CHANNELS,
37
36
  INCENTIVE_TYPES,
38
37
  DYNAMIC_CONTROLS_CONFIG,
39
- CCS_STRATEGY_TYPE_SINGLE,
40
- CCS_CHANNEL_CONTENT_KEY_MAP,
41
- CCS_CHANNEL_DELIVERY_KEY_MAP,
38
+ CHANNEL_CONTENT_KEY_MAP,
39
+ CHANNEL_DELIVERY_KEY_MAP,
40
+ CAMPAIGNS,
42
41
  } from './constants';
43
42
  import { getEnabledSteps } from './utils/getEnabledSteps';
44
43
  import messages from './messages';
@@ -65,25 +64,6 @@ const hasDeliverySettingForChannel = (channelSetting, channel) => {
65
64
  return !!settings && Object.values(settings).some((v) => v !== null && v !== '' && v !== undefined);
66
65
  };
67
66
 
68
- // createCommDefinition resolves (rather than rejects) with the CCS error envelope
69
- // for 4xx/5xx responses — see api.js's request()/checkStatus. A duplicate
70
- // referenceId within the org comes back as this specific 409.
71
- const isDuplicateReferenceIdError = (res) => res?.success === false && res?.status?.message === 'REFERENCE_ID_EXISTS';
72
-
73
- /**
74
- * CCS requires referenceId on create; the consumer-supplied Alert/comm name is
75
- * mandatory but referenceId is optional in every consumer's own form. Generate
76
- * a stable fallback from the name rather than failing the save.
77
- */
78
- const buildCcsReferenceId = (name) => {
79
- const slug = (name || 'COMM')
80
- .trim()
81
- .toUpperCase()
82
- .replace(/[^A-Z0-9]+/g, '_')
83
- .replace(/^_+|_+$/g, '') || 'COMM';
84
- return `${slug}_${Date.now()}`;
85
- };
86
-
87
67
  const CommunicationFlow = ({
88
68
  config,
89
69
  initialData,
@@ -111,7 +91,6 @@ const CommunicationFlow = ({
111
91
  };
112
92
  });
113
93
  const [validationErrors, setValidationErrors] = useState({});
114
- const [saveError, setSaveError] = useState(null);
115
94
 
116
95
  // Memoize enabled steps
117
96
  const enabledSteps = useMemo(() => getEnabledSteps(config), [config]);
@@ -171,75 +150,74 @@ const CommunicationFlow = ({
171
150
  }, []);
172
151
 
173
152
  const handleSave = useCallback(async () => {
174
- setSaveError(null);
175
153
  const aggregatedData = getAggregatedData();
176
154
  const shouldUseCCS = config?.useCCS !== false;
177
- let ccsCommDefinition = null;
178
155
 
179
156
  if (shouldUseCCS) {
180
- const isMultiChannel = [CHANNEL_PRIORITY, AB_TEST].includes(aggregatedData.communicationStrategy);
181
- const contentItem = (aggregatedData.contentItems || [])[0];
182
- // Consumer-supplied name (e.g. CapNotify's Alert Name field) — CommunicationFlow
183
- // has no name input of its own, so this is mandatory input from config.context.
184
- const name = config?.context?.name;
157
+ const ouId = config?.context?.ouId || -1;
158
+ const module = config?.context?.module
159
+ || (config?.consumer ? config.consumer.toUpperCase() : CAMPAIGNS);
185
160
 
186
- // CCS create is SINGLE-strategy only this phase (D1); CHANNEL_PRIORITY/AB_TEST
187
- // carry multiple content items with no CCS equivalent yet.
188
- if (!isMultiChannel && contentItem && name) {
189
- const channel = (contentItem.channel || '').toUpperCase();
190
- const contentKey = CCS_CHANNEL_CONTENT_KEY_MAP[channel];
191
- const deliveryKey = CCS_CHANNEL_DELIVERY_KEY_MAP[channel];
192
- const { dynamicControls = {} } = aggregatedData;
193
- const channelSettings = aggregatedData.deliverySetting?.channelSetting?.[channel] || {};
194
- const referenceId = config?.context?.referenceId || buildCcsReferenceId(name);
161
+ const channelContentKeyMap = CHANNEL_CONTENT_KEY_MAP;
162
+ const channelDeliveryKeyMap = CHANNEL_DELIVERY_KEY_MAP;
195
163
 
196
- const payload = {
197
- referenceId,
198
- name,
199
- description: config?.context?.description || undefined,
200
- strategyType: CCS_STRATEGY_TYPE_SINGLE,
201
- settings: {
202
- additionalSettings: {
203
- useTinyUrl: dynamicControls.useTinyUrl ?? false,
204
- encryptUrl: dynamicControls.sendToControlCustomers ?? false,
205
- linkTrackingEnabled: dynamicControls.overrideDailyLimit ?? false,
206
- userSubscriptionDisabled: dynamicControls.sendToBrandPocs ?? false,
207
- },
208
- executionParams: {},
209
- },
210
- singleChannelStrategy: {
211
- channel,
212
- ...(contentKey && { [contentKey]: contentItem.templateData }),
213
- ...(deliveryKey && { [deliveryKey]: { channelSettings: { channel, ...channelSettings } } }),
214
- },
215
- };
164
+ const contentItems = aggregatedData.contentItems || [];
165
+ const { dynamicControls = {} } = aggregatedData;
216
166
 
167
+ const additionalSettings = {
168
+ useTinyUrl: dynamicControls.useTinyUrl ?? false,
169
+ encryptUrl: dynamicControls.sendToControlCustomers ?? false,
170
+ linkTrackingEnabled: dynamicControls.overrideDailyLimit ?? false,
171
+ userSubscriptionDisabled: dynamicControls.sendToBrandPocs ?? false,
172
+ };
173
+
174
+ if (contentItems.length > 0) {
217
175
  try {
218
- const res = await createCommDefinition(payload);
219
- if (isDuplicateReferenceIdError(res)) {
220
- // Duplicate referenceId in this org — block the save so the user can
221
- // change it, rather than silently proceeding without a CCS comm.
222
- setSaveError(formatMessage(messages.duplicateReferenceIdError));
223
- return;
224
- }
225
- const data = res?.response?.data;
226
- if (data?.id) {
227
- ccsCommDefinition = {
228
- id: data.id,
229
- referenceId: data.referenceId,
230
- version: data.version?.version || 1,
231
- status: data.status,
232
- };
176
+ const responses = await Promise.all(
177
+ contentItems.map((item) => {
178
+ const channel = (item.channel || '').toUpperCase();
179
+ const contentKey = channelContentKeyMap[channel];
180
+ const deliveryKey = channelDeliveryKeyMap[channel];
181
+ const payload = {
182
+ centralCommsPayload: {
183
+ ouId,
184
+ channel,
185
+ module,
186
+ executionParams: {},
187
+ clientName: 'EMF',
188
+ ...(contentKey && {
189
+ [contentKey]: { channel, ...item.templateData },
190
+ }),
191
+ ...(deliveryKey && {
192
+ [deliveryKey]: {
193
+ additionalSettings,
194
+ channelSettings: {
195
+ channel,
196
+ ...(aggregatedData.deliverySetting?.channelSetting?.[channel] || {}),
197
+ },
198
+ },
199
+ }),
200
+ },
201
+ };
202
+ return createCentralCommsMetaId(payload);
203
+ }),
204
+ );
205
+
206
+ const metaIds = responses
207
+ .map((res) => res?.response?.data?.id)
208
+ .filter(Boolean)
209
+ .join(',');
210
+
211
+ if (metaIds) {
212
+ const getResponse = await getCentralCommsMetaIds(metaIds);
233
213
  }
234
214
  } catch (error) {
235
- console.error('[CommunicationFlow] CCS createCommDefinition error:', error);
215
+ console.error('[CommunicationFlow] CCS createCentralCommsMetaId error:', error);
236
216
  }
237
- } else if (!name) {
238
- console.warn('[CommunicationFlow] Skipping CCS createCommDefinition — config.context.name is required');
239
217
  }
240
218
  }
241
219
 
242
- onSave(ccsCommDefinition ? { ...aggregatedData, ccsCommDefinition } : aggregatedData);
220
+ onSave(aggregatedData);
243
221
  }, [getAggregatedData, config, onSave]);
244
222
 
245
223
  // Call onChange callback when stepData changes
@@ -339,11 +317,6 @@ const CommunicationFlow = ({
339
317
  {renderSteps()}
340
318
  {onSave && (
341
319
  <CapRow useLegacy className="communication-flow-container__footer">
342
- {saveError && (
343
- <CapLabel type="label2" className="communication-flow-container__save-error">
344
- {saveError}
345
- </CapLabel>
346
- )}
347
320
  <CapButton type="primary" onClick={handleSave} disabled={isSaveDisabled}>
348
321
  {formatMessage(messages.save)}
349
322
  </CapButton>
@@ -19,7 +19,6 @@ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
19
19
  import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
20
20
  import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
21
21
  import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
22
- import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
23
22
  import CommunicationFlow from './index';
24
23
  import { CHANNELS, DEFAULT_COMMUNICATION_STRATEGY_OPTIONS, DYNAMIC_CONTROLS_CONFIG } from './constants';
25
24
  import {
@@ -75,8 +74,6 @@ const CommunicationFlowCard = ({
75
74
  onChange,
76
75
  cap,
77
76
  intl,
78
- disabled,
79
- disabledTooltip,
80
77
  }) => {
81
78
  const { formatMessage } = intl || {};
82
79
  const [showSlideBox, setShowSlideBox] = useState(false);
@@ -94,9 +91,8 @@ const CommunicationFlowCard = ({
94
91
  }, [onCancel]);
95
92
 
96
93
  const handleOpen = useCallback(() => {
97
- if (disabled) return;
98
94
  setShowSlideBox(true);
99
- }, [disabled]);
95
+ }, []);
100
96
 
101
97
  const firstItem = savedData?.contentItems?.[0];
102
98
  const channelConfig = firstItem
@@ -186,17 +182,9 @@ const CommunicationFlowCard = ({
186
182
  <CapImage src={addCreativesIllustration} />
187
183
  </CapColumn>
188
184
  <CapColumn span={14} className="empty-card-action-col">
189
- {disabled && disabledTooltip ? (
190
- <CapTooltip title={disabledTooltip}>
191
- <CapButton type="secondary" disabled>
192
- {formatMessage(messages.addCreatives)}
193
- </CapButton>
194
- </CapTooltip>
195
- ) : (
196
- <CapButton type="secondary" onClick={handleOpen} disabled={disabled}>
197
- {formatMessage(messages.addCreatives)}
198
- </CapButton>
199
- )}
185
+ <CapButton type="secondary" onClick={handleOpen}>
186
+ {formatMessage(messages.addCreatives)}
187
+ </CapButton>
200
188
  </CapColumn>
201
189
  </CapRow>
202
190
  </CapCard>
@@ -239,8 +227,6 @@ CommunicationFlowCard.propTypes = {
239
227
  onChange: PropTypes.func,
240
228
  cap: PropTypes.object,
241
229
  intl: PropTypes.object.isRequired,
242
- disabled: PropTypes.bool, // Disables the "Add creatives" trigger (e.g. until a consumer-required field is filled)
243
- disabledTooltip: PropTypes.node, // Shown on hover when disabled is true
244
230
  };
245
231
 
246
232
  CommunicationFlowCard.defaultProps = {
@@ -249,8 +235,6 @@ CommunicationFlowCard.defaultProps = {
249
235
  onCancel: null,
250
236
  onChange: null,
251
237
  cap: null,
252
- disabled: false,
253
- disabledTooltip: null,
254
238
  };
255
239
 
256
240
  export default injectIntl(CommunicationFlowCard);
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
 
3
3
  jest.mock('../../../services/api', () => ({
4
- createCommDefinition: jest.fn(),
4
+ createCentralCommsMetaId: jest.fn(),
5
+ getCentralCommsMetaIds: jest.fn(),
5
6
  }));
6
7
 
7
8
  jest.mock('../../CreativesContainer', () => function MockCreativesContainer({
@@ -37,7 +38,7 @@ import { IntlProvider } from 'react-intl';
37
38
  import history from '../../../utils/history';
38
39
  import { initialReducer } from '../../../initialReducer';
39
40
  import CommunicationFlow from '../CommunicationFlow';
40
- import { createCommDefinition } from '../../../services/api';
41
+ import { createCentralCommsMetaId, getCentralCommsMetaIds } from '../../../services/api';
41
42
  import { getEnabledSteps } from '../utils/getEnabledSteps';
42
43
  import {
43
44
  CHANNELS,
@@ -358,93 +359,74 @@ describe('isSaveDisabled', () => {
358
359
  });
359
360
 
360
361
  describe('handleSave — CCS flow', () => {
361
- const ccsConfig = { ...baseConfig, context: { name: 'Order Placed Notification' }, features: {} };
362
-
363
362
  beforeEach(() => {
364
- createCommDefinition.mockResolvedValue({
365
- response: { data: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', status: 'DRAFT', version: { version: 1 } } },
366
- });
363
+ createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'meta-123' } } });
364
+ getCentralCommsMetaIds.mockResolvedValue({ response: { data: {} } });
367
365
  });
368
366
 
369
367
  afterEach(() => {
370
368
  jest.clearAllMocks();
371
369
  });
372
370
 
373
- it('calls createCommDefinition with a SINGLE-strategy payload when useCCS is not false', async () => {
371
+ it('calls createCentralCommsMetaId for each content item when useCCS is not false', async () => {
374
372
  const onSave = jest.fn();
375
373
  renderWithFlow({
376
374
  features: {},
377
- config: ccsConfig,
378
375
  initialData: {
379
- contentItems: [{ channel: 'sms', templateData: { message: 'Hello' } }],
376
+ contentItems: [{ channel: 'sms', templateData: { smsBody: 'Hello' } }],
380
377
  },
381
378
  onSave,
382
379
  });
383
380
 
384
381
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
385
382
 
386
- await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
387
- expect(createCommDefinition).toHaveBeenCalledWith(
383
+ await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalledTimes(1));
384
+ expect(createCentralCommsMetaId).toHaveBeenCalledWith(
388
385
  expect.objectContaining({
389
- name: 'Order Placed Notification',
390
- strategyType: 'SINGLE',
391
- singleChannelStrategy: expect.objectContaining({
392
- channel: 'SMS',
393
- smsMessageContent: { message: 'Hello' },
394
- }),
386
+ centralCommsPayload: expect.objectContaining({ channel: 'SMS', module: 'CAMPAIGNS' }),
395
387
  }),
396
388
  );
397
389
  expect(onSave).toHaveBeenCalledTimes(1);
398
390
  });
399
391
 
400
- it('merges ccsCommDefinition into the data passed to onSave when create succeeds', async () => {
401
- const onSave = jest.fn();
392
+ it('calls getCentralCommsMetaIds when metaIds are returned from createCentralCommsMetaId', async () => {
402
393
  renderWithFlow({
403
394
  features: {},
404
- config: ccsConfig,
405
- initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
406
- onSave,
395
+ initialData: { contentItems: [{ channel: 'EMAIL', templateData: {} }] },
407
396
  });
408
397
 
409
398
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
410
399
 
411
- await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
412
- expect(onSave).toHaveBeenCalledWith(
413
- expect.objectContaining({
414
- ccsCommDefinition: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', version: 1, status: 'DRAFT' },
415
- }),
416
- );
400
+ await waitFor(() => expect(getCentralCommsMetaIds).toHaveBeenCalledWith('meta-123'));
417
401
  });
418
402
 
419
- it('generates a referenceId from name when config.context.referenceId is absent', async () => {
420
- // moduleMocks.js spies on Date.now globally, but this config's resetMocks:true
421
- // clears that return value before every test — set it explicitly here.
422
- jest.spyOn(Date, 'now').mockReturnValue(1612267539410);
403
+ it('skips getCentralCommsMetaIds when response contains no id', async () => {
404
+ createCentralCommsMetaId.mockResolvedValue({ response: { data: {} } });
423
405
  renderWithFlow({
424
406
  features: {},
425
- config: ccsConfig,
426
407
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
427
408
  });
428
409
 
429
410
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
430
411
 
431
- await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
432
- const payload = createCommDefinition.mock.calls[0][0];
433
- expect(payload.referenceId).toBe('ORDER_PLACED_NOTIFICATION_1612267539410');
412
+ await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
413
+ expect(getCentralCommsMetaIds).not.toHaveBeenCalled();
434
414
  });
435
415
 
436
- it('uses config.context.referenceId and description when provided', async () => {
416
+ it('uses ouId and module from config.context when provided', async () => {
437
417
  renderWithFlow({
438
418
  features: {},
439
- config: { ...ccsConfig, context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', description: 'desc' } },
419
+ config: { ...baseConfig, context: { ouId: 42, module: 'LOYALTY' }, features: {} },
440
420
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
441
421
  });
442
422
 
443
423
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
444
424
 
445
- await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
446
- expect(createCommDefinition).toHaveBeenCalledWith(
447
- expect.objectContaining({ referenceId: 'ORDER_PLACED', description: 'desc' }),
425
+ await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
426
+ expect(createCentralCommsMetaId).toHaveBeenCalledWith(
427
+ expect.objectContaining({
428
+ centralCommsPayload: expect.objectContaining({ ouId: 42, module: 'LOYALTY' }),
429
+ }),
448
430
  );
449
431
  });
450
432
 
@@ -452,7 +434,7 @@ describe('handleSave — CCS flow', () => {
452
434
  const onSave = jest.fn();
453
435
  renderWithFlow({
454
436
  features: {},
455
- config: { ...ccsConfig, useCCS: false },
437
+ config: { ...baseConfig, useCCS: false, features: {} },
456
438
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
457
439
  onSave,
458
440
  });
@@ -460,50 +442,14 @@ describe('handleSave — CCS flow', () => {
460
442
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
461
443
 
462
444
  await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
463
- expect(createCommDefinition).not.toHaveBeenCalled();
464
- expect(onSave).toHaveBeenCalledWith(expect.not.objectContaining({ ccsCommDefinition: expect.anything() }));
465
- });
466
-
467
- it('skips createCommDefinition when config.context.name is absent (mandatory)', async () => {
468
- const onSave = jest.fn();
469
- renderWithFlow({
470
- features: {},
471
- config: { ...baseConfig, features: {} },
472
- initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
473
- onSave,
474
- });
475
-
476
- await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
477
-
478
- await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
479
- expect(createCommDefinition).not.toHaveBeenCalled();
480
- });
481
-
482
- it('skips createCommDefinition for multi-channel strategies (CHANNEL_PRIORITY/AB_TEST)', async () => {
483
- const onSave = jest.fn();
484
- renderWithFlow({
485
- features: {},
486
- config: ccsConfig,
487
- initialData: {
488
- communicationStrategy: CHANNEL_PRIORITY,
489
- channels: ['SMS', 'EMAIL'],
490
- contentItems: [{ channel: 'SMS', templateData: {} }, { channel: 'EMAIL', templateData: {} }],
491
- },
492
- onSave,
493
- });
494
-
495
- await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
496
-
497
- await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
498
- expect(createCommDefinition).not.toHaveBeenCalled();
445
+ expect(createCentralCommsMetaId).not.toHaveBeenCalled();
499
446
  });
500
447
 
501
- it('still calls onSave (without ccsCommDefinition) when createCommDefinition rejects', async () => {
502
- createCommDefinition.mockRejectedValue(new Error('Network error'));
448
+ it('still calls onSave when createCentralCommsMetaId rejects', async () => {
449
+ createCentralCommsMetaId.mockRejectedValue(new Error('Network error'));
503
450
  const onSave = jest.fn();
504
451
  renderWithFlow({
505
452
  features: {},
506
- config: ccsConfig,
507
453
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
508
454
  onSave,
509
455
  });
@@ -511,35 +457,12 @@ describe('handleSave — CCS flow', () => {
511
457
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
512
458
 
513
459
  await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
514
- expect(onSave).toHaveBeenCalledWith(expect.not.objectContaining({ ccsCommDefinition: expect.anything() }));
515
460
  });
516
461
 
517
- it('blocks save and shows an error when createCommDefinition resolves with a duplicate REFERENCE_ID_EXISTS conflict', async () => {
518
- createCommDefinition.mockResolvedValue({
519
- success: false,
520
- status: { isError: true, code: 409, message: 'REFERENCE_ID_EXISTS' },
521
- message: 'REFERENCE_ID_EXISTS',
522
- });
462
+ it('skips createCentralCommsMetaId when contentItems is empty', async () => {
523
463
  const onSave = jest.fn();
524
464
  renderWithFlow({
525
465
  features: {},
526
- config: ccsConfig,
527
- initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
528
- onSave,
529
- });
530
-
531
- await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
532
-
533
- await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
534
- expect(await screen.findByText(/already in use in your organization/i)).toBeInTheDocument();
535
- expect(onSave).not.toHaveBeenCalled();
536
- });
537
-
538
- it('skips createCommDefinition when contentItems is empty', async () => {
539
- const onSave = jest.fn();
540
- renderWithFlow({
541
- features: {},
542
- config: ccsConfig,
543
466
  initialData: { contentItems: [] },
544
467
  onSave,
545
468
  });
@@ -547,13 +470,12 @@ describe('handleSave — CCS flow', () => {
547
470
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
548
471
 
549
472
  await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
550
- expect(createCommDefinition).not.toHaveBeenCalled();
473
+ expect(createCentralCommsMetaId).not.toHaveBeenCalled();
551
474
  });
552
475
 
553
- it('places additionalSettings derived from dynamicControls under settings.additionalSettings (not delivery settings)', async () => {
476
+ it('includes additionalSettings derived from dynamicControls in the payload', async () => {
554
477
  renderWithFlow({
555
478
  features: {},
556
- config: ccsConfig,
557
479
  initialData: {
558
480
  contentItems: [{ channel: 'SMS', templateData: {} }],
559
481
  dynamicControls: {
@@ -567,36 +489,14 @@ describe('handleSave — CCS flow', () => {
567
489
 
568
490
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
569
491
 
570
- await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
571
- const payload = createCommDefinition.mock.calls[0][0];
572
- expect(payload.settings.additionalSettings).toEqual({
492
+ await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
493
+ const payload = createCentralCommsMetaId.mock.calls[0][0];
494
+ expect(payload.centralCommsPayload.smsDeliverySettings.additionalSettings).toEqual({
573
495
  useTinyUrl: true,
574
496
  encryptUrl: true,
575
497
  linkTrackingEnabled: true,
576
498
  userSubscriptionDisabled: true,
577
499
  });
578
- expect(payload.singleChannelStrategy.smsDeliverySettings.additionalSettings).toBeUndefined();
579
- });
580
-
581
- it('includes channelSettings from deliverySetting.channelSetting in the payload', async () => {
582
- renderWithFlow({
583
- features: {},
584
- config: ccsConfig,
585
- initialData: {
586
- contentItems: [{ channel: 'SMS', templateData: {} }],
587
- deliverySetting: { channelSetting: { SMS: { domainId: 1001, gsmSenderId: 'BRAND1' } } },
588
- },
589
- });
590
-
591
- await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
592
-
593
- await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
594
- const payload = createCommDefinition.mock.calls[0][0];
595
- expect(payload.singleChannelStrategy.smsDeliverySettings.channelSettings).toEqual({
596
- channel: 'SMS',
597
- domainId: 1001,
598
- gsmSenderId: 'BRAND1',
599
- });
600
500
  });
601
501
  });
602
502
 
@@ -652,34 +552,39 @@ describe('optional chaining safety', () => {
652
552
  jest.clearAllMocks();
653
553
  });
654
554
 
655
- it('does not crash and skips CCS when config.context is entirely absent', async () => {
656
- const onSave = jest.fn();
555
+ it('defaults ouId to -1 when config.context is absent', async () => {
556
+ createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'x' } } });
657
557
  renderWithFlow({
658
558
  features: {},
659
- config: { ...baseConfig, features: {} },
660
559
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
661
- onSave,
662
560
  });
663
561
 
664
562
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
665
563
 
666
- await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
667
- expect(createCommDefinition).not.toHaveBeenCalled();
564
+ await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
565
+ expect(createCentralCommsMetaId).toHaveBeenCalledWith(
566
+ expect.objectContaining({
567
+ centralCommsPayload: expect.objectContaining({ ouId: -1 }),
568
+ }),
569
+ );
668
570
  });
669
571
 
670
- it('does not crash when config.context is present but empty', async () => {
671
- const onSave = jest.fn();
572
+ it('defaults module to consumer.toUpperCase() when config.context.module absent', async () => {
573
+ createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'x' } } });
672
574
  renderWithFlow({
673
575
  features: {},
674
- config: { ...baseConfig, context: {}, features: {} },
576
+ config: { ...baseConfig, consumer: 'loyalty', features: {} },
675
577
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
676
- onSave,
677
578
  });
678
579
 
679
580
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
680
581
 
681
- await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
682
- expect(createCommDefinition).not.toHaveBeenCalled();
582
+ await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
583
+ expect(createCentralCommsMetaId).toHaveBeenCalledWith(
584
+ expect.objectContaining({
585
+ centralCommsPayload: expect.objectContaining({ module: 'LOYALTY' }),
586
+ }),
587
+ );
683
588
  });
684
589
 
685
590
  it('initializes channel from config.channel when initialData.channel is absent', () => {