@capillarytech/creatives-library 8.0.129 → 8.0.131

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 (55) hide show
  1. package/containers/Login/index.js +1 -2
  2. package/containers/Templates/constants.js +10 -1
  3. package/containers/Templates/index.js +45 -45
  4. package/package.json +1 -1
  5. package/services/api.js +14 -7
  6. package/services/tests/haptic-api.test.js +387 -0
  7. package/utils/createMobilePushPayload.js +322 -0
  8. package/utils/tests/{createPayload.test.js → createMobilePushPayload.test.js} +333 -64
  9. package/utils/tests/vendorDataTransformers.test.js +512 -0
  10. package/utils/vendorDataTransformers.js +108 -0
  11. package/v2Components/CapDeviceContent/index.js +1 -1
  12. package/v2Components/CapDocumentUpload/index.js +2 -2
  13. package/v2Components/CapImageUpload/index.js +2 -2
  14. package/v2Components/CapMpushCTA/index.js +13 -12
  15. package/v2Components/CapTagList/index.js +5 -5
  16. package/v2Components/CapVideoUpload/index.js +17 -7
  17. package/v2Components/MobilePushPreviewV2/index.js +28 -15
  18. package/v2Components/TemplatePreview/_templatePreview.scss +131 -29
  19. package/v2Components/TemplatePreview/index.js +130 -131
  20. package/v2Components/TemplatePreview/tests/__snapshots__/index.test.js.snap +10 -10
  21. package/v2Containers/CreativesContainer/index.js +6 -4
  22. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +4748 -4658
  23. package/v2Containers/Login/index.js +1 -2
  24. package/v2Containers/MobilePush/tests/commonMethods.test.js +401 -0
  25. package/v2Containers/MobilePushNew/components/CtaButtons.js +18 -16
  26. package/v2Containers/MobilePushNew/components/MediaUploaders.js +46 -45
  27. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +12 -11
  28. package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +134 -367
  29. package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +1209 -143
  30. package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +314 -3
  31. package/v2Containers/MobilePushNew/constants.js +1 -0
  32. package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +163 -0
  33. package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1131 -895
  34. package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +172 -52
  35. package/v2Containers/MobilePushNew/hooks/useUpload.js +88 -74
  36. package/v2Containers/MobilePushNew/index.js +278 -1532
  37. package/v2Containers/MobilePushNew/messages.js +30 -0
  38. package/v2Containers/MobilePushNew/sagas.js +2 -7
  39. package/v2Containers/MobilePushNew/tests/sagas.test.js +41 -40
  40. package/v2Containers/MobilePushNew/tests/selectors.test.js +240 -0
  41. package/v2Containers/MobilePushNew/tests/utils.test.js +118 -19
  42. package/v2Containers/MobilePushNew/utils.js +53 -2
  43. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1171 -971
  44. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +684 -424
  45. package/v2Containers/Templates/_templates.scss +0 -1
  46. package/v2Containers/Templates/index.js +58 -29
  47. package/v2Containers/Templates/sagas.js +0 -1
  48. package/v2Containers/Whatsapp/constants.js +32 -0
  49. package/v2Containers/Whatsapp/index.js +104 -25
  50. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +3992 -3677
  51. package/v2Containers/Whatsapp/tests/haptic.test.js +405 -0
  52. package/assets/loading_img.gif +0 -0
  53. package/utils/createPayload.js +0 -405
  54. /package/v2Components/TemplatePreview/assets/images/{Android _ With date and time.svg → Android_With_date_and_time.svg} +0 -0
  55. /package/v2Components/TemplatePreview/assets/images/{iOS _ With date and time.svg → iOS_With_date_and_time.svg} +0 -0
