@capillarytech/creatives-library 8.0.123 → 8.0.124

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 (66) hide show
  1. package/containers/App/constants.js +1 -0
  2. package/package.json +1 -1
  3. package/services/api.js +1 -1
  4. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
  5. package/tests/integration/TemplateCreation/api-response.js +5 -0
  6. package/tests/integration/TemplateCreation/msw-handler.js +42 -63
  7. package/utils/common.js +7 -0
  8. package/utils/commonUtils.js +2 -6
  9. package/utils/createPayload.js +240 -0
  10. package/utils/tests/createPayload.test.js +761 -0
  11. package/v2Components/CapDeviceContent/index.js +1 -0
  12. package/v2Components/CapImageUpload/index.js +51 -45
  13. package/v2Components/CapInAppCTA/index.js +1 -0
  14. package/v2Components/CapMpushCTA/constants.js +25 -0
  15. package/v2Components/CapMpushCTA/index.js +332 -0
  16. package/v2Components/CapMpushCTA/index.scss +95 -0
  17. package/v2Components/CapMpushCTA/messages.js +89 -0
  18. package/v2Components/CapTagList/index.js +177 -120
  19. package/v2Components/CapVideoUpload/constants.js +3 -0
  20. package/v2Components/CapVideoUpload/index.js +167 -110
  21. package/v2Components/CapVideoUpload/messages.js +16 -0
  22. package/v2Components/ErrorInfoNote/style.scss +1 -0
  23. package/v2Components/MobilePushPreviewV2/index.js +37 -5
  24. package/v2Components/TemplatePreview/_templatePreview.scss +114 -72
  25. package/v2Components/TemplatePreview/assets/images/Android _ With date and time.svg +29 -0
  26. package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
  27. package/v2Components/TemplatePreview/assets/images/iOS _ With date and time.svg +26 -0
  28. package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
  29. package/v2Components/TemplatePreview/index.js +178 -50
  30. package/v2Components/TemplatePreview/messages.js +4 -0
  31. package/v2Containers/CreativesContainer/SlideBoxContent.js +7 -8
  32. package/v2Containers/CreativesContainer/index.js +194 -138
  33. package/v2Containers/InApp/constants.js +1 -1
  34. package/v2Containers/InApp/index.js +13 -13
  35. package/v2Containers/MobilePush/Create/index.js +1 -0
  36. package/v2Containers/MobilePushNew/actions.js +116 -0
  37. package/v2Containers/MobilePushNew/components/CtaButtons.js +170 -0
  38. package/v2Containers/MobilePushNew/components/MediaUploaders.js +686 -0
  39. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +279 -0
  40. package/v2Containers/MobilePushNew/components/index.js +5 -0
  41. package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +779 -0
  42. package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +2114 -0
  43. package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +343 -0
  44. package/v2Containers/MobilePushNew/constants.js +115 -0
  45. package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1299 -0
  46. package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1223 -0
  47. package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +246 -0
  48. package/v2Containers/MobilePushNew/hooks/useUpload.js +709 -0
  49. package/v2Containers/MobilePushNew/index.js +1960 -0
  50. package/v2Containers/MobilePushNew/index.scss +308 -0
  51. package/v2Containers/MobilePushNew/messages.js +226 -0
  52. package/v2Containers/MobilePushNew/reducer.js +160 -0
  53. package/v2Containers/MobilePushNew/sagas.js +198 -0
  54. package/v2Containers/MobilePushNew/selectors.js +55 -0
  55. package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
  56. package/v2Containers/MobilePushNew/tests/sagas.test.js +863 -0
  57. package/v2Containers/MobilePushNew/tests/selectors.test.js +425 -0
  58. package/v2Containers/MobilePushNew/tests/utils.test.js +322 -0
  59. package/v2Containers/MobilePushNew/utils.js +33 -0
  60. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +5 -5
  61. package/v2Containers/TagList/index.js +56 -10
  62. package/v2Containers/Templates/_templates.scss +101 -1
  63. package/v2Containers/Templates/index.js +147 -35
  64. package/v2Containers/Templates/messages.js +8 -0
  65. package/v2Containers/Templates/sagas.js +2 -0
  66. package/v2Containers/Whatsapp/constants.js +1 -0
