@capillarytech/creatives-library 9.0.55-alpha.0 → 9.0.55-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.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/services/api.js +2 -10
  3. package/styles/containers/layout/_layoutPage.scss +2 -1
  4. package/translations/en.json +2 -2
  5. package/v2Components/AccessForbidden/_accessForbidden.scss +22 -12
  6. package/v2Components/AccessForbidden/index.js +27 -8
  7. package/v2Components/AccessForbidden/messages.js +2 -3
  8. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
  9. package/v2Components/CommonTestAndPreview/constants.js +2 -0
  10. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
  11. package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
  12. package/v2Components/NavigationBar/mfeModuleHeader.config.js +1 -1
  13. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +2 -2
  14. package/v2Containers/CommunicationFlow/CommunicationFlow.js +4 -13
  15. package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
  16. package/v2Containers/CommunicationFlow/constants.js +8 -38
  17. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +3 -8
  18. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +5 -5
  19. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +6 -6
  20. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +4 -4
  21. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +32 -32
  22. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +36 -36
  23. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +130 -130
  24. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +8 -8
  25. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +33 -33
  26. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +302 -302
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "9.0.55-alpha.0",
4
+ "version": "9.0.55-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,21 +701,13 @@ 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).
704
+ // CCS CommDefinition API (new Veyron /api/v1/commdefinition surface, proxied through cap-creatives-api /comm-definitions); used by CommunicationFlow's Save, while legacy messageMeta functions remain for CreativesContainer's save flow (Cap/sagas.js).
708
705
  export const createCommDefinition = (payload) => {
709
706
  const url = `${API_ENDPOINT}/comm-definitions`;
710
707
  return request(url, getAPICallObject('POST', payload, false, false, false, true));
711
708
  };
712
709
 
713
- // Opens a new DRAFT version with the given content on an EXISTING
714
- // CommDefinition (same id/referenceId) — used by CommunicationFlow's edit-mode
715
- // save (re-editing a rejected alert) instead of createCommDefinition, so the
716
- // alert keeps its identity rather than becoming a brand-new CommDefinition.
717
- // Route verified against a real Postman sample against Veyron's own
718
- // /commdefinition/{id}/edit endpoint.
710
+ // Opens a new DRAFT version with the supplied content on an existing CommDefinition (same id/referenceId); used by CommunicationFlow edit-mode saves to preserve the alert's identity instead of creating a new CommDefinition. Route verified against Veyron's /commdefinition/{id}/edit endpoint.
719
711
  export const editCommDefinition = (commDefinitionId, payload) => {
720
712
  const url = `${API_ENDPOINT}/comm-definitions/${commDefinitionId}/edit`;
721
713
  return request(url, getAPICallObject('POST', payload, false, false, false, true));
@@ -77,7 +77,8 @@
77
77
  -webkit-flex: 1 1 auto;
78
78
  -ms-flex: 1 1 auto;
79
79
  flex: 1;
80
- height: calc(100vh - 50px);
80
+ // No height: flex stretch inside `.main` sizes this. A hardcoded
81
+ // calc(100vh - 50px) under-counted the chrome and overhung it by ~83px.
81
82
  overflow: auto;
82
83
  color: #333333;
83
84
  }
@@ -63,8 +63,8 @@
63
63
  "app.v2containers.SmsTrai.Edit.traiEditTitle": "Message",
64
64
  "app.v2containers.SmsTrai.Edit.unicodeLabel": "Allow unicode characters",
65
65
  "app.v2containers.SmsTrai.Edit.unsupportedTagsValidationError": "Unsupported tags: {unsupportedTags}. Please remove them from this message.",
66
- "creatives.components.AccessForbidden.forbiddenDesc": "Please contact your organisation administrator to request access.",
67
- "creatives.components.AccessForbidden.forbiddenHeader": "You don't have permission to view this page.",
66
+ "creatives.components.AccessForbidden.forbiddenDesc": "This page access has not been provided to you.",
67
+ "creatives.components.AccessForbidden.forbiddenHeader": "Access Denied",
68
68
  "creatives.components.BreadCrumbs.header": "This is the BreadCrumbs component !",
69
69
  "creatives.components.CapTagList.Cancel": "Cancel",
70
70
  "creatives.components.CapTagList.Ok": "Ok",