@@ -0,0 +1,387 @@
1
+ import { uploadFile, createWhatsappTemplate } from '../api';
2
+
3
+ // Mock fetch
4
+ global.fetch = jest.fn();
5
+
6
+ describe('HAPTIC API Integration Tests', () => {
7
+ beforeEach(() => {
8
+ jest.clearAllMocks();
9
+ fetch.mockClear();
10
+ });
11
+
12
+ describe('uploadFile with HAPTIC parameters', () => {
13
+ it('should append HAPTIC-specific parameters to FormData', () => {
14
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
15
+ const whatsappParams = {
16
+ source: 'whatsapp',
17
+ wabaId: 'waba-123',
18
+ access_token: 'haptic-token',
19
+ phoneId: 'phone-456',
20
+ hostName: 'hapticwhatsappbulk',
21
+ };
22
+
23
+ // Mock successful response
24
+ fetch.mockResolvedValueOnce({
25
+ status: 200,
26
+ json: () => Promise.resolve({ success: true }),
27
+ });
28
+
29
+ uploadFile({
30
+ file: mockFile,
31
+ assetType: 'whatsapp',
32
+ whatsappParams,
33
+ });
34
+
35
+ // Verify fetch was called
36
+ expect(fetch).toHaveBeenCalledTimes(1);
37
+
38
+ const fetchCall = fetch.mock.calls[0];
39
+ const url = fetchCall[0];
40
+ const options = fetchCall[1];
41
+
42
+ expect(url).toContain('/assets/whatsapp');
43
+ expect(options.method).toBe('POST');
44
+
45
+ // Verify FormData contains HAPTIC parameters
46
+ const formData = options.body;
47
+ expect(formData).toBeInstanceOf(FormData);
48
+
49
+ // Check that FormData contains the file
50
+ expect(formData.get('file')).not.toBeNull();
51
+
52
+ // Check that FormData contains HAPTIC parameters
53
+ expect(formData.get('source')).toBe('whatsapp');
54
+ expect(formData.get('wabaId')).toBe('waba-123');
55
+ expect(formData.get('accessToken')).toBe('haptic-token'); // Note: API converts access_token to accessToken
56
+ expect(formData.get('phoneId')).toBe('phone-456');
57
+ expect(formData.get('hostName')).toBe('hapticwhatsappbulk');
58
+ });
59
+
60
+ it('should handle missing HAPTIC parameters gracefully', () => {
61
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
62
+ const whatsappParams = {
63
+ source: 'whatsapp',
64
+ // Missing parameters
65
+ };
66
+
67
+ fetch.mockResolvedValueOnce({
68
+ status: 200,
69
+ json: () => Promise.resolve({ success: true }),
70
+ });
71
+
72
+ uploadFile({
73
+ file: mockFile,
74
+ assetType: 'whatsapp',
75
+ whatsappParams,
76
+ });
77
+
78
+ const fetchCall = fetch.mock.calls[0];
79
+ const formData = fetchCall[1].body;
80
+
81
+ expect(formData.get('source')).toBe('whatsapp');
82
+ expect(formData.get('wabaId')).toBe('undefined');
83
+ expect(formData.get('accessToken')).toBe('undefined');
84
+ expect(formData.get('phoneId')).toBe('undefined');
85
+ expect(formData.get('hostName')).toBe('undefined');
86
+ });
87
+
88
+ it('should handle undefined whatsappParams', () => {
89
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
90
+
91
+ fetch.mockResolvedValueOnce({
92
+ status: 200,
93
+ json: () => Promise.resolve({ success: true }),
94
+ });
95
+
96
+ uploadFile({
97
+ file: mockFile,
98
+ assetType: 'whatsapp',
99
+ // whatsappParams is undefined
100
+ });
101
+
102
+ const fetchCall = fetch.mock.calls[0];
103
+ const formData = fetchCall[1].body;
104
+
105
+ expect(formData.get('file')).not.toBeNull();
106
+ // When whatsappParams is undefined, no whatsapp-specific fields are added
107
+ expect(formData.get('source')).toBeNull();
108
+ expect(formData.get('wabaId')).toBeNull();
109
+ expect(formData.get('accessToken')).toBeNull();
110
+ expect(formData.get('phoneId')).toBeNull();
111
+ expect(formData.get('hostName')).toBeNull();
112
+ });
113
+
114
+ it('should handle empty string parameters', () => {
115
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
116
+ const whatsappParams = {
117
+ source: 'whatsapp',
118
+ wabaId: '',
119
+ access_token: '',
120
+ phoneId: '',
121
+ hostName: '',
122
+ };
123
+
124
+ fetch.mockResolvedValueOnce({
125
+ status: 200,
126
+ json: () => Promise.resolve({ success: true }),
127
+ });
128
+
129
+ uploadFile({
130
+ file: mockFile,
131
+ assetType: 'whatsapp',
132
+ whatsappParams,
133
+ });
134
+
135
+ const fetchCall = fetch.mock.calls[0];
136
+ const formData = fetchCall[1].body;
137
+
138
+ expect(formData.get('wabaId')).toBe('');
139
+ expect(formData.get('accessToken')).toBe('');
140
+ expect(formData.get('phoneId')).toBe('');
141
+ expect(formData.get('hostName')).toBe('');
142
+ });
143
+
144
+ it('should handle null parameters', () => {
145
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
146
+ const whatsappParams = {
147
+ source: 'whatsapp',
148
+ wabaId: null,
149
+ access_token: null,
150
+ phoneId: null,
151
+ hostName: null,
152
+ };
153
+
154
+ fetch.mockResolvedValueOnce({
155
+ status: 200,
156
+ json: () => Promise.resolve({ success: true }),
157
+ });
158
+
159
+ uploadFile({
160
+ file: mockFile,
161
+ assetType: 'whatsapp',
162
+ whatsappParams,
163
+ });
164
+
165
+ const fetchCall = fetch.mock.calls[0];
166
+ const formData = fetchCall[1].body;
167
+
168
+ expect(formData.get('wabaId')).toBe('null');
169
+ expect(formData.get('accessToken')).toBe('null');
170
+ expect(formData.get('phoneId')).toBe('null');
171
+ expect(formData.get('hostName')).toBe('null');
172
+ });
173
+ });
174
+
175
+ describe('createWhatsappTemplate with HAPTIC payload', () => {
176
+ it('should create FormData with HAPTIC payload structure', () => {
177
+ const payload = {
178
+ name: 'HAPTIC Test Template',
179
+ category: 'MARKETING',
180
+ content: 'Hello {{1}}',
181
+ wabaId: 'waba-123',
182
+ access_token: 'haptic-token',
183
+ PhoneId: 'phone-456',
184
+ hostName: 'hapticwhatsappbulk',
185
+ hapticFileHandle: 'haptic-handle-123',
186
+ status: 'unsubmitted',
187
+ };
188
+
189
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
190
+
191
+ fetch.mockResolvedValueOnce({
192
+ status: 200,
193
+ json: () => Promise.resolve({ success: true }),
194
+ });
195
+
196
+ createWhatsappTemplate({ payload, gupshupMediaFile: mockFile });
197
+
198
+ const fetchCall = fetch.mock.calls[0];
199
+ const url = fetchCall[0];
200
+ const options = fetchCall[1];
201
+
202
+ expect(url).toContain('/templates/WHATSAPP');
203
+ expect(options.method).toBe('POST');
204
+
205
+ const formData = options.body;
206
+ expect(formData).toBeInstanceOf(FormData);
207
+
208
+ // Check payload JSON
209
+ const payloadJson = JSON.parse(formData.get('payload'));
210
+ expect(payloadJson).toEqual(payload);
211
+
212
+ // Check file
213
+ expect(formData.get('gupshupMediaFile')).toBe(mockFile);
214
+ });
215
+
216
+ it('should handle missing media file', () => {
217
+ const payload = {
218
+ name: 'HAPTIC Test Template',
219
+ category: 'MARKETING',
220
+ content: 'Hello {{1}}',
221
+ wabaId: 'waba-123',
222
+ access_token: 'haptic-token',
223
+ PhoneId: 'phone-456',
224
+ hostName: 'hapticwhatsappbulk',
225
+ };
226
+
227
+ fetch.mockResolvedValueOnce({
228
+ status: 200,
229
+ json: () => Promise.resolve({ success: true }),
230
+ });
231
+
232
+ createWhatsappTemplate({ payload });
233
+
234
+ const fetchCall = fetch.mock.calls[0];
235
+ const formData = fetchCall[1].body;
236
+
237
+ expect(formData.get('payload')).toBe(JSON.stringify(payload));
238
+ expect(formData.get('gupshupMediaFile')).toBe('undefined');
239
+ });
240
+
241
+ it('should handle array of media files', () => {
242
+ const payload = {
243
+ name: 'HAPTIC Test Template',
244
+ category: 'MARKETING',
245
+ content: 'Hello {{1}}',
246
+ wabaId: 'waba-123',
247
+ access_token: 'haptic-token',
248
+ PhoneId: 'phone-456',
249
+ hostName: 'hapticwhatsappbulk',
250
+ };
251
+
252
+ const mockFiles = [
253
+ new File(['test1'], 'test1.jpg', { type: 'image/jpeg' }),
254
+ new File(['test2'], 'test2.jpg', { type: 'image/jpeg' }),
255
+ ];
256
+
257
+ fetch.mockResolvedValueOnce({
258
+ status: 200,
259
+ json: () => Promise.resolve({ success: true }),
260
+ });
261
+
262
+ createWhatsappTemplate({ payload, gupshupMediaFile: mockFiles });
263
+
264
+ const fetchCall = fetch.mock.calls[0];
265
+ const formData = fetchCall[1].body;
266
+
267
+ expect(formData.get('gupshupMediaFile_0')).toBe(mockFiles[0]);
268
+ expect(formData.get('gupshupMediaFile_1')).toBe(mockFiles[1]);
269
+ });
270
+ });
271
+
272
+ describe('Parameter validation', () => {
273
+ it('should validate required HAPTIC parameters', () => {
274
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
275
+ const whatsappParams = {
276
+ source: 'whatsapp',
277
+ wabaId: 'waba-123',
278
+ access_token: 'haptic-token',
279
+ phoneId: 'phone-456',
280
+ hostName: 'hapticwhatsappbulk',
281
+ };
282
+
283
+ fetch.mockResolvedValueOnce({
284
+ status: 200,
285
+ json: () => Promise.resolve({ success: true }),
286
+ });
287
+
288
+ uploadFile({
289
+ file: mockFile,
290
+ assetType: 'whatsapp',
291
+ whatsappParams,
292
+ });
293
+
294
+ const fetchCall = fetch.mock.calls[0];
295
+ const formData = fetchCall[1].body;
296
+
297
+ // All required parameters should be present
298
+ expect(formData.get('wabaId')).toBe('waba-123');
299
+ expect(formData.get('accessToken')).toBe('haptic-token');
300
+ expect(formData.get('phoneId')).toBe('phone-456');
301
+ expect(formData.get('hostName')).toBe('hapticwhatsappbulk');
302
+ });
303
+
304
+ it('should handle mixed parameter types', () => {
305
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
306
+ const whatsappParams = {
307
+ source: 'whatsapp',
308
+ wabaId: 'waba-123',
309
+ access_token: 'haptic-token',
310
+ phoneId: '', // empty string
311
+ hostName: null, // null value
312
+ };
313
+
314
+ fetch.mockResolvedValueOnce({
315
+ status: 200,
316
+ json: () => Promise.resolve({ success: true }),
317
+ });
318
+
319
+ uploadFile({
320
+ file: mockFile,
321
+ assetType: 'whatsapp',
322
+ whatsappParams,
323
+ });
324
+
325
+ const fetchCall = fetch.mock.calls[0];
326
+ const formData = fetchCall[1].body;
327
+
328
+ expect(formData.get('wabaId')).toBe('waba-123');
329
+ expect(formData.get('accessToken')).toBe('haptic-token');
330
+ expect(formData.get('phoneId')).toBe('');
331
+ expect(formData.get('hostName')).toBe('null');
332
+ });
333
+ });
334
+
335
+ describe('Error handling', () => {
336
+ it('should handle API errors gracefully', async () => {
337
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
338
+ const whatsappParams = {
339
+ source: 'whatsapp',
340
+ wabaId: 'waba-123',
341
+ access_token: 'haptic-token',
342
+ phoneId: 'phone-456',
343
+ hostName: 'hapticwhatsappbulk',
344
+ };
345
+
346
+ fetch.mockRejectedValueOnce(new Error('Network error'));
347
+
348
+ try {
349
+ await uploadFile({
350
+ file: mockFile,
351
+ assetType: 'whatsapp',
352
+ whatsappParams,
353
+ });
354
+ } catch (error) {
355
+ expect(error.message).toBe('Network error');
356
+ }
357
+ });
358
+
359
+ it('should handle HTTP error responses', async () => {
360
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
361
+ const whatsappParams = {
362
+ source: 'whatsapp',
363
+ wabaId: 'waba-123',
364
+ access_token: 'haptic-token',
365
+ phoneId: 'phone-456',
366
+ hostName: 'hapticwhatsappbulk',
367
+ };
368
+
369
+ fetch.mockResolvedValueOnce({
370
+ status: 400,
371
+ statusText: 'Bad Request',
372
+ json: () => Promise.resolve({ error: 'Invalid parameters' }),
373
+ });
374
+
375
+ try {
376
+ await uploadFile({
377
+ file: mockFile,
378
+ assetType: 'whatsapp',
379
+ whatsappParams,
380
+ });
381
+ } catch (error) {
382
+ expect(error.status).toBe(400);
383
+ expect(error.statusText).toBe('Bad Request');
384
+ }
385
+ });
386
+ });
387
+ });
@@ -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;