@@ -18,6 +18,7 @@ export const JP_LOCALE_HIDE_FEATURE = 'JP_LOCALE_HIDE_FEATURE';
18
18
  export const ENABLE_WECHAT = 'ENABLE_WECHAT';
19
19
  export const ENABLE_CUSTOMER_BARCODE_TAG = "ENABLE_CUSTOMER_BARCODE_TAG";
20
20
  export const EMAIL_UNSUBSCRIBE_TAG_MANDATORY = "EMAIL_UNSUBSCRIBE_TAG_MANDATORY";
21
+ export const ENABLE_NEW_MPUSH = "ENABLE_NEW_MPUSH";
21
22
 
22
23
  export const CARD_RELATED_TAGS = [
23
24
  'card_series',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.123",
4
+ "version": "8.0.124",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -231,7 +231,7 @@ export const createWeChatTemplate = ({template}) => {
231
231
  return request(url, getAPICallObject('POST', template));
232
232
  };
233
233
 
234
- export const createMobilePushTemplate = ({template}) => {
234
+ export const createMobilePushTemplate = (template) => {
235
235
  const url = `${API_ENDPOINT}/templates/MOBILEPUSH`;
236
236
  return request(url, getAPICallObject('POST', template));
237
237
  };
@@ -32,7 +32,7 @@ jest.mock('@capillarytech/cap-ui-utils', () => ({
32
32
  },
33
33
  }));
34
34
 
35
- jest.setTimeout(30000);
35
+ jest.setTimeout(60000);
36
36
 
37
37
  const initializeCreatives = () => {
38
38
  const store = configureStore(mockInitialState, initialReducer, history);
@@ -51,6 +51,12 @@ beforeEach(() => {
51
51
  server.listen();
52
52
  localStorage.clear();
53
53
  localStorage.setItem('token', true);
54
+
55
+ // Mock cap auth
56
+ window.capAuth = {
57
+ hasFeatureAccess: jest.fn().mockReturnValue(false),
58
+ isInitialized: jest.fn().mockReturnValue(true),
59
+ };
54
60
  });
55
61
 
56
62
  // clean up once the tests are done
@@ -62,7 +68,6 @@ afterAll(() => {
62
68
  });
63
69
 
64
70
  describe("Creatives testing template creation", () => {
65
- jest.useFakeTimers();
66
71
  it("Should navigate from creatives home page and create template in RCS channel", async () => {
67
72
  initializeCreatives();
68
73
  const creativesScreen = await helper.getCreativesScreen();
@@ -93,7 +98,7 @@ describe("Creatives testing template creation", () => {
93
98
  name: globalMessages.email.defaultMessage,
94
99
  });
95
100
  await userEvent.click(whatsapp);
96
- waitFor(() => {
101
+ await waitFor(() => {
97
102
  expect(
98
103
  creativesScreen.getByText(
99
104
  /Whatsapp accounts are not setup for your brand/i
@@ -20418,3 +20418,8 @@ export const tag = {
20418
20418
  }
20419
20419
  };
20420
20420
 
20421
+ export const cdnTransformationConfig = {
20422
+ enabled: false,
20423
+ transformations: [],
20424
+ };
20425
+
@@ -4,73 +4,52 @@ import 'whatwg-fetch';
4
4
  import * as apiResponse from './api-response';
5
5
 
6
6
  import config from '../../../config/app';
7
+ import { MOBILE_PUSH } from '../../../v2Containers/CreativesContainer/constants';
7
8
  const API_ENDPOINT = config.development.api_endpoint;
8
9
  const AUTH_ENDPOINT = config.development.auth_endpoint;
9
10
  const CAMPAIGNS_API_ENDPOINT = config.development.campaigns_api_org_endpoint;
10
11
 
11
12
  export const server = setupServer(
12
- rest.options('*', (req, res, ctx) =>
13
- res(
14
- ctx.status(200),
15
- ctx.set('access-control-allow-origin', '*'),
16
- ctx.set('Access-Control-Allow-Headers', 'x-cap-org'),
17
- ctx.set('Access-Control-Allow-Headers', 'x-cap-remote-user'),
18
- ctx.set('Access-Control-Allow-Headers', 'x-cap-api-data-context-org-id'),
19
- ctx.set('Access-Control-Allow-Headers', 'x-cap-api-auth-org-id'),
20
- ),
21
- ),
22
- rest.get(`${AUTH_ENDPOINT}/auth/org/:orgId/users`, (req, res, ctx) => {
23
- const { orgId } = req.params;
24
- switch (orgId) {
25
- case '50146':
26
- return res(ctx.status(200), ctx.json(apiResponse.authOrgUsersReonData));
27
- }
28
- }),
13
+ rest.options('*', (req, res, ctx) => res(
14
+ ctx.status(200),
15
+ ctx.set('access-control-allow-origin', '*'),
16
+ ctx.set('Access-Control-Allow-Headers', 'x-cap-org'),
17
+ ctx.set('Access-Control-Allow-Headers', 'x-cap-remote-user'),
18
+ ctx.set('Access-Control-Allow-Headers', 'x-cap-api-data-context-org-id'),
19
+ ctx.set('Access-Control-Allow-Headers', 'x-cap-api-auth-org-id'),
20
+ )),
21
+ rest.get(`${AUTH_ENDPOINT}/auth/org/:orgId/users`, (req, res, ctx) => {
22
+ const { orgId } = req.params;
23
+ switch (orgId) {
24
+ case '50146':
25
+ return res(ctx.status(200), ctx.json(apiResponse.authOrgUsersReonData));
26
+ }
27
+ }),
29
28
 
30
- rest.get(`${AUTH_ENDPOINT}/user`, (req, res, ctx) =>
31
- res(ctx.status(200), ctx.json(apiResponse.authUserReonData)),
32
- ),
29
+ rest.get(`${AUTH_ENDPOINT}/user`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.authUserReonData))),
33
30
 
34
- rest.get(`${AUTH_ENDPOINT}/org/users`, (req, res, ctx) =>
35
- res(ctx.status(200), ctx.json(apiResponse.authOrgUsersReonData)),
36
- ),
37
- rest.get(`${API_ENDPOINT}/templates/Sms`, (req, res, ctx) =>
38
- res(ctx.status(200), ctx.json(apiResponse.smsTemplates)),
39
- ),
40
- rest.get(`${API_ENDPOINT}/templates/v1/Sms`, (req, res, ctx) =>
41
- res(ctx.status(200), ctx.json(apiResponse.smsTemplates)),
42
- ),
43
- rest.get(`${API_ENDPOINT}/templates/Rcs`, (req, res, ctx) =>
44
- res(ctx.status(200), ctx.json(apiResponse.rcsTemplates)),
45
- ),
46
- rest.get(`${API_ENDPOINT}/templates/v1/Rcs`, (req, res, ctx) =>
47
- res(ctx.status(200), ctx.json(apiResponse.rcsTemplates)),
48
- ),
49
- rest.get(`${API_ENDPOINT}/meta/wecrm`, (req, res, ctx) =>
50
- res(ctx.status(200), ctx.json(apiResponse.viberAccount)),
51
- ),
52
- rest.get(`${API_ENDPOINT}/meta/wecrm`, (req, res, ctx) =>
53
- res(ctx.status(200), ctx.json(apiResponse.whatsappAccount)),
54
- ),
55
- rest.get(`${API_ENDPOINT}/meta/wecrm`, (req, res, ctx) =>
56
- res(ctx.status(200), ctx.json(apiResponse.mpushAccount)),
57
- ),
58
- rest.get(`${API_ENDPOINT}/assets/image`, (req, res, ctx) =>
59
- res(ctx.status(200), ctx.json(apiResponse.gallery)),
60
- ),
61
- rest.get(`${API_ENDPOINT}/templates/v1/Line`, (req, res, ctx) =>
62
- res(ctx.status(200), ctx.json(apiResponse.lineTemplates)),
63
- ),
64
- rest.get(`${API_ENDPOINT}/templates/v1/Viber`, (req, res, ctx) =>
65
- res(ctx.status(200), ctx.json(apiResponse.viberTemplates)),
66
- ),
67
- rest.get(`${API_ENDPOINT}/templates/v1/Email`, (req, res, ctx) =>
68
- res(ctx.status(200), ctx.json(apiResponse.emailTemplates)),
69
- ),
70
- rest.get(`${API_ENDPOINT}/meta/TAG`, (req, res, ctx) =>
71
- res(ctx.status(200), ctx.json(apiResponse.tag)),
72
- ),
73
- rest.get(`${CAMPAIGNS_API_ENDPOINT}/meta/domainProperties`, (req, res, ctx) =>
74
- res(ctx.status(200), ctx.json(apiResponse.domainProperties)),
75
- ),
76
- );
31
+ rest.get(`${AUTH_ENDPOINT}/org/users`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.authOrgUsersReonData))),
32
+ rest.get(`${API_ENDPOINT}/templates/Sms`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.smsTemplates))),
33
+ rest.get(`${API_ENDPOINT}/templates/v1/Sms`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.smsTemplates))),
34
+ rest.get(`${API_ENDPOINT}/templates/Rcs`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.rcsTemplates))),
35
+ rest.get(`${API_ENDPOINT}/templates/v1/Rcs`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.rcsTemplates))),
36
+ rest.get(`${API_ENDPOINT}/meta/wecrm`, (req, res, ctx) => {
37
+ const sourceName = req.url.searchParams.get('source_name');
38
+ switch (sourceName) {
39
+ case 'WHATSAPP':
40
+ return res(ctx.status(200), ctx.json(apiResponse.whatsappAccount));
41
+ case 'VIBER':
42
+ return res(ctx.status(200), ctx.json(apiResponse.viberAccount));
43
+ case MOBILE_PUSH:
44
+ return res(ctx.status(200), ctx.json(apiResponse.mpushAccount));
45
+ default:
46
+ return res(ctx.status(200), ctx.json(apiResponse.whatsappAccount));
47
+ }
48
+ }),
49
+ rest.get(`${API_ENDPOINT}/assets/image`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.gallery))),
50
+ rest.get(`${API_ENDPOINT}/templates/v1/Line`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.lineTemplates))),
51
+ rest.get(`${API_ENDPOINT}/templates/v1/Viber`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.viberTemplates))),
52
+ rest.get(`${API_ENDPOINT}/templates/v1/Email`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.emailTemplates))),
53
+ rest.get(`${API_ENDPOINT}/meta/TAG`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.tag))),
54
+ rest.get(`${CAMPAIGNS_API_ENDPOINT}/meta/domainProperties`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.domainProperties))),
55
+ );
package/utils/common.js CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  BADGES_ISSUE,
23
23
  ENABLE_WECHAT,