@@ -1,14 +1,24 @@
1
- // Access-denied screen: rbac-lock icon + heading + subtext (matches the shared
2
- // "no access" screen used across Loyalty+ / Org Settings).
3
- .no-access-icon {
4
- font-size: 48px;
5
- color: #8c8c8c;
6
- }
7
- .no-access-title {
1
+ @import '~@capillarytech/cap-ui-library/styles/_variables';
2
+
3
+ .access-denied {
4
+ width: 100%;
5
+ // This module's ComponentWrapper has no padding, unlike the other MFEs'.
8
6
  margin-top: 16px;
9
- }
10
- .no-access-text {
11
- margin-top: 8px;
12
- color: #6b778c;
13
- max-width: 420px;
7
+ padding-top: $CAP_SPACE_80;
8
+ text-align: center;
9
+
10
+ .access-denied-illustration {
11
+ width: 20rem;
12
+ height: 20rem;
13
+ margin-bottom: $CAP_SPACE_12;
14
+ }
15
+
16
+ .access-denied-title-container {
17
+ gap: $CAP_SPACE_08;
18
+ margin-bottom: $CAP_SPACE_08;
19
+ }
20
+
21
+ .access-denied-description {
22
+ color: $CAP_G04;
23
+ }
14
24
  }
@@ -1,18 +1,37 @@
1
1
  import React from 'react';
2
2
  import { FormattedMessage } from 'react-intl';
3
- import { CapRow, CapHeading, CapIcon } from '@capillarytech/cap-ui-library';
3
+ import {
4
+ CapRow,
5
+ CapImage,
6
+ CapIcon,
7
+ CapHeading,
8
+ CapLabel,
9
+ } from '@capillarytech/cap-ui-library';
10
+ import accessDeniedIllustration from '@capillarytech/cap-ui-library/assets/images/createNewIllustration.svg';
4
11
  import messages from './messages';
5
12
  import './_accessForbidden.scss';
6
13
 
7
14
  const AccessForbidden = () => (
8
- <CapRow type='flex' vertical justify='center' align='middle' className="margin-t-32 text-align-center">
9
- <CapIcon type="rbac-lock" className="no-access-icon" />
10
- <CapHeading type="h3" className="no-access-title">
11
- <FormattedMessage {...messages.forbiddenHeader} />
12
- </CapHeading>
13
- <CapHeading type="h6" className="no-access-text">
15
+ <CapRow vertical align="middle" justify="center" className="access-denied">
16
+ <CapImage
17
+ src={accessDeniedIllustration}
18
+ className="access-denied-illustration"
19
+ alt=""
20
+ />
21
+ <CapRow
22
+ type="flex"
23
+ align="middle"
24
+ justify="center"
25
+ className="access-denied-title-container"
26
+ >
27
+ <CapIcon type="rbac-lock" size="m" />
28
+ <CapHeading type="h2">
29
+ <FormattedMessage {...messages.forbiddenHeader} />
30
+ </CapHeading>
31
+ </CapRow>
32
+ <CapLabel type="label2" className="access-denied-description">
14
33
  <FormattedMessage {...messages.forbiddenDesc} />
15
- </CapHeading>
34
+ </CapLabel>
16
35
  </CapRow>
17
36
  );
18
37
 
@@ -5,11 +5,10 @@ const prefix = 'creatives.components.AccessForbidden';
5
5
  export default defineMessages({
6
6
  forbiddenHeader: {
7
7
  id: `${prefix}.forbiddenHeader`,
8
- defaultMessage: "You don't have permission to view this page.",
8
+ defaultMessage: 'Access Denied',
9
9
  },
10
10
  forbiddenDesc: {
11
11
  id: `${prefix}.forbiddenDesc`,
12
- defaultMessage:
13
- 'Please contact your organisation administrator to request access.',
12
+ defaultMessage: 'This page access has not been provided to you.',
14
13
  },
15
14
  });
@@ -139,6 +139,19 @@ const UnifiedPreview = ({
139
139
  );
140
140
  }
141
141
 
142
+ case CHANNELS.LINE:
143
+ // LINE currently carries a single text message; CCS stores lineMessageContent.messageBody as JSON for LINE's native messages[] (see testAndPreviewDataTransform.js), already reduced to plain text before this point, so reuse the SMS chat-bubble preview. Rich LINE types (image/video/sticker/imageMap/carousel/flex) are not specially rendered yet.
144
+ return (
145
+ <SmsPreviewContent
146
+ content={typeof content === 'string' ? content : (content?.resolvedBody || '')}
147
+ device={device}
148
+ isUpdating={isUpdating}
149
+ error={error}
150
+ formatMessage={formatMessage}
151
+ showHeader={showHeader}
152
+ />
153
+ );
154
+
142
155
  case CHANNELS.WHATSAPP: {
143
156
  // WhatsApp content is an object with templateMsg, media, CTA, etc.
144
157
  const whatsappContent = typeof content === 'object' ? content : {};
@@ -85,6 +85,7 @@ export const CHANNELS = {
85
85
  VIBER: 'VIBER',
86
86
  ZALO: 'ZALO',
87
87
  WEBPUSH: 'WEBPUSH',
88
+ LINE: 'LINE',
88
89
  };
89
90
 
90
91
  /** Unified preview tab keys when RCS shows RCS + SMS fallback panes. */
@@ -100,6 +101,7 @@ export const CHANNELS_USING_ANDROID_PREVIEW_DEVICE = [
100
101
  CHANNELS.INAPP,
101
102
  CHANNELS.MOBILEPUSH,
102
103
  CHANNELS.VIBER,
104
+ CHANNELS.LINE,
103
105
  ];
104
106
 
105
107
  /** RCS createMessageMeta test payload — rich card envelope literals. */
@@ -479,6 +479,112 @@ describe('UnifiedPreview', () => {
479
479
  });
480
480
  });
