@capillarytech/creatives-library 8.0.130 → 8.0.132

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 (77) hide show
  1. package/containers/App/constants.js +1 -0
  2. package/containers/Login/index.js +1 -2
  3. package/package.json +1 -1
  4. package/services/api.js +5 -0
  5. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
  6. package/tests/integration/TemplateCreation/api-response.js +5 -0
  7. package/tests/integration/TemplateCreation/msw-handler.js +42 -63
  8. package/utils/common.js +7 -0
  9. package/utils/commonUtils.js +2 -6
  10. package/utils/createMobilePushPayload.js +322 -0
  11. package/utils/tests/createMobilePushPayload.test.js +1054 -0
  12. package/v2Components/CapDeviceContent/index.js +1 -1
  13. package/v2Components/CapImageUpload/index.js +57 -44
  14. package/v2Components/CapInAppCTA/index.js +1 -0
  15. package/v2Components/CapMpushCTA/constants.js +25 -0
  16. package/v2Components/CapMpushCTA/index.js +403 -0
  17. package/v2Components/CapMpushCTA/index.scss +95 -0
  18. package/v2Components/CapMpushCTA/messages.js +101 -0
  19. package/v2Components/CapTagList/index.js +178 -121
  20. package/v2Components/CapVideoUpload/constants.js +3 -0
  21. package/v2Components/CapVideoUpload/index.js +182 -115
  22. package/v2Components/CapVideoUpload/messages.js +16 -0
  23. package/v2Components/Carousel/index.js +15 -13
  24. package/v2Components/ErrorInfoNote/style.scss +1 -0
  25. package/v2Components/MobilePushPreviewV2/index.js +57 -12
  26. package/v2Components/TemplatePreview/_templatePreview.scss +218 -74
  27. package/v2Components/TemplatePreview/assets/images/Android_With_date_and_time.svg +29 -0
  28. package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
  29. package/v2Components/TemplatePreview/assets/images/iOS_With_date_and_time.svg +26 -0
  30. package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
  31. package/v2Components/TemplatePreview/index.js +234 -107
  32. package/v2Components/TemplatePreview/messages.js +4 -0
  33. package/v2Components/TemplatePreview/tests/__snapshots__/index.test.js.snap +10 -10
  34. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -62
  35. package/v2Containers/CreativesContainer/index.js +193 -136
  36. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -22
  37. package/v2Containers/InApp/constants.js +1 -0
  38. package/v2Containers/InApp/index.js +13 -13
  39. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +4748 -4658
  40. package/v2Containers/Login/index.js +1 -2
  41. package/v2Containers/MobilePush/Create/index.js +1 -0
  42. package/v2Containers/MobilePush/commonMethods.js +7 -14
  43. package/v2Containers/MobilePush/tests/commonMethods.test.js +401 -0
  44. package/v2Containers/MobilePushNew/actions.js +116 -0
  45. package/v2Containers/MobilePushNew/components/CtaButtons.js +183 -0
  46. package/v2Containers/MobilePushNew/components/MediaUploaders.js +835 -0
  47. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +346 -0
  48. package/v2Containers/MobilePushNew/components/index.js +5 -0
  49. package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +565 -0
  50. package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +3180 -0
  51. package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +654 -0
  52. package/v2Containers/MobilePushNew/constants.js +116 -0
  53. package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1462 -0
  54. package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1459 -0
  55. package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +366 -0
  56. package/v2Containers/MobilePushNew/hooks/useUpload.js +740 -0
  57. package/v2Containers/MobilePushNew/index.js +2158 -0
  58. package/v2Containers/MobilePushNew/index.scss +308 -0
  59. package/v2Containers/MobilePushNew/messages.js +272 -0
  60. package/v2Containers/MobilePushNew/reducer.js +160 -0
  61. package/v2Containers/MobilePushNew/sagas.js +193 -0
  62. package/v2Containers/MobilePushNew/selectors.js +55 -0
  63. package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
  64. package/v2Containers/MobilePushNew/tests/sagas.test.js +864 -0
  65. package/v2Containers/MobilePushNew/tests/selectors.test.js +665 -0
  66. package/v2Containers/MobilePushNew/tests/utils.test.js +421 -0
  67. package/v2Containers/MobilePushNew/utils.js +84 -0
  68. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1176 -976
  69. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +684 -424
  70. package/v2Containers/TagList/index.js +56 -10
  71. package/v2Containers/Templates/_templates.scss +100 -1
  72. package/v2Containers/Templates/index.js +170 -31
  73. package/v2Containers/Templates/messages.js +8 -0
  74. package/v2Containers/Templates/sagas.js +1 -0
  75. package/v2Containers/Whatsapp/constants.js +1 -0
  76. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +3992 -3677
  77. package/assets/loading_img.gif +0 -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',
