@capillarytech/creatives-library 9.0.54-0 → 9.0.54-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.
@@ -1,8 +1,7 @@
1
1
  import React from 'react';
2
2
 
3
3
  jest.mock('../../../services/api', () => ({
4
- createCentralCommsMetaId: jest.fn(),
5
- getCentralCommsMetaIds: jest.fn(),
4
+ createCommDefinition: jest.fn(),
6
5
  }));
7
6
 
8
7
  jest.mock('../../CreativesContainer', () => function MockCreativesContainer({
@@ -38,7 +37,7 @@ import { IntlProvider } from 'react-intl';
38
37
  import history from '../../../utils/history';
39
38
  import { initialReducer } from '../../../initialReducer';
40
39
  import CommunicationFlow from '../CommunicationFlow';
41
- import { createCentralCommsMetaId, getCentralCommsMetaIds } from '../../../services/api';
40
+ import { createCommDefinition } from '../../../services/api';
42
41
  import { getEnabledSteps } from '../utils/getEnabledSteps';
43
42
  import {
44
43
  CHANNELS,
@@ -359,82 +358,200 @@ describe('isSaveDisabled', () => {
359
358
  });
360
359
 
361
360
  describe('handleSave — CCS flow', () => {
361
+ const ccsConfig = { ...baseConfig, context: { name: 'Order Placed Notification' }, features: {} };
362
+
362
363
  beforeEach(() => {
363
- createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'meta-123' } } });
364
- getCentralCommsMetaIds.mockResolvedValue({ response: { data: {} } });
364
+ createCommDefinition.mockResolvedValue({
365
+ response: { data: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', status: 'DRAFT', version: { version: 1 } } },
366
+ });
365
367
  });
366
368
 
367
369
  afterEach(() => {
368
370
  jest.clearAllMocks();
369
371
  });
370
372
 
371
- it('calls createCentralCommsMetaId for each content item when useCCS is not false', async () => {
373
+ it('calls createCommDefinition with a SINGLE-strategy payload when useCCS is not false', async () => {
372
374
  const onSave = jest.fn();
373
375
  renderWithFlow({
374
376
  features: {},
377
+ config: ccsConfig,
375
378
  initialData: {
376
- contentItems: [{ channel: 'sms', templateData: { smsBody: 'Hello' } }],
379
+ contentItems: [{ channel: 'sms', templateData: { message: 'Hello' } }],
377
380
  },
378
381
  onSave,
379
382
  });
380
383
 
381
384
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
382
385
 
383
- await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalledTimes(1));
384
- expect(createCentralCommsMetaId).toHaveBeenCalledWith(
386
+ await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
387
+ expect(createCommDefinition).toHaveBeenCalledWith(
385
388
  expect.objectContaining({
386
- centralCommsPayload: expect.objectContaining({ channel: 'SMS', module: 'CAMPAIGNS' }),
389
+ name: 'Order Placed Notification',
390
+ strategyType: 'SINGLE',
391
+ singleChannelStrategy: expect.objectContaining({
392
+ variant: expect.objectContaining({
393
+ channel: 'SMS',
394
+ smsMessageContent: { channel: 'SMS', message: 'Hello' },
395
+ }),
396
+ }),
387
397
  }),
388
398
  );
389
399
  expect(onSave).toHaveBeenCalledTimes(1);
390
400
  });
391
401
 
392
- it('calls getCentralCommsMetaIds when metaIds are returned from createCentralCommsMetaId', async () => {
402
+ it('transforms EMAIL content from the legacy editor shape (emailSubject) to CCS\'s messageSubject', async () => {
403
+ const onSave = jest.fn();
393
404
  renderWithFlow({
394
405
  features: {},
395
- initialData: { contentItems: [{ channel: 'EMAIL', templateData: {} }] },
406
+ config: ccsConfig,
407
+ initialData: {
408
+ contentItems: [{
409
+ channel: 'EMAIL',
410
+ templateData: { emailSubject: 'Order Confirmed', messageBody: '<html>Hi</html>' },
411
+ }],
412
+ },
413
+ onSave,
396
414
  });
397
415
 
398
416
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
399
417
 
400
- await waitFor(() => expect(getCentralCommsMetaIds).toHaveBeenCalledWith('meta-123'));
418
+ await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
419
+ expect(createCommDefinition).toHaveBeenCalledWith(
420
+ expect.objectContaining({
421
+ singleChannelStrategy: expect.objectContaining({
422
+ variant: expect.objectContaining({
423
+ channel: 'EMAIL',
424
+ emailMessageContent: {
425
+ channel: 'EMAIL',
426
+ messageSubject: 'Order Confirmed',
427
+ messageBody: '<html>Hi</html>',
428
+ },
429
+ }),
430
+ }),
431
+ }),
432
+ );
401
433
  });
402
434
 
403
- it('skips getCentralCommsMetaIds when response contains no id', async () => {
404
- createCentralCommsMetaId.mockResolvedValue({ response: { data: {} } });
435
+ it('normalizes the internal MOBILEPUSH channel to CCS\'s PUSH enum (keys stay mpush*)', async () => {
436
+ const onSave = jest.fn();
405
437
  renderWithFlow({
406
438
  features: {},
439
+ config: ccsConfig,
440
+ initialData: {
441
+ contentItems: [{ channel: 'MOBILEPUSH', templateData: { messageSubject: 'Hi' } }],
442
+ },
443
+ onSave,
444
+ });
445
+
446
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
447
+
448
+ await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
449
+ expect(createCommDefinition).toHaveBeenCalledWith(
450
+ expect.objectContaining({
451
+ singleChannelStrategy: expect.objectContaining({
452
+ variant: expect.objectContaining({
453
+ channel: 'PUSH',
454
+ mpushMessageContent: { channel: 'PUSH', messageSubject: 'Hi' },
455
+ }),
456
+ }),
457
+ }),
458
+ );
459
+ });
460
+
461
+ it('merges ccsCommDefinition into the data passed to onSave when create succeeds', async () => {
462
+ const onSave = jest.fn();
463
+ renderWithFlow({
464
+ features: {},
465
+ config: ccsConfig,
407
466
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
467
+ onSave,
408
468
  });
409
469
 
410
470
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
411
471
 
412
- await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
413
- expect(getCentralCommsMetaIds).not.toHaveBeenCalled();
472
+ await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
473
+ expect(onSave).toHaveBeenCalledWith(
474
+ expect.objectContaining({
475
+ ccsCommDefinition: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', version: 1, status: 'DRAFT' },
476
+ }),
477
+ );
414
478
  });
415
479
 
416
- it('uses ouId and module from config.context when provided', async () => {
480
+ it('preserves a real version of 0 from CCS (not coerced to 1 by a falsy-zero fallback)', async () => {
481
+ const onSave = jest.fn();
482
+ createCommDefinition.mockResolvedValueOnce({
483
+ response: { data: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', status: 'DRAFT', version: { version: 0 } } },
484
+ });
417
485
  renderWithFlow({
418
486
  features: {},
419
- config: { ...baseConfig, context: { ouId: 42, module: 'LOYALTY' }, features: {} },
487
+ config: ccsConfig,
420
488
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
489
+ onSave,
421
490
  });
422
491
 
423
492
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
424
493
 
425
- await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
426
- expect(createCentralCommsMetaId).toHaveBeenCalledWith(
494
+ await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
495
+ expect(onSave).toHaveBeenCalledWith(
427
496
  expect.objectContaining({
428
- centralCommsPayload: expect.objectContaining({ ouId: 42, module: 'LOYALTY' }),
497
+ ccsCommDefinition: { id: 'cd_123', referenceId: 'ORDER_PLACED_123', version: 0, status: 'DRAFT' },
429
498
  }),
430
499
  );
431
500
  });
432
501
 
502
+ it('generates a referenceId from name when config.context.referenceId is absent', async () => {
503
+ // moduleMocks.js spies on Date.now globally, but this config's resetMocks:true
504
+ // clears that return value before every test — set it explicitly here.
505
+ jest.spyOn(Date, 'now').mockReturnValue(1612267539410);
506
+ renderWithFlow({
507
+ features: {},
508
+ config: ccsConfig,
509
+ initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
510
+ });
511
+
512
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
513
+
514
+ await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
515
+ const payload = createCommDefinition.mock.calls[0][0];
516
+ expect(payload.referenceId).toBe('ORDER_PLACED_NOTIFICATION_1612267539410');
517
+ });
518
+
519
+ it('uses config.context.referenceId and description when provided', async () => {
520
+ renderWithFlow({
521
+ features: {},
522
+ config: { ...ccsConfig, context: { ...ccsConfig.context, referenceId: 'ORDER_PLACED', description: 'desc' } },
523
+ initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
524
+ });
525
+
526
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
527
+
528
+ await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
529
+ expect(createCommDefinition).toHaveBeenCalledWith(
530
+ expect.objectContaining({ referenceId: 'ORDER_PLACED', description: 'desc' }),
531
+ );
532
+ });
533
+
433
534
  it('skips CCS entirely when useCCS is false', async () => {
434
535
  const onSave = jest.fn();
435
536
  renderWithFlow({
436
537
  features: {},
437
- config: { ...baseConfig, useCCS: false, features: {} },
538
+ config: { ...ccsConfig, useCCS: false },
539
+ initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
540
+ onSave,
541
+ });
542
+
543
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
544
+
545
+ await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
546
+ expect(createCommDefinition).not.toHaveBeenCalled();
547
+ expect(onSave).toHaveBeenCalledWith(expect.not.objectContaining({ ccsCommDefinition: expect.anything() }));
548
+ });
549
+
550
+ it('skips createCommDefinition when config.context.name is absent (mandatory)', async () => {
551
+ const onSave = jest.fn();
552
+ renderWithFlow({
553
+ features: {},
554
+ config: { ...baseConfig, features: {} },
438
555
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
439
556
  onSave,
440
557
  });
@@ -442,14 +559,34 @@ describe('handleSave — CCS flow', () => {
442
559
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
443
560
 
444
561
  await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
445
- expect(createCentralCommsMetaId).not.toHaveBeenCalled();
562
+ expect(createCommDefinition).not.toHaveBeenCalled();
446
563
  });
447
564
 
448
- it('still calls onSave when createCentralCommsMetaId rejects', async () => {
449
- createCentralCommsMetaId.mockRejectedValue(new Error('Network error'));
565
+ it('skips createCommDefinition for multi-channel strategies (CHANNEL_PRIORITY/AB_TEST)', async () => {
450
566
  const onSave = jest.fn();
451
567
  renderWithFlow({
452
568
  features: {},
569
+ config: ccsConfig,
570
+ initialData: {
571
+ communicationStrategy: CHANNEL_PRIORITY,
572
+ channels: ['SMS', 'EMAIL'],
573
+ contentItems: [{ channel: 'SMS', templateData: {} }, { channel: 'EMAIL', templateData: {} }],
574
+ },
575
+ onSave,
576
+ });
577
+
578
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
579
+
580
+ await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
581
+ expect(createCommDefinition).not.toHaveBeenCalled();
582
+ });
583
+
584
+ it('still calls onSave (without ccsCommDefinition) when createCommDefinition rejects', async () => {
585
+ createCommDefinition.mockRejectedValue(new Error('Network error'));
586
+ const onSave = jest.fn();
587
+ renderWithFlow({
588
+ features: {},
589
+ config: ccsConfig,
453
590
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
454
591
  onSave,
455
592
  });
@@ -457,12 +594,35 @@ describe('handleSave — CCS flow', () => {
457
594
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
458
595
 
459
596
  await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
597
+ expect(onSave).toHaveBeenCalledWith(expect.not.objectContaining({ ccsCommDefinition: expect.anything() }));
598
+ });
599
+
600
+ it('blocks save and shows an error when createCommDefinition resolves with a duplicate REFERENCE_ID_EXISTS conflict', async () => {
601
+ createCommDefinition.mockResolvedValue({
602
+ success: false,
603
+ status: { isError: true, code: 409, message: 'REFERENCE_ID_EXISTS' },
604
+ message: 'REFERENCE_ID_EXISTS',
605
+ });
606
+ const onSave = jest.fn();
607
+ renderWithFlow({
608
+ features: {},
609
+ config: ccsConfig,
610
+ initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
611
+ onSave,
612
+ });
613
+
614
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
615
+
616
+ await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
617
+ expect(await screen.findByText(/already in use in your organization/i)).toBeInTheDocument();
618
+ expect(onSave).not.toHaveBeenCalled();
460
619
  });
461
620
 
462
- it('skips createCentralCommsMetaId when contentItems is empty', async () => {
621
+ it('skips createCommDefinition when contentItems is empty', async () => {
463
622
  const onSave = jest.fn();
464
623
  renderWithFlow({
465
624
  features: {},
625
+ config: ccsConfig,
466
626
  initialData: { contentItems: [] },
467
627
  onSave,
468
628
  });
@@ -470,12 +630,13 @@ describe('handleSave — CCS flow', () => {
470
630
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
471
631
 
472
632
  await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
473
- expect(createCentralCommsMetaId).not.toHaveBeenCalled();
633
+ expect(createCommDefinition).not.toHaveBeenCalled();
474
634
  });
475
635
 
476
- it('includes additionalSettings derived from dynamicControls in the payload', async () => {
636
+ it('places additionalSettings derived from dynamicControls under settings.additionalSettings (not delivery settings)', async () => {
477
637
  renderWithFlow({
478
638
  features: {},
639
+ config: ccsConfig,
479
640
  initialData: {
480
641
  contentItems: [{ channel: 'SMS', templateData: {} }],
481
642
  dynamicControls: {
@@ -489,14 +650,36 @@ describe('handleSave — CCS flow', () => {
489
650
 
490
651
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
491
652
 
492
- await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
493
- const payload = createCentralCommsMetaId.mock.calls[0][0];
494
- expect(payload.centralCommsPayload.smsDeliverySettings.additionalSettings).toEqual({
653
+ await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
654
+ const payload = createCommDefinition.mock.calls[0][0];
655
+ expect(payload.settings.additionalSettings).toEqual({
495
656
  useTinyUrl: true,
496
657
  encryptUrl: true,
497
658
  linkTrackingEnabled: true,
498
659
  userSubscriptionDisabled: true,
499
660
  });
661
+ expect(payload.singleChannelStrategy.variant.smsDeliverySettings.additionalSettings).toBeUndefined();
662
+ });
663
+
664
+ it('includes channelSettings from deliverySetting.channelSetting in the payload', async () => {
665
+ renderWithFlow({
666
+ features: {},
667
+ config: ccsConfig,
668
+ initialData: {
669
+ contentItems: [{ channel: 'SMS', templateData: {} }],
670
+ deliverySetting: { channelSetting: { SMS: { domainId: 1001, gsmSenderId: 'BRAND1' } } },
671
+ },
672
+ });
673
+
674
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
675
+
676
+ await waitFor(() => expect(createCommDefinition).toHaveBeenCalled());
677
+ const payload = createCommDefinition.mock.calls[0][0];
678
+ expect(payload.singleChannelStrategy.variant.smsDeliverySettings.channelSettings).toEqual({
679
+ channel: 'SMS',
680
+ domainId: 1001,
681
+ gsmSenderId: 'BRAND1',
682
+ });
500
683
  });
501
684
  });
502
685
 
@@ -552,39 +735,34 @@ describe('optional chaining safety', () => {
552
735
  jest.clearAllMocks();
553
736
  });
554
737
 
555
- it('defaults ouId to -1 when config.context is absent', async () => {
556
- createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'x' } } });
738
+ it('does not crash and skips CCS when config.context is entirely absent', async () => {
739
+ const onSave = jest.fn();
557
740
  renderWithFlow({
558
741
  features: {},
742
+ config: { ...baseConfig, features: {} },
559
743
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
744
+ onSave,
560
745
  });
561
746
 
562
747
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
563
748
 
564
- await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
565
- expect(createCentralCommsMetaId).toHaveBeenCalledWith(
566
- expect.objectContaining({
567
- centralCommsPayload: expect.objectContaining({ ouId: -1 }),
568
- }),
569
- );
749
+ await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
750
+ expect(createCommDefinition).not.toHaveBeenCalled();
570
751
  });
571
752
 
572
- it('defaults module to consumer.toUpperCase() when config.context.module absent', async () => {
573
- createCentralCommsMetaId.mockResolvedValue({ response: { data: { id: 'x' } } });
753
+ it('does not crash when config.context is present but empty', async () => {
754
+ const onSave = jest.fn();
574
755
  renderWithFlow({
575
756
  features: {},
576
- config: { ...baseConfig, consumer: 'loyalty', features: {} },
757
+ config: { ...baseConfig, context: {}, features: {} },
577
758
  initialData: { contentItems: [{ channel: 'SMS', templateData: {} }] },
759
+ onSave,
578
760
  });
579
761
 
580
762
  await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
581
763
 
582
- await waitFor(() => expect(createCentralCommsMetaId).toHaveBeenCalled());
583
- expect(createCentralCommsMetaId).toHaveBeenCalledWith(
584
- expect.objectContaining({
585
- centralCommsPayload: expect.objectContaining({ module: 'LOYALTY' }),
586
- }),
587
- );
764
+ await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
765
+ expect(createCommDefinition).not.toHaveBeenCalled();
588
766
  });
589
767
 
590
768
  it('initializes channel from config.channel when initialData.channel is absent', () => {
@@ -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)', () => {
@@ -38,6 +38,72 @@ 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
+ // CCS CommDefinition strategyType — distinct from SINGLE_TEMPLATE above, which
42
+ // is this UI's own communicationStrategy value. Only SINGLE is supported this
43
+ // phase; CCS create is skipped entirely for CHANNEL_PRIORITY/AB_TEST.
44
+ export const CCS_STRATEGY_TYPE_SINGLE = 'SINGLE';
45
+
46
+ // Mirrors CCS's SingleChannelStrategy field names exactly (Cap Notify API
47
+ // Design doc sample payloads, verified against real create responses). Kept
48
+ // separate from CHANNEL_CONTENT_KEY_MAP/CHANNEL_DELIVERY_KEY_MAP below, which
49
+ // are the legacy messageMeta payload's keys.
50
+ export const CCS_CHANNEL_CONTENT_KEY_MAP = {
51
+ SMS: 'smsMessageContent',
52
+ EMAIL: 'emailMessageContent',
53
+ WHATSAPP: 'whatsappMessageContent',
54
+ // CCS's channel enum value for mobile push is 'PUSH' (verified against real
55
+ // create responses), even though the content/delivery keys stay 'mpush*'.
56
+ PUSH: 'mpushMessageContent',
57
+ INAPP: 'inAppMessageContent',
58
+ ANDROID: 'androidMessageContent',
59
+ IOS: 'iosMessageContent',
60
+ ZALO: 'zaloMessageContent',
61
+ VIBER: 'viberMessageContent',
62
+ LINE: 'lineMessageContent',
63
+ WEBPUSH: 'webPushMessageContent',
64
+ RCS: 'rcsMessageContent',
65
+ };
66
+
67
+ // contentItem.templateData is built by the legacy CreativesContainer content
68
+ // editor (messageMeta-oriented field names) — for channels whose legacy field
69
+ // names don't match CCS's own content schema, transform before sending rather
70
+ // than dumping templateData as-is. EMAIL: legacy stores the subject as
71
+ // `emailSubject` (see getContentBody.js), CCS expects `messageSubject`;
72
+ // `messageBody` already matches CCS's name by coincidence. Channels absent
73
+ // here are passed through unchanged (their legacy/CCS field names already
74
+ // align, e.g. SMS's `message`/`messageBody`).
75
+ export const CCS_CONTENT_TRANSFORMS = {
76
+ EMAIL: (templateData = {}) => ({
77
+ messageSubject: templateData.emailSubject,
78
+ messageBody: templateData.messageBody,
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's own internal channel identifier for mobile push is 'MOBILEPUSH'
98
+ // (see CreativesContainer/constants.js MOBILE_PUSH), but CCS's channel enum
99
+ // value is 'PUSH' (verified against real create responses — the content/
100
+ // delivery KEYS still say 'mpush*', only the enum VALUE is 'PUSH') —
101
+ // normalize before building the CCS payload. Every other channel identifier
102
+ // matches CCS as-is.
103
+ export const CCS_CHANNEL_NAME_MAP = {
104
+ MOBILEPUSH: 'PUSH',
105
+ };
106
+
41
107
  // Channel config - single source of truth for CreativesContainer and TemplatesV2
42
108
  // paneKey: TemplatesV2 defaultPanes object key (for channelsToHide)
43
109
  // channelProp: CreativesContainer channel prop (must match pane.key for tab to be active)
@@ -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
  };
@@ -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
  };
@@ -1441,7 +1441,6 @@ export class Creatives extends React.Component {
1441
1441
  cardType = "",
1442
1442
  cardSettings = {},
1443
1443
  accountId = "",
1444
- vfTemplateId,
1445
1444
  } = get(versions, 'base.content.RCS.rcsContent', {});
1446
1445
  const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
1447
1446
  ? cardContentFromSubmit[0]
@@ -1470,7 +1469,6 @@ export class Creatives extends React.Component {
1470
1469
  creativeName: name,
1471
1470
  rcsContent,
1472
1471
  accountId,
1473
- ...(vfTemplateId && { templateId: vfTemplateId }),
1474
1472
  ...(includeRootRcsCardVarMappedOnConsumerPayload
1475
1473
  ? { rcsCardVarMapped: cardVarMappedFromFirstRcsCard }
1476
1474
  : {}),
@@ -32,7 +32,6 @@ export const RCS_STATUSES = {
32
32
 
33
33
  export const HOST_ICS="rcsicsbulk";
34
34
  export const HOST_INFOBIP="rcsinfobipbulk";
35
- export const HOST_VALUE_FIRST="rcsvaluefirstbulk";
36
35
 
37
36
  export const contentType = {
38
37
  text_message: 'text_message',
@@ -358,8 +358,6 @@ export const Rcs = (props) => {
358
358
  const [accessToken, setAccessToken] = useState('');
359
359
  const [hostName, setHostName] = useState('');
360
360
  const [accountName, setAccountName] = useState('');
361
- const [botId, setBotId] = useState('');
362
- const [vfTemplateId, setVfTemplateId] = useState('');
363
361
  const isHostInfoBip = hostName === HOST_INFOBIP;
364
362
  const isHostIcs = hostName === HOST_ICS;
365
363
 
@@ -383,14 +381,12 @@ export const Rcs = (props) => {
383
381
  setHostName(accountObj.hostName || '');
384
382
  setAccountName(accountObj.name || '');
385
383
  setRcsAccount(accountObj.id || '');
386
- setBotId(accountObj.connectionProperties?.agentid || '');
387
384
  } else {
388
385
  setAccountId('');
389
386
  setWecrmAccountId('');
390
387
  setAccessToken('');
391
388
  setHostName('');
392
389
  setAccountName('');
393
- setBotId('');
394
390
  }
395
391
  }, [accountData.selectedRcsAccount]);
396
392
 
@@ -1391,8 +1387,6 @@ export const Rcs = (props) => {
1391
1387
  const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
1392
1388
  setRcsAccount(rcsAccountId);
1393
1389
  }
1394
- const storedVfTemplateId = get(details, 'versions.base.content.RCS.rcsContent.vfTemplateId', '');
1395
- if (storedVfTemplateId) setVfTemplateId(storedVfTemplateId);
1396
1390
 
1397
1391
  applySmsFallbackHydration({
1398
1392
  details,
@@ -2830,8 +2824,7 @@ const onTitleAddVar = () => {
2830
2824
  }
2831
2825
  ],
2832
2826
  contentType: isFullMode ? templateType : RICHCARD,
2833
- ...(isFullMode && {accountId, accessToken, accountName, hostName, botId}),
2834
- ...(vfTemplateId && { vfTemplateId }),
2827
+ ...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
2835
2828
  },
2836
2829
  ...smsFallbackPayloadContent,
2837
2830
  },
@@ -2971,7 +2964,6 @@ const onTitleAddVar = () => {
2971
2964
  accountName,
2972
2965
  hostName,
2973
2966
  smsRegister,
2974
- vfTemplateId,
2975
2967
  ]);
2976
2968
 
2977
2969
  /**