@capillarytech/creatives-library 9.0.54-alpha.1 → 9.0.54-alpha.3

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "9.0.54-alpha.1",
4
+ "version": "9.0.54-alpha.3",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -215,8 +215,12 @@ const CommunicationFlow = ({
215
215
  singleChannelStrategy: {
216
216
  variant: {
217
217
  channel,
218
- ...(contentKey && { [contentKey]: { channel, ...contentPayload } }),
219
- ...(deliveryKey && { [deliveryKey]: { channelSettings: { channel, ...channelSettings } } }),
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.
222
+ ...(contentKey && { [contentKey]: { ...contentPayload, channel } }),
223
+ ...(deliveryKey && { [deliveryKey]: { channelSettings: { ...channelSettings, channel } } }),
220
224
  },
221
225
  },
222
226
  };
@@ -42,7 +42,7 @@ const resolveValue = (val) => {
42
42
 
43
43
  const SENDER_ID_RESOLVERS = {
44
44
  [SMS]: (setting) => resolveValue(setting?.gsmSenderId),
45
- [EMAIL]: (setting) => resolveValue(setting?.senderEmail),
45
+ [EMAIL]: (setting) => resolveValue(setting?.senderId),
46
46
  [VIBER]: (setting) => resolveValue(setting?.sender) || resolveValue(setting?.gsmSenderId),
47
47
  [WHATSAPP]: (setting) => resolveValue(setting?.senderMobNum),
48
48
  [RCS]: (setting) => resolveValue(setting?.senderMobNum) || resolveValue(setting?.rcsSender),
@@ -407,7 +407,7 @@ describe('handleSave — CCS flow', () => {
407
407
  initialData: {
408
408
  contentItems: [{
409
409
  channel: 'EMAIL',
410
- templateData: { emailSubject: 'Order Confirmed', messageBody: '<html>Hi</html>' },
410
+ templateData: { emailSubject: 'Order Confirmed', emailBody: '<html>Hi</html>' },
411
411
  }],
412
412
  },
413
413
  onSave,
@@ -432,13 +432,67 @@ describe('handleSave — CCS flow', () => {
432
432
  );
433
433
  });
434
434
 
435
- it('normalizes the internal MOBILEPUSH channel to CCS\'s PUSH enum (keys stay mpush*)', async () => {
435
+ it('transforms WEBPUSH content from the legacy editor\'s messageContent.content wrapping to CCS\'s flat shape', async () => {
436
436
  const onSave = jest.fn();
437
+ const cta = { type: 'SITE_URL', actionLink: 'https://example.com' };
438
+ const expandableDetails = { media: [{ url: 'https://example.com/banner.png', type: 'IMAGE' }], ctas: [] };
437
439
  renderWithFlow({
438
440
  features: {},
439
441
  config: ccsConfig,
440
442
  initialData: {
441
- contentItems: [{ channel: 'MOBILEPUSH', templateData: { messageSubject: 'Hi' } }],
443
+ contentItems: [{
444
+ channel: 'WEBPUSH',
445
+ templateData: {
446
+ messageContent: {
447
+ content: {
448
+ channel: 'WEBPUSH',
449
+ accountId: 13792,
450
+ content: { title: 'Hi', message: 'Body', iconImageUrl: 'https://example.com/icon.png', cta, expandableDetails },
451
+ messageSubject: 'Subject',
452
+ offers: [],
453
+ },
454
+ },
455
+ },
456
+ }],
457
+ },
458
+ onSave,
459
+ });
460
+
461
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
462
+
463
+ await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
464
+ expect(createCommDefinition).toHaveBeenCalledWith(
465
+ expect.objectContaining({
466
+ singleChannelStrategy: expect.objectContaining({
467
+ variant: expect.objectContaining({
468
+ channel: 'WEBPUSH',
469
+ webPushMessageContent: {
470
+ channel: 'WEBPUSH',
471
+ messageSubject: 'Subject',
472
+ accountId: 13792,
473
+ content: { title: 'Hi', message: 'Body', iconImageUrl: 'https://example.com/icon.png', cta, expandableDetails },
474
+ },
475
+ }),
476
+ }),
477
+ }),
478
+ );
479
+ });
480
+
481
+ it('normalizes the internal MOBILEPUSH channel to CCS\'s PUSH enum (keys stay mpush*), even when templateData/channelSetting carry their own stray channel field', async () => {
482
+ const onSave = jest.fn();
483
+ renderWithFlow({
484
+ features: {},
485
+ config: ccsConfig,
486
+ initialData: {
487
+ // `channel: 'MOBILEPUSH'` inline on templateData reproduces the real
488
+ // production bug: contentItem.templateData carries the UI's own
489
+ // internal channel identifier, which must not win over the
490
+ // normalized 'PUSH' enum once spread into mpushMessageContent.
491
+ contentItems: [{ channel: 'MOBILEPUSH', templateData: { channel: 'MOBILEPUSH', messageSubject: 'Hi' } }],
492
+ // Likewise, deliverySetting.channelSetting.PUSH carrying its own
493
+ // stray `channel` key must not win over the normalized value once
494
+ // spread into mpushDeliverySettings.channelSettings.
495
+ deliverySetting: { channelSetting: { PUSH: { channel: 'MOBILEPUSH', domainId: 2002 } } },
442
496
  },
443
497
  onSave,
444
498
  });
@@ -452,6 +506,9 @@ describe('handleSave — CCS flow', () => {
452
506
  variant: expect.objectContaining({
453
507
  channel: 'PUSH',
454
508
  mpushMessageContent: { channel: 'PUSH', messageSubject: 'Hi' },
509
+ mpushDeliverySettings: {
510
+ channelSettings: { channel: 'PUSH', domainId: 2002 },
511
+ },
455
512
  }),
456
513
  }),
457
514
  }),
@@ -247,11 +247,11 @@ describe('CommunicationFlowCard', () => {
247
247
  expect(screen.getByText(/Sender ID/)).toBeInTheDocument();
248
248
  });
249
249
 
250
- it('shows senderEmail for EMAIL with Sender ID label', () => {
250
+ it('shows senderId for EMAIL with Sender ID label', () => {
251
251
  renderCard({
252
252
  initialData: makeSavedData({
253
253
  contentItems: [{ channel: 'EMAIL', templateData: {} }],
254
- deliverySetting: { channelSetting: { EMAIL: { senderEmail: 'test@example.com' } } },
254
+ deliverySetting: { channelSetting: { EMAIL: { senderId: 'test@example.com' } } },
255
255
  }),
256
256
  });
257
257
  expect(screen.getByText(/test@example.com/)).toBeInTheDocument();
@@ -68,15 +68,29 @@ export const CCS_CHANNEL_CONTENT_KEY_MAP = {
68
68
  // editor (messageMeta-oriented field names) — for channels whose legacy field
69
69
  // names don't match CCS's own content schema, transform before sending rather
70
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`).
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`.
75
76
  export const CCS_CONTENT_TRANSFORMS = {
76
77
  EMAIL: (templateData = {}) => ({
77
78
  messageSubject: templateData.emailSubject,
78
- messageBody: templateData.messageBody,
79
+ messageBody: templateData.emailBody || templateData.emailHtml,
79
80
  }),
81
+ // WEBPUSH: the legacy editor nests the real data under
82
+ // messageContent.content (see CommunicationFlowCard.js's own comment on
83
+ // this same wrapping, for the edit-mode case) — extracting it flat here
84
+ // matches CCS's shape ({ messageSubject, accountId, content }); `offers`
85
+ // (a legacy-only field) is deliberately dropped, not part of CCS's schema.
86
+ WEBPUSH: (templateData = {}) => {
87
+ const inner = templateData.messageContent?.content || templateData;
88
+ return {
89
+ messageSubject: inner.messageSubject,
90
+ accountId: inner.accountId,
91
+ content: inner.content,
92
+ };
93
+ },
80
94
  };
81
95
 
82
96
  export const CCS_CHANNEL_DELIVERY_KEY_MAP = {
@@ -298,4 +312,4 @@ export const INCENTIVE_TYPES = [
298
312
  {
299
313
  value: 'badges', label: <FormattedMessage {...messages.badges} />, isActive: false, disabled: false,
300
314
  },
301
- ];
315
+ ];
@@ -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,14 @@ 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.
187
+ const getEmailDomainGatewayMapIdByDomainId = (entity, domainId) => {
188
+ const domainEntry = getEmailDomainById(entity, domainId);
189
+ return domainEntry?.id ?? '';
190
+ };
191
+
184
192
  /** Domain name for summary display (parseEntityForDisplay) */
185
193
  const getEmailDomainNameForDisplay = (entity) => {
186
194
  const emailData = getEmailData(entity);
@@ -262,6 +270,8 @@ const getEmailDefaultsForDomain = (domainData) => {
262
270
  * context.fieldValues has current form state (e.g. selected emailDomain)
263
271
  * Aligned with adiona-ui/cap-campaigns-v2: default = contactInfo item with default:true, or first
264
272
  */
273
+ export { getEmailDomainGatewayMapIdByDomainId };
274
+
265
275
  export const getDefaultValueForField = (fieldKey, entity, context = {}) => {
266
276
  if (!entity) return '';
267
277
  const fieldValues = context?.fieldValues || {};
@@ -574,7 +584,11 @@ export const parseEntityForDisplay = (entity, context = {}) => {
574
584
  senderDetails: entity,
575
585
  smsSenderId: getSmsSenderIdValue(entity),
576
586
  smsDomain: getSmsDomainValue(entity),
587
+ // emailDomain here is the domain NAME (for display only) - buildChannelSettingFromFieldValues
588
+ // needs the numeric domainId/domainGatewayMapId, sourced separately below.
577
589
  emailDomain: getEmailDomainNameForDisplay(entity),
590
+ emailDomainId: getEmailDomainValue(entity),
591
+ emailDomainGatewayMapId: getEmailDomainGatewayMapIdByDomainId(entity, getEmailDomainValue(entity)),
578
592
  emailSenderId: getEmailSenderIdValue(entity),
579
593
  viberSenderId: getSenderIdFromContactInfo(entity, 'VIBER'),
580
594
  zaloSenderId: getSenderIdFromContactInfo(entity, 'ZALO'),
@@ -600,10 +614,14 @@ export const buildChannelSettingFromFieldValues = (fieldValues = {}) => {
600
614
  }
601
615
  if (fieldValues.emailDomain != null || fieldValues.emailSenderId != null || fieldValues.emailSenderName != null || fieldValues.emailReplyToId != null) {
602
616
  channelSetting.EMAIL = {
603
- domainId: fieldValues.emailDomain,
604
- senderEmail: fieldValues.emailSenderId,
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.
620
+ domainId: fieldValues.emailDomainId ?? fieldValues.emailDomain,
621
+ domainGatewayMapId: fieldValues.emailDomainGatewayMapId,
622
+ senderId: fieldValues.emailSenderId,
605
623
  senderLabel: fieldValues.emailSenderName,
606
- senderReplyTo: fieldValues.emailReplyToId,
624
+ replyToId: fieldValues.emailReplyToId,
607
625
  };
608
626
  }
609
627
  if (fieldValues.whatsappSenderNumber != null || fieldValues.whatsappAccount != null) {
@@ -673,9 +691,10 @@ export const parseChannelSettingForDisplay = (channelSetting = {}) => {
673
691
  }
674
692
  if (channelSetting.EMAIL) {
675
693
  out.emailDomain = channelSetting.EMAIL.domainId;
676
- out.emailSenderId = channelSetting.EMAIL.senderEmail;
694
+ out.emailDomainGatewayMapId = channelSetting.EMAIL.domainGatewayMapId;
695
+ out.emailSenderId = channelSetting.EMAIL.senderId;
677
696
  out.emailSenderName = channelSetting.EMAIL.senderLabel;
678
- out.emailReplyToId = channelSetting.EMAIL.senderReplyTo;
697
+ out.emailReplyToId = channelSetting.EMAIL.replyToId;
679
698
  }
680
699
  if (channelSetting.WHATSAPP) {
681
700
  out.whatsappSenderNumber = channelSetting.WHATSAPP.senderMobNum;