481
481
 
482
+ describe('Channel Routing - LINE', () => {
483
+ it('should render SmsPreviewContent for LINE channel with string content', () => {
484
+ const props = {
485
+ ...defaultProps,
486
+ channel: CHANNELS.LINE,
487
+ content: 'Hello world',
488
+ };
489
+
490
+ render(
491
+ <TestWrapper>
492
+ <ComponentToRender {...props} />
493
+ </TestWrapper>
494
+ );
495
+
496
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
497
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('Hello world');
498
+ });
499
+
500
+ it('should handle object content with resolvedBody for LINE channel', () => {
501
+ const props = {
502
+ ...defaultProps,
503
+ channel: CHANNELS.LINE,
504
+ content: {
505
+ resolvedBody: 'some text',
506
+ },
507
+ };
508
+
509
+ render(
510
+ <TestWrapper>
511
+ <ComponentToRender {...props} />
512
+ </TestWrapper>
513
+ );
514
+
515
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
516
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('some text');
517
+ });
518
+
519
+ it('should not crash and render empty content for LINE channel when content is undefined', () => {
520
+ const props = {
521
+ ...defaultProps,
522
+ channel: CHANNELS.LINE,
523
+ content: undefined,
524
+ };
525
+
526
+ render(
527
+ <TestWrapper>
528
+ <ComponentToRender {...props} />
529
+ </TestWrapper>
530
+ );
531
+
532
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
533
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('');
534
+ });
535
+
536
+ it('should not crash and render empty content for LINE channel when content is null', () => {
537
+ const props = {
538
+ ...defaultProps,
539
+ channel: CHANNELS.LINE,
540
+ content: null,
541
+ };
542
+
543
+ render(
544
+ <TestWrapper>
545
+ <ComponentToRender {...props} />
546
+ </TestWrapper>
547
+ );
548
+
549
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
550
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('');
551
+ });
552
+
553
+ it('should render empty content for LINE channel when content is not a string and has no resolvedBody', () => {
554
+ const props = {
555
+ ...defaultProps,
556
+ channel: CHANNELS.LINE,
557
+ content: { someOtherField: 'value' },
558
+ };
559
+
560
+ render(
561
+ <TestWrapper>
562
+ <ComponentToRender {...props} />
563
+ </TestWrapper>
564
+ );
565
+
566
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
567
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('');
568
+ });
569
+
570
+ it('should not render the Unknown Channel fallback for LINE channel', () => {
571
+ const props = {
572
+ ...defaultProps,
573
+ channel: CHANNELS.LINE,
574
+ content: 'Hello world',
575
+ };
576
+
577
+ render(
578
+ <TestWrapper>
579
+ <ComponentToRender {...props} />
580
+ </TestWrapper>
581
+ );
582
+
583
+ expect(screen.queryByText('Unknown Channel')).toBeNull();
584
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
585
+ });
586
+ });
587
+
482
588
  describe('Channel Routing - WHATSAPP', () => {
483
589
  it('should render WhatsAppPreviewContent for WHATSAPP channel', () => {
484
590
  const props = {
@@ -42,6 +42,7 @@ import {
42
42
  CLEAR_PREVIEW_ERRORS,
43
43
  // Channel Constants
44
44
  CHANNELS,
45
+ CHANNELS_USING_ANDROID_PREVIEW_DEVICE,
45
46
  // Device Constants
46
47
  DESKTOP,
47
48
  MOBILE,
@@ -180,15 +181,22 @@ describe('CommonTestAndPreview Constants', () => {
180
181
  expect(CHANNELS.VIBER).toBe('VIBER');
181
182
  expect(CHANNELS.ZALO).toBe('ZALO');
182
183
  expect(CHANNELS.WEBPUSH).toBe('WEBPUSH');
184
+ expect(CHANNELS.LINE).toBe('LINE');
183
185
  });
184
186
 
185
187
  it('should have all required channel keys', () => {
186
- const expectedChannels = ['EMAIL', 'SMS', 'RCS', 'WHATSAPP', 'INAPP', 'MOBILEPUSH', 'VIBER', 'ZALO', 'WEBPUSH'];
188
+ const expectedChannels = ['EMAIL', 'SMS', 'RCS', 'WHATSAPP', 'INAPP', 'MOBILEPUSH', 'VIBER', 'ZALO', 'WEBPUSH', 'LINE'];
187
189
  expect(Object.keys(CHANNELS)).toEqual(expect.arrayContaining(expectedChannels));
188
190
  expect(Object.keys(CHANNELS).length).toBe(expectedChannels.length);
189
191
  });
190
192
  });
191
193
 
194
+ describe('CHANNELS_USING_ANDROID_PREVIEW_DEVICE', () => {
195
+ it('should include CHANNELS.LINE', () => {
196
+ expect(CHANNELS_USING_ANDROID_PREVIEW_DEVICE).toContain(CHANNELS.LINE);
197
+ });
198
+ });
199
+
192
200
  describe('Device Constants', () => {
193
201
  it('should export all device constants with correct values', () => {
194
202
  expect(DESKTOP).toBe('desktop');
@@ -4,7 +4,7 @@ export const MFE_MODULE_HEADER = {
4
4
  header: {
5
5
  name: 'app.commonUtils.capUiLibrary.navigationSPA.moduleHeader.creatives.default.name',
6
6
  description: 'app.commonUtils.capUiLibrary.navigationSPA.moduleHeader.creatives.default.description',
7
- showBorder: false,
7
+ showBorder: true,
8
8
  showSettings: false,
9
9
  },
10
10
  landingRoutes: [
@@ -141,8 +141,8 @@ exports[`<Cap /> should render correct component 1`] = `
141
141
  "app.v2containers.SmsTrai.Edit.traiEditTitle": "Message",
142
142
  "app.v2containers.SmsTrai.Edit.unicodeLabel": "Allow unicode characters",
143
143
  "app.v2containers.SmsTrai.Edit.unsupportedTagsValidationError": "Unsupported tags: {unsupportedTags}. Please remove them from this message.",
144
- "creatives.components.AccessForbidden.forbiddenDesc": "Please contact your organisation administrator to request access.",
145
- "creatives.components.AccessForbidden.forbiddenHeader": "You don't have permission to view this page.",
144
+ "creatives.components.AccessForbidden.forbiddenDesc": "This page access has not been provided to you.",
145
+ "creatives.components.AccessForbidden.forbiddenHeader": "Access Denied",
146
146
  "creatives.components.BreadCrumbs.header": "This is the BreadCrumbs component !",
147
147
  "creatives.components.CapTagList.Cancel": "Cancel",
148
148
  "creatives.components.CapTagList.Ok": "Ok",
@@ -67,15 +67,11 @@ const hasDeliverySettingForChannel = (channelSetting, channel) => {
67
67
  return !!settings && Object.values(settings).some((v) => v !== null && v !== '' && v !== undefined);
68
68
  };
69
69
 
70
- // createCommDefinition resolves (rather than rejects) with the CCS error envelope
71
- // for 4xx/5xx responses — see api.js's request()/checkStatus. A duplicate
72
- // referenceId within the org comes back as this specific 409.
70
+ // createCommDefinition resolves with the CCS error envelope for 4xx/5xx responses (see api.js request()/checkStatus); duplicate referenceId within the org returns a 409.
73
71
  const isDuplicateReferenceIdError = (res) => res?.success === false && res?.status?.message === 'REFERENCE_ID_EXISTS';
74
72
 
75
73
  /**
76
- * CCS requires referenceId on create; the consumer-supplied Alert/comm name is
77
- * mandatory but referenceId is optional in every consumer's own form. Generate
78
- * a stable fallback from the name rather than failing the save.
74
+ * CCS requires referenceId on create, while consumers require only the Alert/comm name; generate a stable fallback from the name when referenceId is omitted instead of failing the save.
79
75
  */
80
76
  const buildCcsReferenceId = (name) => {
81
77
  const slug = (name || 'COMM')
@@ -215,19 +211,14 @@ const CommunicationFlow = ({
215
211
  singleChannelStrategy: {
216
212
  variant: {
217
213
  channel,
218
- // `channel` spreads LAST in both objects below contentItem.templateData
219
- // (e.g. mobile push) carries the UI's own internal channel identifier
220
- // (MOBILEPUSH), which must never win over CCS's normalized enum value
221
- // (PUSH) once spread order puts it after.
214
+ // `channel` must be spread last in both objects so CCS's normalized enum (e.g. `PUSH`) overrides the UI's internal identifier (e.g. `MOBILEPUSH`) from templateData.
222
215
  ...(contentKey && { [contentKey]: { ...contentPayload, channel } }),
223
216
  ...(deliveryKey && { [deliveryKey]: { channelSettings: { ...channelSettings, channel } } }),
224
217
  },
225
218
  },
226
219
  };
227
220
 
228
- // Editing an existing (e.g. rejected) alert: config.context.existingCommDefinitionId
229
- // opens a new DRAFT version on that SAME CommDefinition instead of creating a
230
- // brand-new one, so the alert keeps its id/referenceId across the edit.
221
+ // Editing an existing alert (e.g. rejected) uses config.context.existingCommDefinitionId to create a new DRAFT version on the same CommDefinition, preserving its id/referenceId instead of creating a new alert.
231
222
  const { existingCommDefinitionId } = config?.context || {};
232
223
 
233
224
  try {
@@ -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,21 +38,15 @@ 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.
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.
44
42
  export const CCS_STRATEGY_TYPE_SINGLE = 'SINGLE';
45
43
 
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.
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.
50
45
  export const CCS_CHANNEL_CONTENT_KEY_MAP = {
51
46
  SMS: 'smsMessageContent',
52
47
  EMAIL: 'emailMessageContent',
53
48
  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*'.
49
+ // CCS's channel enum value for mobile push is 'PUSH' (verified against real create responses), even though the content/delivery keys stay 'mpush*'.
56
50
  PUSH: 'mpushMessageContent',
57
51
  INAPP: 'inAppMessageContent',
58
52
  ANDROID: 'androidMessageContent',
@@ -64,34 +58,17 @@ export const CCS_CHANNEL_CONTENT_KEY_MAP = {
64
58
  RCS: 'rcsMessageContent',
65
59
  };
66
60
 
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` and the HTML body as `emailBody` (see
72
- // extractContentForPreview.js and CreativesContainer/index.js, where the Bee
73
- // editor's output is written back into templateData.emailBody — also
74
- // mirrored by the existing legacy emailMessageContent builder in
75
- // utils/transformerUtils.js). CCS expects `messageSubject`/`messageBody`.
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`.
76
62
  export const CCS_CONTENT_TRANSFORMS = {
77
63
  EMAIL: (templateData = {}) => ({
78
64
  messageSubject: templateData.emailSubject,
79
65
  messageBody: templateData.emailBody || templateData.emailHtml,
80
66
  }),
81
- // SMS: legacy stores the typed text as `messageBody` (see
82
- // CreativesContainer/index.js's getCreativesData SMS case, and the existing
83
- // legacy smsMessageContent builder in utils/transformerUtils.js, both of
84
- // which read/write this same field) — CCS's schema expects it as `message`
85
- // (confirmed against a real CCS response showing smsMessageContent.message
86
- // stayed null when messageBody was sent instead).
67
+ // SMS: legacy stores text as `messageBody`, while CCS expects `message`; map the field explicitly to avoid CCS returning smsMessageContent.message as null.
87
68
  SMS: (templateData = {}) => ({
88
69
  message: templateData.messageBody || '',
89
70
  }),
90
- // WEBPUSH: the legacy editor nests the real data under
91
- // messageContent.content (see CommunicationFlowCard.js's own comment on
92
- // this same wrapping, for the edit-mode case) — extracting it flat here
93
- // matches CCS's shape ({ messageSubject, accountId, content }); `offers`
94
- // (a legacy-only field) is deliberately dropped, not part of CCS's schema.
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.
95
72
  WEBPUSH: (templateData = {}) => {
96
73
  const inner = templateData.messageContent?.content || templateData;
97
74
  return {
@@ -117,19 +94,12 @@ export const CCS_CHANNEL_DELIVERY_KEY_MAP = {
117
94
  RCS: 'rcsDeliverySettings',
118
95
  };
119
96
 
120
- // The UI's own internal channel identifier for mobile push is 'MOBILEPUSH'
121
- // (see CreativesContainer/constants.js MOBILE_PUSH), but CCS's channel enum
122
- // value is 'PUSH' (verified against real create responses — the content/
123
- // delivery KEYS still say 'mpush*', only the enum VALUE is 'PUSH') —
124
- // normalize before building the CCS payload. Every other channel identifier
125
- // matches CCS as-is.
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.
126
98
  export const CCS_CHANNEL_NAME_MAP = {
127
99
  MOBILEPUSH: 'PUSH',
128
100
  };
129
101
 
130
- // Channel config - single source of truth for CreativesContainer and TemplatesV2
131
- // paneKey: TemplatesV2 defaultPanes object key (for channelsToHide)
132
- // channelProp: CreativesContainer channel prop (must match pane.key for tab to be active)
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.
133
103
  export const CHANNELS = [
134
104
  {
135
105
  value: 'SMS',
@@ -181,9 +181,7 @@ 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 own outer `id`
185
- // (distinct from `domainProperties.id`, which is the numeric domainId) - the
186
- // domain-gateway-mapping id for the selected domain entry.
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.
187
185
  const getEmailDomainGatewayMapIdByDomainId = (entity, domainId) => {
188
186
  const domainEntry = getEmailDomainById(entity, domainId);
189
187
  return domainEntry?.id ?? '';
@@ -584,8 +582,7 @@ export const parseEntityForDisplay = (entity, context = {}) => {
584
582
  senderDetails: entity,
585
583
  smsSenderId: getSmsSenderIdValue(entity),
586
584
  smsDomain: getSmsDomainValue(entity),
587
- // emailDomain here is the domain NAME (for display only) - buildChannelSettingFromFieldValues
588
- // needs the numeric domainId/domainGatewayMapId, sourced separately below.
585
+ // emailDomain here is the domain NAME (for display only) - buildChannelSettingFromFieldValues needs the numeric domainId/domainGatewayMapId, sourced separately below.
589
586
  emailDomain: getEmailDomainNameForDisplay(entity),
590
587
  emailDomainId: getEmailDomainValue(entity),
591
588
  emailDomainGatewayMapId: getEmailDomainGatewayMapIdByDomainId(entity, getEmailDomainValue(entity)),
@@ -614,9 +611,7 @@ export const buildChannelSettingFromFieldValues = (fieldValues = {}) => {
614
611
  }
615
612
  if (fieldValues.emailDomain != null || fieldValues.emailSenderId != null || fieldValues.emailSenderName != null || fieldValues.emailReplyToId != null) {
616
613
  channelSetting.EMAIL = {
617
- // emailDomainId (numeric, from the auto-save-defaults path via parseEntityForDisplay)
618
- // takes priority over emailDomain, which is numeric when sourced from SenderDetails'
619
- // own field state but a display NAME string when sourced from parseEntityForDisplay.
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.
620
615
  domainId: fieldValues.emailDomainId ?? fieldValues.emailDomain,
621
616
  domainGatewayMapId: fieldValues.emailDomainGatewayMapId,
622
617
  senderId: fieldValues.emailSenderId,
@@ -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
  `;