24
24
  LIQUID_SUPPORT,
25
+ ENABLE_NEW_MPUSH,
25
26
  } from '../containers/App/constants';
26
27
  import { apiMessageFormatHandler } from './commonUtils';
27
28
 
@@ -124,6 +125,12 @@ export const isEmailUnsubscribeTagMandatory = Auth.hasFeatureAccess.bind(
124
125
  null,
125
126
  EMAIL_UNSUBSCRIBE_TAG_MANDATORY,
126
127
  );
128
+
129
+ export const hasNewMobilePushFeatureEnabled = Auth.hasFeatureAccess.bind(
130
+ null,
131
+ ENABLE_NEW_MPUSH,
132
+ );
133
+
127
134
  //filtering tags based on scope
128
135
  export const filterTags = (tagsToFilter, tagsList) => tagsList?.filter(
129
136
  (tag) => !tagsToFilter?.includes(tag?.definition?.value)
@@ -35,7 +35,7 @@ export const addBaseToTemplate = (template) => {
35
35
  ...template.versions,
36
36
  base: {
37
37
  ...history[0],
38
- ...( get(template, 'versions.base.subject') ? {subject : get(template, 'versions.base.subject')} :{ subject:history?.[0]?.subject }),
38
+ ...( get(template, 'versions.base.subject') ? {subject: get(template, 'versions.base.subject')} : { subject: history?.[0]?.subject }),
39
39
  },
40
40
  },
41
41
  });