@@ -16,7 +16,6 @@ import * as actions from '../../containers/Cap/actions';
16
16
  import { capSaga } from '../Cap/sagas';
17
17
  import injectSaga from '../../utils/injectSaga';
18
18
  import { UserIsNotAuthenticated } from '../../utils/authWrapper';
19
- import loaderGif from '../../assets/loading_img.gif';
20
19
  const logo = require('./assets/images/capillary_logo.png');
21
20
 
22
21
  export class Login extends React.Component { // eslint-disable-line react/prefer-stateless-function
@@ -84,7 +83,7 @@ export class Login extends React.Component { // eslint-disable-line react/prefer
84
83
  ? <div className="cap-loader-box">
85
84
  <img
86
85
  className="loader-image"
87
- src={loaderGif}
86
+ src={"https://storage.crm.n.content-cdn.io/capillary/capillary_loading_logo.gif"}
88
87
  alt="Capillary"/>
89
88
  </div>
90
89
  : <Col className="login-box" style={{textAlign: 'center'}}>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.130",
4
+ "version": "8.0.132",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -281,6 +281,11 @@ export const createMobilePushTemplate = ({template}) => {
281
281
  const url = `${API_ENDPOINT}/templates/MOBILEPUSH`;
282
282
  return request(url, getAPICallObject('POST', template));
283
283
  };
284
+ //this is for new mobile push templates
285
+ export const createMobilePushTemplateV2 = (template) => {
286
+ const url = `${API_ENDPOINT}/templates/MOBILEPUSH`;
287
+ return request(url, getAPICallObject('POST', template));
288
+ };
284
289
 
285
290
  export const duplicateTemplate = ({id, channel}) => {
286
291
  const url = `${API_ENDPOINT}/templates/duplicate/${id}/${channel}`;
@@ -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,322 @@
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, BIG_TEXT, BIG_PICTURE,
6
+ MANUAL_CAROUSEL,
7
+ AUTO_CAROUSEL,
8
+ FILMSTRIP_CAROUSEL,
9
+ ANDROID,
10
+ IOS,
11
+ } from "../v2Containers/MobilePushNew/constants";
12
+ import messages from '../v2Containers/MobilePushNew/messages';
13
+
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)
18
+ * - Supports platform-specific content (Android/iOS)
19
+ * - Handles CTA and custom fields
20
+ * @param {Object} params - The parameters for creating the payload
21
+ * @param {string} params.templateName - The name of the template
22
+ * @param {Object} params.androidContent - Android-specific content
23
+ * @param {Object} params.iosContent - iOS-specific content
24
+ * @param {Object} params.imageSrc - Image source URLs
25
+ * @param {Object} params.mpushVideoSrcAndPreview - Video source and preview URLs
26
+ * @param {Object} params.accountData - Account data for the payload
27
+ * @param {boolean} params.sameContent - Whether content is shared between platforms
28
+ * @param {Object} params.options - Additional options
29
+ * @param {Object} params.intl - Internationalization object for messages
30
+ */
31
+ const createMobilePushPayload = ({
32
+ templateName,
33
+ androidContent,
34
+ iosContent,
35
+ imageSrc = {},
36
+ mpushVideoSrcAndPreview = {},
37
+ accountData,
38
+ sameContent = false,
39
+ options = {},
40
+ intl,
41
+ }) => {
42
+ // Platform support checks must be at the top
43
+ const isAndroidSupported = accountData?.configs?.android === '1';
44
+ const isIosSupported = accountData?.configs?.ios === '1';
45
+
46
+ const {
47
+ accountId: accountIdFromAccountData, id, licenseCode: licenseCodeFromAccountData, sourceAccountIdentifier, sourceType: sourceTypeFromAccountData, sourceTypeName,
48
+ } = accountData || {};
49
+ // Validate account data
50
+ const accountId = accountIdFromAccountData || id;
51
+ const licenseCode = licenseCodeFromAccountData || sourceAccountIdentifier;
52
+ const sourceType = sourceTypeFromAccountData || sourceTypeName;
53
+
54
+ if (!accountId || !licenseCode || !sourceType) {
55
+ throw new Error(intl.formatMessage(messages.accountDataRequiredError));
56
+ }
57
+
58
+ // Validate template name
59
+ const trimmedTemplateName = templateName?.trim();
60
+ if (!trimmedTemplateName) {
61
+ throw new Error(intl.formatMessage(messages.templateNameEmptyError));
62
+ }
63
+
64
+ // Validate content
65
+ if (isAndroidSupported && (!androidContent?.title || !androidContent?.message)) {
66
+ throw new Error(intl.formatMessage(messages.contentValidationError, { platform: 'Android' }));
67
+ }
68
+ if (isIosSupported && (!iosContent?.title || !iosContent?.message)) {
69
+ throw new Error(intl.formatMessage(messages.contentValidationError, { platform: 'iOS' }));
70
+ }
71
+
72
+ // Ensure imageSrc has the required properties
73
+ const safeImageSrc = {
74
+ androidImageSrc: imageSrc?.androidImageSrc || '',
75
+ iosImageSrc: imageSrc?.iosImageSrc || '',
76
+ };
77
+
78
+ // Ensure mpushVideoSrcAndPreview has the required properties
79
+ const safeMpushVideoSrcAndPreview = {
80
+ mpushVideoSrc: mpushVideoSrcAndPreview?.mpushVideoSrc || '',
81
+ mpushVideoPreviewImg: mpushVideoSrcAndPreview?.mpushVideoPreviewImg || '',
82
+ duration: mpushVideoSrcAndPreview?.duration || 0,
83
+ };
84
+
85
+ // Build the payload
86
+ const payload = {
87
+ name: trimmedTemplateName,
88
+ definition: {
89
+ accountId,
90
+ licenseCode,
91
+ sourceType,
92
+ sameContent,
93
+ mode: options?.mode,
94
+ },
95
+ versions: {
96
+ base: {},
97
+ },
98
+ };
99
+
100
+ // Build Android content
101
+ if (isAndroidSupported) {
102
+ payload.versions.base.ANDROID = buildPlatformContent(
103
+ androidContent,
104
+ safeImageSrc.androidImageSrc,
105
+ safeMpushVideoSrcAndPreview,
106
+ ANDROID,
107
+ );
108
+ }
109
+
110
+ // Build iOS content
111
+ if (isIosSupported) {
112
+ payload.versions.base.IOS = buildPlatformContent(
113
+ iosContent,
114
+ safeImageSrc.iosImageSrc,
115
+ safeMpushVideoSrcAndPreview,
116
+ IOS,
117
+ );
118
+ }
119
+
120
+ return payload;
121
+ };
122
+
123
+ /**
124
+ * Builds platform-specific content for the payload
125
+ * @param {Object} content - Platform content (Android/iOS)
126
+ * @param {string} imageSrc - Image source URL
127
+ * @param {Object} mpushVideoSrcAndPreview - Video source and preview URLs
128
+ * @param {string} platform - Platform (ANDROID/IOS)
129
+ * @returns {Object} Platform-specific content
130
+ */
131
+ function buildPlatformContent(content, imageSrc, mpushVideoSrcAndPreview, platform) {
132
+ const {
133
+ title = '',
134
+ message = '',
135
+ expandableDetails = {},
136
+ actionOnClick,
137
+ linkType,
138
+ deepLinkValue,
139
+ externalLinkValue,
140
+ } = content || {};
141
+
142
+ const platformContent = {
143
+ title,
144
+ message,
145
+ expandableDetails: { ...expandableDetails },
146
+ custom: buildCustomFields(content),
147
+ };
148
+
149
+ // Handle CTA
150
+ if (actionOnClick) {
151
+ platformContent.cta = {
152
+ type: linkType === DEEP_LINK ? DEEP_LINK : EXTERNAL_URL,
153
+ actionLink: linkType === DEEP_LINK ? deepLinkValue : externalLinkValue,
154
+ };
155
+ }
156
+
157
+ // Handle media types
158
+ const style = content?.mediaType || expandableDetails?.style;
159
+
160
+ // Handle BIG_PICTURE media type
161
+ if (style === BIG_PICTURE || style === IMAGE) {
162
+ platformContent.expandableDetails.style = BIG_PICTURE;
163
+ platformContent.expandableDetails.image = imageSrc;
164
+ } else if (style === VIDEO) { // Handle VIDEO media type
165
+ platformContent.expandableDetails.style = VIDEO;
166
+ platformContent.expandableDetails.media = [{
167
+ url: mpushVideoSrcAndPreview.mpushVideoSrc || (content?.mediaList?.[0]?.url || ''),
168
+ text: content?.mediaList?.[0]?.text || '',
169
+ type: VIDEO,
170
+ }];
171
+ } else if (style === GIF) { // Handle GIF media type
172
+ platformContent.expandableDetails.style = GIF;
173
+ if (content?.mediaList?.[0]) {
174
+ // If mediaList exists, use it and preserve the original type
175
+ const { url = '', text = '', type = GIF } = content.mediaList[0];
176
+ platformContent.expandableDetails.media = [{
177
+ url,
178
+ text,
179
+ type: type.toLowerCase(),
180
+ }];
181
+ } else {
182
+ // If no mediaList but mpushVideoSrc exists, convert to VIDEO
183
+ platformContent.expandableDetails.media = [{
184
+ url: mpushVideoSrcAndPreview.mpushVideoSrc,
185
+ text: '',
186
+ type: GIF, // Backend expects GIF type for GIFs
187
+ }];
188
+ }
189
+ } else if ([CAROUSEL, MANUAL_CAROUSEL, AUTO_CAROUSEL, FILMSTRIP_CAROUSEL].includes(style)) { // Handle CAROUSEL media types
190
+ platformContent.expandableDetails.style = MANUAL_CAROUSEL;
191
+
192
+ // Handle carouselData
193
+ if (content?.carouselData?.length > 0) {
194
+ platformContent.expandableDetails.carouselData = content.carouselData.map((card) => {
195
+ const {
196
+ mediaType,
197
+ imageUrl,
198
+ videoSrc,
199
+ buttons,
200
+ } = card || {};
201
+
202
+ return {
203
+ mediaType: mediaType || IMAGE.toLowerCase(),
204
+ imageUrl: imageUrl || '',
205
+ videoSrc: videoSrc || '',
206
+ buttons: buttons ? buttons.map((button) => {
207
+ const { linkType: buttonLinkType, deepLinkKeys, deepLinkValue: buttonDeepLinkValue } = button || {};
208
+ if (buttonLinkType === DEEP_LINK && deepLinkKeys && buttonDeepLinkValue) {
209
+ const deepLinkKeysArray = Array.isArray(deepLinkKeys) ? deepLinkKeys : [deepLinkKeys];
210
+ const updatedDeepLinkValue = appendDeepLinkKeysToUrl(buttonDeepLinkValue, deepLinkKeysArray);
211
+
212
+ return {
213
+ ...button,
214
+ deepLinkValue: updatedDeepLinkValue,
215
+ };
216
+ }
217
+ return button;
218
+ }) : [],
219
+ };
220
+ });
221
+ }
222
+
223
+ // Handle mediaList
224
+ if (content?.mediaList && Array.isArray(content.mediaList) && content.mediaList.length > 0) {
225
+ platformContent.expandableDetails.media = content.mediaList.map((media) => {
226
+ const {
227
+ url,
228
+ text,
229
+ type,
230
+ } = media || {};
231
+
232
+ return {
233
+ url,
234
+ text: text || '',
235
+ type,
236
+ };
237
+ });
238
+ }
239
+ } else { // Handle BIG_TEXT media type (default)
240
+ platformContent.expandableDetails.style = BIG_TEXT;
241
+ platformContent.expandableDetails.message = message;
242
+ }
243
+
244
+ // Handle GIF media type
245
+ if (content?.mediaType === GIF) {
246
+ platformContent.expandableDetails = {
247
+ media: [{
248
+ url: mpushVideoSrcAndPreview?.mpushVideoSrc || (content?.mediaList?.[0]?.url || ''),
249
+ text: content?.mediaList?.[0]?.text || '',
250
+ type: GIF,
251
+ }],
252
+ };
253
+ }
254
+
255
+ // iOS-specific handling
256
+ if (platform === IOS) {
257
+ // iOS always needs a ctas array in expandableDetails
258
+ platformContent.expandableDetails.ctas = platformContent.expandableDetails.ctas || [];
259
+ }
260
+
261
+ return platformContent;
262
+ }
263
+
264
+ /**
265
+ * Builds custom fields array from content
266
+ * @param {Object} content - Platform content (Android/iOS)
267
+ * @returns {Array} Array of custom fields
268
+ */
269
+ function buildCustomFields(content) {
270
+ let customFields = [];
271
+ const { custom = {} } = content || {};
272
+ // If content.custom is already an array, use it as base
273
+ if (Array.isArray(custom)) {
274
+ customFields = [...custom];
275
+ } else if (custom && typeof custom === 'object') {
276
+ // If it's an object, map to array
277
+ customFields = Object.entries(custom).map(([key, value]) => ({ key, value }));
278
+ }
279
+ const { deepLinkKeysValue, deepLinkValue, linkType } = custom || {};
280
+ // Add deepLinkKeys to custom fields if it exists
281
+ if (deepLinkKeysValue && linkType === DEEP_LINK) {
282
+ if (Array.isArray(deepLinkKeysValue)) {
283
+ // Handle array of deep link keys
284
+ deepLinkKeysValue.forEach((key) => {
285
+ if (key) {
286
+ customFields.push({
287
+ key,
288
+ value: deepLinkValue,
289
+ });
290
+ }
291
+ });
292
+ } else {
293
+ // Handle single deep link key
294
+ customFields.push({
295
+ key: deepLinkKeysValue,
296
+ value: deepLinkValue,
297
+ });
298
+ }
299
+ }
300
+
301
+ return customFields;
302
+ }
303
+
304
+ /**
305
+ * Appends deep link keys to URL
306
+ * @param {string} url - Base URL
307
+ * @param {Array} keys - Deep link keys
308
+ * @returns {string} URL with appended keys
309
+ */
310
+ function appendDeepLinkKeysToUrl(url, keys) {
311
+ if (!url || !keys || !Array.isArray(keys) || keys?.length === 0) return url;
312
+
313
+ const separator = url.includes('?') ? '&' : '?';
314
+ const validKeys = keys.filter((key) => typeof key === 'string' && key?.length > 0);
315
+ if (validKeys?.length === 0) return url;
316
+
317
+ const keyParams = validKeys.map((key) => `${key}={{${key}}}`).join('&');
318
+
319
+ return `${url}${separator}${keyParams}`;
320
+ }
321
+
322
+ export default createMobilePushPayload;