@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
@@ -1,405 +0,0 @@
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
- // Platform support checks must be at the top
31
- const isAndroidSupported = accountData?.configs?.android === '1';
32
- const isIosSupported = accountData?.configs?.ios === '1';
33
- // Validate and trim template name
34
- const trimmedTemplateName = (templateName || "").trim();
35
- if (!trimmedTemplateName) {
36
- throw new Error("Template name is empty or contains only white spaces");
37
- }
38
-
39
- // Validate androidContent and iosContent
40
- if (androidContent && (!androidContent?.title || !androidContent?.message)) {
41
- throw new Error("Android content must have title and message");
42
- }
43
-
44
- if (iosContent && (!iosContent?.title || !iosContent?.message)) {
45
- throw new Error("iOS content must have title and message");
46
- }
47
-
48
- // Validate accountData
49
- if (!accountData) {
50
- throw new Error("Account data is required. Please select a MobilePush account.");
51
- }
52
-
53
- const accountId = accountData?.accountId || accountData?.id;
54
- const licenseCode = accountData?.licenseCode || accountData?.sourceAccountIdentifier;
55
- const sourceType = accountData?.sourceType || accountData?.sourceTypeName;
56
-
57
- // Ensure imageSrc has the required properties
58
- const safeImageSrc = {
59
- androidImageSrc: imageSrc?.androidImageSrc || "",
60
- iosImageSrc: imageSrc?.iosImageSrc || "",
61
- };
62
-
63
- // Helper to build expandableDetails for legacy and rich media
64
- function buildExpandableDetails(content, contentImageSrc, platform = ANDROID) {
65
- const style = content?.expandableDetails?.style || content?.mediaType || BIG_TEXT;
66
- const expandableDetails = { style };
67
-
68
- if (style === IMAGE || style === BIG_PICTURE) {
69
- expandableDetails.style = BIG_PICTURE;
70
- expandableDetails.message = content?.message;
71
- expandableDetails.image = contentImageSrc || "";
72
- } else if (style === VIDEO) {
73
- expandableDetails.message = content?.message;
74
- // Always build media array for VIDEO
75
- let url = '';
76
- let text = '';
77
- let videoPreviewUrl = '';
78
- if (mpushVideoSrcAndPreview?.mpushVideoSrc) {
79
- url = mpushVideoSrcAndPreview?.mpushVideoSrc;
80
- videoPreviewUrl = mpushVideoSrcAndPreview?.mpushVideoPreviewImg || '';
81
- } else if (content?.mediaList && content?.mediaList[0]) {
82
- const { url: mediaUrl = '', text: mediaText = '' } = content?.mediaList[0];
83
- url = mediaUrl;
84
- text = mediaText;
85
- }
86
- expandableDetails.media = [
87
- {
88
- url,
89
- text,
90
- type: VIDEO,
91
- videoPreviewUrl,
92
- },
93
- ];
94
- } else if (style === GIF) {
95
- expandableDetails.message = content?.message;
96
- // Handle GIF: preserve original type from mediaList if available, otherwise convert to VIDEO
97
- let url = '';
98
- let text = '';
99
- let mediaType = VIDEO; // Default to VIDEO for backend compatibility
100
-
101
- if (content?.mediaList && content?.mediaList[0]) {
102
- // If mediaList exists, use it and preserve the original type
103
- const { url: mediaUrl = '', text: mediaText = '', type: originalType = VIDEO } = content?.mediaList[0];
104
- url = mediaUrl;
105
- text = mediaText;
106
- mediaType = originalType; // Preserve original type from mediaList
107
- } else if (mpushVideoSrcAndPreview?.mpushVideoSrc) {
108
- // If no mediaList but mpushVideoSrc exists, convert to VIDEO
109
- url = mpushVideoSrcAndPreview?.mpushVideoSrc;
110
- mediaType = VIDEO;
111
- }
112
-
113
- expandableDetails.media = [
114
- {
115
- url,
116
- text,
117
- type: mediaType,
118
- },
119
- ];
120
- } else if (style === BIG_TEXT || style === TEXT || style === NONE) {
121
- expandableDetails.style = BIG_TEXT;
122
- expandableDetails.message = content?.message;
123
- } else if ((style === CAROUSEL || style === MANUAL_CAROUSEL || style === AUTO_CAROUSEL || style === FILMSTRIP_CAROUSEL)) {
124
- // Handle carousel data
125
- expandableDetails.style = 'MANUAL_CAROUSEL';
126
- expandableDetails.message = content?.message;
127
-
128
- // Add carousel data if it exists
129
- if (content?.carouselData && Array.isArray(content?.carouselData) && content?.carouselData.length > 0) {
130
- expandableDetails.carouselData = content?.carouselData.map((card) => ({
131
- mediaType: card.mediaType || 'image',
132
- imageUrl: card.imageUrl || '',
133
- videoSrc: card.videoSrc || '',
134
- buttons: card.buttons ? card.buttons.map((button) => {
135
- if (button.linkType === DEEP_LINK && button.deepLinkKeys && button.deepLinkValue) {
136
- const deepLinkKeys = Array.isArray(button.deepLinkKeys) ? button.deepLinkKeys : [button.deepLinkKeys];
137
- const updatedDeepLinkValue = appendDeepLinkKeysToUrl(button.deepLinkValue, deepLinkKeys);
138
- return {
139
- actionOnClick: button.actionOnClick,
140
- linkType: button.linkType,
141
- deepLinkValue: updatedDeepLinkValue,
142
- externalLinkValue: button.externalLinkValue,
143
- // deepLinkKeys property removed - not expected by backend
144
- };
145
- }
146
- return {
147
- actionOnClick: button.actionOnClick,
148
- linkType: button.linkType,
149
- deepLinkValue: button.deepLinkValue,
150
- externalLinkValue: button.externalLinkValue,
151
- // deepLinkKeys property removed - not expected by backend
152
- };
153
- }) : [],
154
- }));
155
- }
156
-
157
- // Handle mediaList for carousel (used in tests and some scenarios)
158
- if (content?.mediaList && Array.isArray(content?.mediaList) && content?.mediaList.length > 0) {
159
- expandableDetails.media = content?.mediaList.map((m) => ({
160
- url: m.url,
161
- text: m.text || "",
162
- type: m.type,
163
- }));
164
- }
165
- } else if ([GIF].includes(style) && content?.mediaList) {
166
- expandableDetails.media = (content?.mediaList || []).map((m) => ({
167
- url: m.url,
168
- text: m.text || "",
169
- type: m.type,
170
- }));
171
- }
172
-
173
- // iOS always needs ctas array
174
- if (platform === IOS) {
175
- expandableDetails.ctas = content?.expandableDetails?.ctas || [];
176
- }
177
-
178
- // Process button CTAs to append deep link keys as query parameters
179
- if (content?.expandableDetails?.ctas && Array.isArray(content.expandableDetails.ctas)) {
180
- expandableDetails.ctas = content.expandableDetails.ctas.map((cta) => {
181
- if (cta.type === DEEP_LINK && cta.deepLinkKeys && cta.actionLink) {
182
- const deepLinkKeys = Array.isArray(cta.deepLinkKeys) ? cta.deepLinkKeys : [cta.deepLinkKeys];
183
- const updatedActionLink = appendDeepLinkKeysToUrl(cta.actionLink, deepLinkKeys);
184
- return {
185
- actionText: cta.actionText,
186
- type: cta.type,
187
- actionLink: updatedActionLink,
188
- // deepLinkKeys property removed - not expected by backend
189
- };
190
- }
191
- return {
192
- actionText: cta.actionText,
193
- type: cta.type,
194
- actionLink: cta.actionLink,
195
- // deepLinkKeys property removed - not expected by backend
196
- };
197
- });
198
- }
199
-
200
- // Add any additional expandableDetails fields
201
- if (content?.expandableDetails) {
202
- Object.entries(content?.expandableDetails).forEach(([k, v]) => {
203
- if (!(k in expandableDetails)) expandableDetails[k] = v;
204
- });
205
- }
206
- return expandableDetails;
207
- }
208
-
209
- // Helper to build custom fields array (legacy)
210
- // Helper function to append deep link keys as query parameters
211
- function appendDeepLinkKeysToUrl(baseUrl, deepLinkKeys) {
212
- if (!baseUrl || !deepLinkKeys || deepLinkKeys.length === 0) {
213
- return baseUrl;
214
- }
215
-
216
- try {
217
- const url = new URL(baseUrl);
218
- deepLinkKeys.forEach((key) => {
219
- if (key) {
220
- url.searchParams.set(key, key);
221
- }
222
- });
223
- return url.toString();
224
- } catch (error) {
225
- // If URL parsing fails, append as query string manually
226
- const separator = baseUrl.includes('?') ? '&' : '?';
227
- const queryParams = deepLinkKeys
228
- .filter((key) => key)
229
- .map((key) => `${key}=${key}`)
230
- .join('&');
231
- return queryParams ? `${baseUrl}${separator}${queryParams}` : baseUrl;
232
- }
233
- }
234
-
235
- function buildCustomFields(content) {
236
- let customFields = [];
237
-
238
- // If content.custom is already an array, use it as base
239
- if (Array.isArray(content?.custom)) {
240
- customFields = [...content.custom];
241
- }
242
- // If it's an object, map to array
243
- else if (content?.custom && typeof content?.custom === 'object') {
244
- customFields = Object.entries(content?.custom).map(([key, value]) => ({ key, value }));
245
- }
246
-
247
- // Add deepLinkKeys to custom fields if it exists
248
- if (content?.deepLinkKeysValue && content?.linkType === DEEP_LINK) {
249
- if (Array.isArray(content.deepLinkKeysValue)) {
250
- // Handle array of deep link keys
251
- content.deepLinkKeysValue.forEach((key) => {
252
- if (key) {
253
- customFields.push({
254
- key,
255
- value: key,
256
- });
257
- }
258
- });
259
- } else if (typeof content.deepLinkKeysValue === 'string' && content.deepLinkKeysValue) {
260
- // Handle single string value (backward compatibility)
261
- customFields.push({
262
- key: content.deepLinkKeysValue,
263
- value: content.deepLinkKeysValue,
264
- });
265
- }
266
- }
267
-
268
- // Add carousel button deep link keys to custom fields
269
- if (content?.carouselData && Array.isArray(content.carouselData)) {
270
- content.carouselData.forEach((card) => {
271
- if (card.buttons && Array.isArray(card.buttons)) {
272
- card.buttons.forEach((button) => {
273
- if (button.linkType === DEEP_LINK && button.deepLinkKeys) {
274
- if (Array.isArray(button.deepLinkKeys)) {
275
- // Handle array of deep link keys
276
- button.deepLinkKeys.forEach((key) => {
277
- if (key) {
278
- customFields.push({
279
- key,
280
- value: key,
281
- });
282
- }
283
- });
284
- } else if (typeof button.deepLinkKeys === 'string' && button.deepLinkKeys) {
285
- // Handle single string value (backward compatibility)
286
- customFields.push({
287
- key: button.deepLinkKeys,
288
- value: button.deepLinkKeys,
289
- });
290
- }
291
- }
292
- });
293
- }
294
- });
295
- }
296
-
297
- return customFields;
298
- }
299
-
300
- // Compose androidContent for legacy payload
301
- let androidLegacy;
302
- if (isAndroidSupported && isValidContent(androidContent)) {
303
- androidLegacy = {
304
- luid: "{{luid}}",
305
- cuid: "{{cuid}}",
306
- communicationId: "{{communicationId}}",
307
- title: androidContent.title,
308
- message: androidContent.message,
309
- expandableDetails: buildExpandableDetails(androidContent, safeImageSrc.androidImageSrc),
310
- custom: buildCustomFields(androidContent),
311
- type: androidContent.mediaType,
312
- deviceType: 'ANDROID',
313
- };
314
- if (androidContent?.actionOnClick && (androidContent?.deepLinkValue || androidContent?.externalLinkValue)) {
315
- let actionLink = androidContent?.linkType === EXTERNAL_LINK ? androidContent?.externalLinkValue : androidContent?.deepLinkValue;
316
-
317
- // Append deep link keys as query parameters for deep links
318
- if (androidContent?.linkType === DEEP_LINK && androidContent?.deepLinkKeysValue && androidContent.deepLinkKeysValue.length > 0) {
319
- actionLink = appendDeepLinkKeysToUrl(actionLink, androidContent.deepLinkKeysValue);
320
- }
321
-
322
- androidLegacy.cta = {
323
- type: androidContent.linkType === EXTERNAL_LINK ? EXTERNAL_URL : DEEP_LINK,
324
- actionLink,
325
- };
326
- }
327
- }
328
-
329
- // Compose iosContent for legacy payload
330
- let iosLegacy;
331
- if (isIosSupported && isValidContent(iosContent)) {
332
- iosLegacy = {
333
- luid: "{{luid}}",
334
- cuid: "{{cuid}}",
335
- communicationId: "{{communicationId}}",
336
- title: iosContent.title,
337
- message: iosContent.message,
338
- expandableDetails: buildExpandableDetails(iosContent, safeImageSrc.iosImageSrc, 'IOS'),
339
- custom: buildCustomFields(iosContent),
340
- type: iosContent.mediaType,
341
- deviceType: 'IOS',
342
- };
343
- if (iosContent?.actionOnClick && (iosContent?.deepLinkValue || iosContent?.externalLinkValue)) {
344
- let actionLink = iosContent?.linkType === EXTERNAL_LINK ? iosContent?.externalLinkValue : iosContent?.deepLinkValue;
345
-
346
- // Append deep link keys as query parameters for deep links
347
- if (iosContent?.linkType === DEEP_LINK && iosContent?.deepLinkKeysValue && iosContent.deepLinkKeysValue.length > 0) {
348
- actionLink = appendDeepLinkKeysToUrl(actionLink, iosContent.deepLinkKeysValue);
349
- }
350
-
351
- iosLegacy.cta = {
352
- type: iosContent?.linkType === EXTERNAL_LINK ? EXTERNAL_URL : DEEP_LINK,
353
- actionLink,
354
- };
355
- }
356
- }
357
-
358
- // Helper to check if content is non-empty and valid
359
- function isValidContent(content) {
360
- return content && typeof content === 'object' && content.title && content.message;
361
- }
362
-
363
- // Determine platform support from accountData
364
- // const isAndroidSupported = accountData?.configs?.android === '1';
365
- // const isIosSupported = accountData?.configs?.ios === '1';
366
-
367
- if (isAndroidSupported && (!androidContent || !androidContent?.title || !androidContent?.message)) {
368
- console.error('Android content missing or invalid:', androidContent);
369
- throw new Error("Android content must have title and message");
370
- }
371
- if (isIosSupported && (!iosContent || !iosContent?.title || !iosContent?.message)) {
372
- console.error('iOS content missing or invalid:', iosContent);
373
- throw new Error("iOS content must have title and message");
374
- }
375
-
376
- // Compose the full payload in legacy format
377
- const versionsBase = {};
378
- if (androidLegacy) versionsBase.ANDROID = androidLegacy;
379
- if (iosLegacy) versionsBase.IOS = iosLegacy;
380
-
381
- // Fallback template name to platform title if not provided
382
- let finalTemplateName = trimmedTemplateName;
383
- if (!finalTemplateName) {
384
- finalTemplateName = (androidContent && androidContent.title) || (iosContent && iosContent.title) || '';
385
- }
386
-
387
- const payload = {
388
- versions: {
389
- base: versionsBase,
390
- },
391
- type: MOBILE_PUSH_CHANNEL,
392
- name: finalTemplateName,
393
- definition: {
394
- accountId,
395
- licenseCode,
396
- mode: options?.mode || TEXT.toLowerCase(),
397
- sourceType,
398
- sameContent,
399
- },
400
- };
401
-
402
- return payload;
403
- }
404
-
405
- export { createPayload };