@@ -151,7 +151,7 @@ export const validateLiquidTemplateContent = async (
151
151
  const emptyBodyError = formatMessage(messages?.emailBodyEmptyError);
152
152
  const somethingWrongMsg = formatMessage(messages?.somethingWentWrong);
153
153
  // Empty content check
154
-
154
+
155
155
  if (!content || content.trim() === "") {
156
156
  onError({
157
157
  standardErrors: [emptyBodyError],
@@ -170,7 +170,6 @@ export const validateLiquidTemplateContent = async (
170
170
 
171
171
  const {askAiraResponse: result, isError} = await getLiquidTagsAsync(content);
172
172
  const validString = /\S/.test(content);
173
-
174
173
  // Handle API errors or empty content
175
174
  if (result?.errors?.length > 0 || !validString || isError) {
176
175
  let standardErrors = [];
@@ -302,7 +301,6 @@ export const _validatePlatformSpecificContent = async (
302
301
  if (singleTab === IOS) {
303
302
  isAndroidValid = true;
304
303
  }
305
-
306
304
  return isAndroidValid && isIosValid; // Overall success
307
305
  };
308
306
 
@@ -340,7 +338,6 @@ export const validateMobilePushContent = async (formData, options) => {
340
338
  if (iosContent) return [iosContent, IOS?.toLowerCase()];
341
339
  return ["", ""];
342
340
  };
343
-
344
341
  if (overallSuccess) {
345
342
  const [contentToSubmit, tabTypeToSubmit] = getContentToSubmit();
346
343
  // Call the FINAL onSuccess only ONCE here
@@ -373,7 +370,6 @@ export const validateInAppContent = async (formData, options) => {
373
370
  onSuccess, // FINAL success callback
374
371
  ...restOptions // Options for validateLiquidTemplateContent
375
372
  } = options;
376
-
377
373
  // Clear previous errors with new structure
378
374
  onError({
379
375
  standardErrors: {
@@ -0,0 +1,240 @@
1
+ // Utility to create the Mobile Push payload for rich media, with validation
2
+ import { DEEP_LINK } from "../v2Components/CapInAppCTA/constants";
3
+ import { EXTERNAL_URL } from "../v2Components/CapMpushCTA/constants";
4
+ import {
5
+ IMAGE, VIDEO, GIF, CAROUSEL, TEXT, BIG_TEXT, BIG_PICTURE, MOBILE_PUSH_CHANNEL,
6
+ EXTERNAL_LINK,
7
+ ANDROID,
8
+ IOS,
9
+ MANUAL_CAROUSEL,
10
+ AUTO_CAROUSEL,
11
+ FILMSTRIP_CAROUSEL,
12
+ } from "../v2Containers/MobilePushNew/constants";
13
+ import { NONE } from "../v2Containers/Whatsapp/constants";
14
+ /**
15
+ * Constructs the payload for Mobile Push, supporting both legacy and rich media as per backend contract.
16
+ * - Trims and validates templateName
17
+ * - Handles legacy and rich media (VIDEO, GIF, CAROUSEL, etc.)
18
+ * - Builds androidContent and iosContent with correct structure
19
+ * @param {Object} params - All required params
20
+ * @param {string} params.templateName
21
+ * @param {Object} params.androidContent
22
+ * @param {Object} params.iosContent
23
+ * @param {Object} params.accountData
24
+ * @param {Object} [params.options] - Optional extra options (ouId, actionId, etc.)
25
+ * @returns {Object} - Payload ready for backend
26
+ */
27
+ function createPayload({
28
+ templateName, androidContent, iosContent, imageSrc = {}, mpushVideoSrcAndPreview = {}, accountData, options = {}, sameContent = false,
29
+ } = {}) {
30
+ // Validate and trim template name
31
+ const trimmedTemplateName = (templateName || "").trim();
32
+ if (!trimmedTemplateName) {
33
+ throw new Error("Template name is empty or contains only white spaces");
34
+ }
35
+
36
+ // Validate androidContent and iosContent
37
+ if (!androidContent || !androidContent?.title || !androidContent?.message) {
38
+ throw new Error("Android content must have title and message");
39
+ }
40
+
41
+ if (!iosContent || !iosContent?.title || !iosContent?.message) {
42
+ throw new Error("iOS content must have title and message");
43
+ }
44
+
45
+ // Validate accountData
46
+ if (!accountData) {
47
+ throw new Error("Account data is required. Please select a MobilePush account.");
48
+ }
49
+
50
+ const accountId = accountData?.accountId || accountData?.id;
51
+ const licenseCode = accountData?.licenseCode || accountData?.sourceAccountIdentifier;
52
+ const sourceType = accountData?.sourceType || accountData?.sourceTypeName;
53
+
54
+ // Ensure imageSrc has the required properties
55
+ const safeImageSrc = {
56
+ androidImageSrc: imageSrc?.androidImageSrc || "",
57
+ iosImageSrc: imageSrc?.iosImageSrc || "",
58
+ };
59
+
60
+ // Helper to build expandableDetails for legacy and rich media
61
+ function buildExpandableDetails(content, contentImageSrc, platform = ANDROID) {
62
+ const style = content?.expandableDetails?.style || content?.mediaType || BIG_TEXT;
63
+ const expandableDetails = { style };
64
+
65
+ if (style === IMAGE || style === BIG_PICTURE) {
66
+ expandableDetails.style = BIG_PICTURE;
67
+ expandableDetails.message = content?.message;
68
+ expandableDetails.image = contentImageSrc || "";
69
+ } else if (style === VIDEO) {
70
+ // Always build media array for VIDEO
71
+ let url = '';
72
+ let text = '';
73
+ let videoPreviewUrl = '';
74
+ if (mpushVideoSrcAndPreview?.mpushVideoSrc) {
75
+ url = mpushVideoSrcAndPreview?.mpushVideoSrc;
76
+ videoPreviewUrl = mpushVideoSrcAndPreview?.mpushVideoPreviewImg || '';
77
+ } else if (content?.mediaList && content?.mediaList[0]) {
78
+ const { url: mediaUrl = '', text: mediaText = '' } = content?.mediaList[0];
79
+ url = mediaUrl;
80
+ text = mediaText;
81
+ }
82
+ expandableDetails.media = [
83
+ {
84
+ url,
85
+ text,
86
+ type: VIDEO,
87
+ videoPreviewUrl,
88
+ },
89
+ ];
90
+ } else if (style === GIF) {
91
+ // Handle GIF: preserve original type from mediaList if available, otherwise convert to VIDEO
92
+ let url = '';
93
+ let text = '';
94
+ let mediaType = VIDEO; // Default to VIDEO for backend compatibility
95
+
96
+ if (content?.mediaList && content?.mediaList[0]) {
97
+ // If mediaList exists, use it and preserve the original type
98
+ const { url: mediaUrl = '', text: mediaText = '', type: originalType = VIDEO } = content?.mediaList[0];
99
+ url = mediaUrl;
100
+ text = mediaText;
101
+ mediaType = originalType; // Preserve original type from mediaList
102
+ } else if (mpushVideoSrcAndPreview?.mpushVideoSrc) {
103
+ // If no mediaList but mpushVideoSrc exists, convert to VIDEO
104
+ url = mpushVideoSrcAndPreview?.mpushVideoSrc;
105
+ mediaType = VIDEO;
106
+ }
107
+
108
+ expandableDetails.media = [
109
+ {
110
+ url,
111
+ text,
112
+ type: mediaType,
113
+ },
114
+ ];
115
+ } else if (style === BIG_TEXT || style === TEXT || style === NONE) {
116
+ expandableDetails.style = BIG_TEXT;
117
+ expandableDetails.message = content?.message;
118
+ } else if ((style === CAROUSEL || style === MANUAL_CAROUSEL || style === AUTO_CAROUSEL || style === FILMSTRIP_CAROUSEL)) {
119
+ // Handle carousel data
120
+ expandableDetails.style = 'MANUAL_CAROUSEL';
121
+ expandableDetails.message = content?.message;
122
+
123
+ // Add carousel data if it exists
124
+ if (content?.carouselData && Array.isArray(content?.carouselData) && content?.carouselData.length > 0) {
125
+ expandableDetails.carouselData = content?.carouselData.map((card) => ({
126
+ mediaType: card.mediaType || 'image',
127
+ imageUrl: card.imageUrl || '',
128
+ videoSrc: card.videoSrc || '',
129
+ buttons: card.buttons || [],
130
+ }));
131
+ }
132
+
133
+ // Handle mediaList for carousel (used in tests and some scenarios)
134
+ if (content?.mediaList && Array.isArray(content?.mediaList) && content?.mediaList.length > 0) {
135
+ expandableDetails.media = content?.mediaList.map((m) => ({
136
+ url: m.url,
137
+ text: m.text || "",
138
+ type: m.type,
139
+ }));
140
+ }
141
+ } else if ([GIF].includes(style) && content?.mediaList) {
142
+ expandableDetails.media = (content?.mediaList || []).map((m) => ({
143
+ url: m.url,
144
+ text: m.text || "",
145
+ type: m.type,
146
+ }));
147
+ }
148
+
149
+ // iOS always needs ctas array
150
+ if (platform === IOS) {
151
+ expandableDetails.ctas = content?.expandableDetails?.ctas || [];
152
+ }
153
+
154
+ // Add any additional expandableDetails fields
155
+ if (content?.expandableDetails) {
156
+ Object.entries(content?.expandableDetails).forEach(([k, v]) => {
157
+ if (!(k in expandableDetails)) expandableDetails[k] = v;
158
+ });
159
+ }
160
+ return expandableDetails;
161
+ }
162
+
163
+ // Helper to build custom fields array (legacy)
164
+ function buildCustomFields(content) {
165
+ // If content.custom is already an array, return as is
166
+ if (Array.isArray(content?.custom)) return content?.custom;
167
+ // If it's an object, map to array
168
+ if (content?.custom && typeof content?.custom === 'object') {
169
+ return Object.entries(content?.custom).map(([key, value]) => ({ key, value }));
170
+ }
171
+ return [];
172
+ }
173
+
174
+ // Compose androidContent for legacy payload
175
+ const androidLegacy = {
176
+ luid: "{{luid}}",
177
+ cuid: "{{cuid}}",
178
+ communicationId: "{{communicationId}}",
179
+ title: androidContent.title,
180
+ message: androidContent.message,
181
+ expandableDetails: buildExpandableDetails(androidContent, safeImageSrc.androidImageSrc),
182
+ custom: buildCustomFields(androidContent),
183
+ type: androidContent.mediaType,
184
+ deviceType: 'ANDROID',
185
+ };
186
+
187
+ // Add main notification body CTA if actionOnClick is enabled
188
+ if (androidContent?.actionOnClick && (androidContent?.deepLinkValue || androidContent?.externalLinkValue)) {
189
+ const actionLink = androidContent?.linkType === EXTERNAL_LINK ? androidContent?.externalLinkValue : androidContent?.deepLinkValue;
190
+ androidLegacy.cta = {
191
+ type: androidContent.linkType === EXTERNAL_LINK ? EXTERNAL_URL : DEEP_LINK,
192
+ actionLink,
193
+ };
194
+ }
195
+
196
+ // Compose iosContent for legacy payload
197
+ const iosLegacy = {
198
+ luid: "{{luid}}",
199
+ cuid: "{{cuid}}",
200
+ communicationId: "{{communicationId}}",
201
+ title: iosContent.title,
202
+ message: iosContent.message,
203
+ expandableDetails: buildExpandableDetails(iosContent, safeImageSrc.iosImageSrc, 'IOS'),
204
+ custom: buildCustomFields(iosContent),
205
+ type: iosContent.mediaType,
206
+ deviceType: 'IOS',
207
+ };
208
+
209
+ // Add main notification body CTA if actionOnClick is enabled
210
+ if (iosContent?.actionOnClick && (iosContent?.deepLinkValue || iosContent?.externalLinkValue)) {
211
+ const actionLink = iosContent?.linkType === EXTERNAL_LINK ? iosContent?.externalLinkValue : iosContent?.deepLinkValue;
212
+ iosLegacy.cta = {
213
+ type: iosContent?.linkType === EXTERNAL_LINK ? EXTERNAL_URL : DEEP_LINK,
214
+ actionLink,
215
+ };
216
+ }
217
+
218
+ // Compose the full payload in legacy format
219
+ const payload = {
220
+ versions: {
221
+ base: {
222
+ ANDROID: androidLegacy,
223
+ IOS: iosLegacy,
224
+ },
225
+ },
226
+ type: MOBILE_PUSH_CHANNEL,
227
+ name: trimmedTemplateName,
228
+ definition: {
229
+ accountId,
230
+ licenseCode,
231
+ mode: options?.mode || TEXT.toLowerCase(),
232
+ sourceType,
233
+ sameContent,
234
+ },
235
+ };
236
+
237
+ return payload;
238
+ }
239
+
240
+ export { createPayload };