@capillarytech/creatives-library 8.0.130 → 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 (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
@@ -0,0 +1,740 @@
1
+ import { useState, useCallback, useEffect } from 'react';
2
+ import { getCdnUrl } from '../../../utils/cdnTransformation';
3
+ import {
4
+ MOBILE_PUSH_CHANNEL,
5
+ MPUSH_IMG_SIZE,
6
+ MPUSH_VIDEO_SIZE,
7
+ ALLOWED_IMAGE_EXTENSIONS_REGEX,
8
+ ALLOWED_EXTENSIONS_VIDEO_REGEX,
9
+ ALLOWED_GIF_REGEX,
10
+ ANDROID,
11
+ MPUSH_GIF_SIZE,
12
+ IMAGE,
13
+ VIDEO,
14
+ GIF,
15
+ CAROUSEL,
16
+ } from '../constants';
17
+ import messages from '../messages';
18
+
19
+ // Helper function to get video properties
20
+ const getVideoProperties = (isAndroid = false) => ({
21
+ videoSrc: isAndroid ? 'androidVideoSrc' : 'iosVideoSrc',
22
+ videoPreview: isAndroid ? 'androidVideoPreview' : 'iosVideoPreview',
23
+ videoDuration: isAndroid ? 'androidVideoDuration' : 'iosVideoDuration',
24
+ });
25
+
26
+ const useUpload = (mobilePushActions, editData = {}, _uploadedAssetData0 = {}, _uploadedAssetData1 = {}, _uploadAssetSuccess = false, sameContent = false, setAndroidContent, setIosContent, activeTab, _androidContent, _iosContent, formatMessage) => {
27
+ // Asset list state - make platform-specific to prevent cross-platform bleeding
28
+ const [androidAssetList, updateAndroidAssetList] = useState([]);
29
+ const [iosAssetList, updateIosAssetList] = useState([]);
30
+ const [mpushVideoSrcAndPreview, setMpushVideoSrcAndPreview] = useState({
31
+ mpushVideoSrc: "",
32
+ mpushVideoPreviewImg: "",
33
+ duration: 0,
34
+ });
35
+
36
+ // Image state for non-carousel media types (IMAGE, VIDEO, GIF)
37
+ const [imageSrc, setImageSrc] = useState({
38
+ androidImageSrc: "",
39
+ iosImageSrc: "",
40
+ });
41
+
42
+ // Video state for platform-specific video/GIF uploads
43
+ const [videoState, setVideoState] = useState({
44
+ androidVideoSrc: "",
45
+ iosVideoSrc: "",
46
+ androidVideoPreview: "",
47
+ iosVideoPreview: "",
48
+ androidVideoDuration: 0,
49
+ iosVideoDuration: 0,
50
+ });
51
+
52
+ const [uploadErrors, setUploadErrors] = useState({});
53
+
54
+ // Add assetList state for test assertions
55
+ const [assetList, setAssetList] = useState([]);
56
+
57
+ // Patch updateAndroidAssetList and updateIosAssetList to also update assetList for test visibility
58
+ const updateAndroidAssetListPatched = (data) => {
59
+ setAssetList(data);
60
+ updateAndroidAssetList(data);
61
+ };
62
+ const updateIosAssetListPatched = (data) => {
63
+ setAssetList(data);
64
+ updateIosAssetList(data);
65
+ };
66
+
67
+ // File validation functions
68
+ const validateImageFile = useCallback((file) => {
69
+ const errors = [];
70
+
71
+ // File size validation
72
+ if (file?.size > MPUSH_IMG_SIZE) {
73
+ errors.push(formatMessage(messages.fileSizeError, { size: MPUSH_IMG_SIZE / 1000000 }));
74
+ }
75
+
76
+ // File type validation
77
+ if (!ALLOWED_IMAGE_EXTENSIONS_REGEX.test(file?.name)) {
78
+ errors.push(formatMessage(messages.imageFileTypeError));
79
+ }
80
+
81
+ return errors;
82
+ }, [formatMessage]);
83
+
84
+ const validateVideoFile = useCallback((file) => {
85
+ const errors = [];
86
+
87
+ // File size validation
88
+ if (file?.size > MPUSH_VIDEO_SIZE) {
89
+ errors.push(formatMessage(messages.fileSizeError, { size: MPUSH_VIDEO_SIZE / 1000000 }));
90
+ }
91
+
92
+ // File type validation for video
93
+ if (!ALLOWED_EXTENSIONS_VIDEO_REGEX.test(file?.name)) {
94
+ errors.push(formatMessage(messages.videoFileTypeError));
95
+ }
96
+
97
+ return errors;
98
+ }, [formatMessage]);
99
+
100
+ const validateGifFile = useCallback((file) => {
101
+ const errors = [];
102
+
103
+ // File size validation
104
+ if (file?.size > MPUSH_GIF_SIZE) {
105
+ errors.push(formatMessage(messages.fileSizeError, { size: MPUSH_GIF_SIZE / 1000000 }));
106
+ }
107
+
108
+ // File type validation for GIF
109
+ if (!ALLOWED_GIF_REGEX.test(file?.name)) {
110
+ errors.push(formatMessage(messages.gifFileTypeError));
111
+ }
112
+
113
+ return errors;
114
+ }, [formatMessage]);
115
+
116
+ // Upload asset function with validation
117
+ const uploadMpushAsset = useCallback((file, type, fileParams) => {
118
+ let validationErrors = [];
119
+
120
+ // Validate based on file type
121
+ if (type === IMAGE.toLowerCase()) {
122
+ validationErrors = validateImageFile(file);
123
+ } else if (type === VIDEO.toLowerCase()) {
124
+ validationErrors = validateVideoFile(file);
125
+ } else if (type === GIF.toLowerCase()) {
126
+ validationErrors = validateGifFile(file);
127
+ }
128
+
129
+ // If validation fails, set errors and don't upload
130
+ if (validationErrors?.length > 0) {
131
+ setUploadErrors({
132
+ [activeTab === ANDROID ? 0 : 1]: validationErrors,
133
+ });
134
+ return;
135
+ }
136
+
137
+ // Clear previous errors and proceed with upload
138
+ setUploadErrors({});
139
+
140
+ // Use "video" as assetType for both video and gif uploads
141
+ // since backend treats them the same way
142
+ const assetType = (type === GIF.toLowerCase()) ? VIDEO.toLowerCase() : type;
143
+
144
+ const mobilePushParams = {
145
+ source: MOBILE_PUSH_CHANNEL.toLowerCase(),
146
+ channel: MOBILE_PUSH_CHANNEL,
147
+ };
148
+ mobilePushActions.uploadAsset(file, assetType, fileParams, activeTab === ANDROID ? 0 : 1, mobilePushParams);
149
+ }, [mobilePushActions, validateImageFile, validateVideoFile, validateGifFile, activeTab]);
150
+
151
+ // Clear media data when switching media types
152
+ const clearImageDataByMediaType = useCallback((mediaType) => {
153
+ if (mediaType === IMAGE) {
154
+ if (sameContent) {
155
+ // When sync is enabled, clear both platforms
156
+ setImageSrc({
157
+ androidImageSrc: "",
158
+ iosImageSrc: "",
159
+ });
160
+ } else {
161
+ // When sync is disabled, only clear the current platform
162
+ const platform = activeTab === ANDROID ? 'androidImageSrc' : 'iosImageSrc';
163
+ setImageSrc((prevState) => ({
164
+ ...prevState,
165
+ [platform]: "",
166
+ }));
167
+ }
168
+ } else if (mediaType === VIDEO || mediaType === GIF) {
169
+ if (sameContent) {
170
+ // When sync is enabled, clear both platforms
171
+ setVideoState({
172
+ androidVideoSrc: "",
173
+ iosVideoSrc: "",
174
+ androidVideoPreview: "",
175
+ iosVideoPreview: "",
176
+ androidVideoDuration: 0,
177
+ iosVideoDuration: 0,
178
+ });
179
+
180
+ // Clear video data from both platform contents
181
+ const clearVideoData = {
182
+ videoSrc: "",
183
+ videoPreview: "",
184
+ videoDuration: 0,
185
+ };
186
+ setAndroidContent((prevContent) => ({
187
+ ...prevContent,
188
+ ...clearVideoData,
189
+ }));
190
+ setIosContent((prevContent) => ({
191
+ ...prevContent,
192
+ ...clearVideoData,
193
+ }));
194
+
195
+ // Clear Redux uploadedAssetData for both platforms
196
+ mobilePushActions.clearAsset(0);
197
+ mobilePushActions.clearAsset(1);
198
+ } else {
199
+ // When sync is disabled, only clear the current platform
200
+ const platformIndex = activeTab === ANDROID ? 0 : 1;
201
+ const isAndroid = activeTab === ANDROID;
202
+ const videoProps = getVideoProperties(isAndroid);
203
+
204
+ setVideoState((prevState) => ({
205
+ ...prevState,
206
+ [videoProps.videoSrc]: "",
207
+ [videoProps.videoPreview]: "",
208
+ [videoProps.videoDuration]: 0,
209
+ }));
210
+
211
+ // Clear video data from current platform content only
212
+ const clearVideoData = {
213
+ videoSrc: "",
214
+ videoPreview: "",
215
+ videoDuration: 0,
216
+ };
217
+ if (isAndroid) {
218
+ setAndroidContent((prevContent) => ({
219
+ ...prevContent,
220
+ ...clearVideoData,
221
+ }));
222
+ } else {
223
+ setIosContent((prevContent) => ({
224
+ ...prevContent,
225
+ ...clearVideoData,
226
+ }));
227
+ }
228
+
229
+ // Clear Redux uploadedAssetData for current platform only
230
+ mobilePushActions.clearAsset(platformIndex);
231
+ }
232
+ }
233
+ }, [setVideoState, mobilePushActions, sameContent, activeTab, setAndroidContent, setIosContent]);
234
+
235
+ useEffect(() => {
236
+ if (sameContent) {
237
+ // When sync is enabled, update both platforms with the same image
238
+ const sharedImageSrc = imageSrc?.androidImageSrc || imageSrc?.iosImageSrc;
239
+ setAndroidContent((prevAndroidContent) => ({
240
+ ...prevAndroidContent,
241
+ imageSrc: sharedImageSrc,
242
+ }));
243
+ setIosContent((prevIosContent) => ({
244
+ ...prevIosContent,
245
+ imageSrc: sharedImageSrc,
246
+ }));
247
+ } else {
248
+ // When sync is disabled, update only the respective platform
249
+ setAndroidContent((prevAndroidContent) => ({
250
+ ...prevAndroidContent,
251
+ imageSrc: imageSrc?.androidImageSrc,
252
+ }));
253
+ setIosContent((prevIosContent) => ({
254
+ ...prevIosContent,
255
+ imageSrc: imageSrc?.iosImageSrc,
256
+ }));
257
+ }
258
+ }, [imageSrc?.androidImageSrc, imageSrc?.iosImageSrc, sameContent]);
259
+
260
+ useEffect(() => {
261
+ if (sameContent) {
262
+ // When sync is enabled, update both platforms with the same video
263
+ const sharedVideoSrc = videoState?.androidVideoSrc || videoState?.iosVideoSrc;
264
+ const sharedVideoPreview = videoState?.androidVideoPreview || videoState?.iosVideoPreview;
265
+ const sharedVideoDuration = videoState?.androidVideoDuration || videoState?.iosVideoDuration;
266
+
267
+ setAndroidContent((prevAndroidContent) => ({
268
+ ...prevAndroidContent,
269
+ videoSrc: sharedVideoSrc,
270
+ videoPreview: sharedVideoPreview,
271
+ videoDuration: sharedVideoDuration,
272
+ }));
273
+ setIosContent((prevIosContent) => ({
274
+ ...prevIosContent,
275
+ videoSrc: sharedVideoSrc,
276
+ videoPreview: sharedVideoPreview,
277
+ videoDuration: sharedVideoDuration,
278
+ }));
279
+ } else {
280
+ // When sync is disabled, update only the respective platform
281
+ setAndroidContent((prevAndroidContent) => ({
282
+ ...prevAndroidContent,
283
+ videoSrc: videoState?.androidVideoSrc,
284
+ videoPreview: videoState?.androidVideoPreview,
285
+ videoDuration: videoState?.androidVideoDuration,
286
+ }));
287
+ setIosContent((prevIosContent) => ({
288
+ ...prevIosContent,
289
+ videoSrc: videoState?.iosVideoSrc,
290
+ videoPreview: videoState?.iosVideoPreview,
291
+ videoDuration: videoState?.iosVideoDuration,
292
+ }));
293
+ }
294
+ }, [videoState?.androidVideoSrc, videoState?.iosVideoSrc, videoState?.androidVideoPreview, videoState?.iosVideoPreview, videoState?.androidVideoDuration, videoState?.iosVideoDuration, sameContent]);
295
+
296
+ // Sync imageSrc state for both platforms when sameContent is toggled ON
297
+ useEffect(() => {
298
+ if (sameContent) {
299
+ // Determine which image to use (prefer activeTab, fallback to either)
300
+ let currentImageSrc = '';
301
+ if (activeTab === ANDROID) {
302
+ currentImageSrc = imageSrc?.androidImageSrc || imageSrc?.iosImageSrc;
303
+ } else {
304
+ currentImageSrc = imageSrc?.iosImageSrc || imageSrc?.androidImageSrc;
305
+ }
306
+ if (currentImageSrc && (imageSrc?.androidImageSrc !== currentImageSrc || imageSrc?.iosImageSrc !== currentImageSrc)) {
307
+ setImageSrc({
308
+ androidImageSrc: currentImageSrc,
309
+ iosImageSrc: currentImageSrc,
310
+ });
311
+ }
312
+ }
313
+ // Only run when sameContent or activeTab changes
314
+ // eslint-disable-next-line react-hooks/exhaustive-deps
315
+ }, [sameContent, activeTab]);
316
+
317
+ // Sync videoState for both platforms when sameContent is toggled ON
318
+ useEffect(() => {
319
+ if (sameContent) {
320
+ // Determine which video to use (prefer activeTab, fallback to either)
321
+ let currentVideoSrc = '';
322
+ let currentVideoPreview = '';
323
+ let currentVideoDuration = 0;
324
+ if (activeTab === ANDROID) {
325
+ currentVideoSrc = videoState?.androidVideoSrc || videoState?.iosVideoSrc;
326
+ currentVideoPreview = videoState?.androidVideoPreview || videoState?.iosVideoPreview;
327
+ currentVideoDuration = videoState?.androidVideoDuration || videoState?.iosVideoDuration;
328
+ } else {
329
+ currentVideoSrc = videoState?.iosVideoSrc || videoState?.androidVideoSrc;
330
+ currentVideoPreview = videoState?.iosVideoPreview || videoState?.androidVideoPreview;
331
+ currentVideoDuration = videoState?.iosVideoDuration || videoState?.androidVideoDuration;
332
+ }
333
+ if (
334
+ currentVideoSrc && (
335
+ videoState?.androidVideoSrc !== currentVideoSrc
336
+ || videoState?.iosVideoSrc !== currentVideoSrc
337
+ || videoState?.androidVideoPreview !== currentVideoPreview
338
+ || videoState?.iosVideoPreview !== currentVideoPreview
339
+ || videoState?.androidVideoDuration !== currentVideoDuration
340
+ || videoState?.iosVideoDuration !== currentVideoDuration
341
+ )
342
+ ) {
343
+ setVideoState({
344
+ androidVideoSrc: currentVideoSrc,
345
+ iosVideoSrc: currentVideoSrc,
346
+ androidVideoPreview: currentVideoPreview,
347
+ iosVideoPreview: currentVideoPreview,
348
+ androidVideoDuration: currentVideoDuration,
349
+ iosVideoDuration: currentVideoDuration,
350
+ });
351
+ }
352
+ }
353
+ // Only run when sameContent or activeTab changes
354
+ // eslint-disable-next-line react-hooks/exhaustive-deps
355
+ }, [sameContent, activeTab]);
356
+
357
+ // Image upload handlers
358
+ const updateOnMpushImageReUpload = useCallback(() => {
359
+ if (sameContent) {
360
+ // When sync is enabled, set image for both platforms
361
+ setImageSrc({
362
+ androidImageSrc: "",
363
+ iosImageSrc: "",
364
+ });
365
+ } else {
366
+ const platform = activeTab === ANDROID ? 'androidImageSrc' : 'iosImageSrc';
367
+ setImageSrc((prevState) => ({
368
+ ...prevState,
369
+ [platform]: "",
370
+ }));
371
+ }
372
+ }, [sameContent, activeTab]);
373
+
374
+ const setUpdateMpushImageSrc = useCallback((filePath, index, mediaType = IMAGE) => {
375
+ // Handle both non-carousel and carousel media types
376
+ if (mediaType === CAROUSEL) {
377
+ // For carousel images, we need to set them in the Redux state for the uploader to recognize
378
+ // This is needed when initializing carousel images from template data
379
+ if (sameContent) {
380
+ // When sync is enabled, set image for both platforms
381
+ setImageSrc({
382
+ androidImageSrc: filePath,
383
+ iosImageSrc: filePath,
384
+ });
385
+ } else {
386
+ // When sync is disabled, set only for the specific platform using the passed index
387
+ const platform = index === 0 ? 'androidImageSrc' : 'iosImageSrc';
388
+ setImageSrc((prevState) => ({
389
+ ...prevState,
390
+ [platform]: filePath,
391
+ }));
392
+ }
393
+ // Don't clear assets for carousel initialization
394
+ return;
395
+ }
396
+
397
+ if (sameContent) {
398
+ // When sync is enabled, set image for both platforms
399
+ setImageSrc({
400
+ androidImageSrc: filePath,
401
+ iosImageSrc: filePath,
402
+ });
403
+ } else {
404
+ // When sync is disabled, set only for the specific platform using the passed index
405
+ const platform = index === 0 ? 'androidImageSrc' : 'iosImageSrc';
406
+ setImageSrc((prevState) => ({
407
+ ...prevState,
408
+ [platform]: filePath,
409
+ }));
410
+ }
411
+
412
+ mobilePushActions.clearAsset(index);
413
+ }, [mobilePushActions, sameContent, setImageSrc]);
414
+
415
+ // Video re-upload handler - clear video data based on platform and sync settings
416
+ const updateOnMpushVideoReUpload = useCallback(() => {
417
+ // Clear shared video preview state (kept for backward compatibility)
418
+ setMpushVideoSrcAndPreview({
419
+ mpushVideoSrc: "",
420
+ mpushVideoPreviewImg: "",
421
+ duration: 0,
422
+ });
423
+
424
+ // Clear asset list to remove uploaded video data
425
+ updateAndroidAssetListPatched([]);
426
+ updateIosAssetListPatched([]);
427
+
428
+ // Clear platform-specific video data
429
+ if (sameContent) {
430
+ // When sync is enabled, clear both platforms
431
+ setVideoState({
432
+ androidVideoSrc: "",
433
+ iosVideoSrc: "",
434
+ androidVideoPreview: "",
435
+ iosVideoPreview: "",
436
+ androidVideoDuration: 0,
437
+ iosVideoDuration: 0,
438
+ });
439
+
440
+ // Clear Redux uploadedAssetData for both platforms
441
+ mobilePushActions.clearAsset(0);
442
+ mobilePushActions.clearAsset(1);
443
+ } else {
444
+ // When sync is disabled, only clear the current platform
445
+ const platformIndex = activeTab === ANDROID ? 0 : 1;
446
+ const isAndroid = activeTab === ANDROID;
447
+
448
+ setVideoState((prevState) => ({
449
+ ...prevState,
450
+ [isAndroid ? 'androidVideoSrc' : 'iosVideoSrc']: "",
451
+ [isAndroid ? 'androidVideoPreview' : 'iosVideoPreview']: "",
452
+ [isAndroid ? 'androidVideoDuration' : 'iosVideoDuration']: 0,
453
+ }));
454
+
455
+ // Clear Redux uploadedAssetData for current platform only
456
+ mobilePushActions.clearAsset(platformIndex);
457
+ }
458
+ }, [setVideoState, mobilePushActions, sameContent, activeTab]);
459
+
460
+ // Video upload handlers - platform-specific video/GIF uploads
461
+ const setUpdateMpushVideoSrc = useCallback((index, data, isInitialization = false) => {
462
+ const {
463
+ videoSrc = "", previewUrl = "", videoDuration = 0, videoName = "", fileHandle = "",
464
+ } = data;
465
+
466
+ // Update the shared video preview state (kept for backward compatibility)
467
+ setMpushVideoSrcAndPreview({
468
+ mpushVideoSrc: videoSrc,
469
+ mpushVideoPreviewImg: previewUrl,
470
+ duration: videoDuration,
471
+ });
472
+
473
+ // Update platform-specific video state
474
+ if (sameContent) {
475
+ // When sync is enabled, set video for both platforms (for GIF or VIDEO)
476
+ setVideoState({
477
+ androidVideoSrc: videoSrc,
478
+ iosVideoSrc: videoSrc,
479
+ androidVideoPreview: previewUrl,
480
+ iosVideoPreview: previewUrl,
481
+ androidVideoDuration: videoDuration,
482
+ iosVideoDuration: videoDuration,
483
+ });
484
+ } else {
485
+ // When sync is disabled, set only for the specific platform using the passed index
486
+ const isAndroid = index === 0;
487
+ const videoProps = getVideoProperties(isAndroid);
488
+
489
+ setVideoState((prevState) => ({
490
+ ...prevState,
491
+ [videoProps.videoSrc]: videoSrc,
492
+ [videoProps.videoPreview]: previewUrl,
493
+ [videoProps.videoDuration]: videoDuration,
494
+ }));
495
+ }
496
+
497
+ // Update asset list with complete data
498
+ const assetData = {
499
+ videoSrc,
500
+ previewUrl,
501
+ videoDuration,
502
+ videoName,
503
+ fileHandle,
504
+ ...data, // Include any additional data passed
505
+ };
506
+ if (sameContent) {
507
+ updateAndroidAssetListPatched(assetData);
508
+ updateIosAssetListPatched(assetData);
509
+ } else if (index === 0) {
510
+ updateAndroidAssetListPatched(assetData);
511
+ } else {
512
+ updateIosAssetListPatched(assetData);
513
+ }
514
+
515
+ // Only clear Redux asset data during actual uploads, not during initialization
516
+ if (!isInitialization) {
517
+ mobilePushActions.clearAsset(index);
518
+ }
519
+ }, [mobilePushActions, sameContent]);
520
+
521
+ // Get CDN URL for images/videos
522
+ const getCdnImageUrl = useCallback((imageUrl) => {
523
+ if (!imageUrl) return "";
524
+ return getCdnUrl({
525
+ url: imageUrl,
526
+ channelName: MOBILE_PUSH_CHANNEL,
527
+ });
528
+ }, []);
529
+
530
+ const getCdnVideoUrl = useCallback((videoUrl) => {
531
+ if (!videoUrl) return "";
532
+ return getCdnUrl({
533
+ url: videoUrl,
534
+ channelName: MOBILE_PUSH_CHANNEL,
535
+ });
536
+ }, []);
537
+
538
+ // Generate payload for API calls
539
+ const getMediaPayload = useCallback((mediaType, tabParam) => {
540
+ const currentImageSrc = tabParam === ANDROID ? imageSrc?.androidImageSrc : imageSrc?.iosImageSrc;
541
+ const videoProps = getVideoProperties(tabParam === ANDROID);
542
+ const currentVideoSrc = videoState?.[videoProps.videoSrc];
543
+ const currentVideoPreview = videoState?.[videoProps.videoPreview];
544
+ const currentVideoDuration = videoState?.[videoProps.videoDuration];
545
+
546
+ switch (mediaType) {
547
+ case IMAGE:
548
+ return currentImageSrc ? {
549
+ imageUrl: getCdnImageUrl(currentImageSrc),
550
+ } : {};
551
+
552
+ case VIDEO:
553
+ return currentVideoSrc ? {
554
+ videoUrl: getCdnVideoUrl(currentVideoSrc),
555
+ videoPreviewImg: currentVideoPreview
556
+ ? getCdnImageUrl(currentVideoPreview) : "",
557
+ duration: currentVideoDuration,
558
+ } : {};
559
+
560
+ case GIF:
561
+ return currentVideoSrc ? {
562
+ gifUrl: getCdnVideoUrl(currentVideoSrc),
563
+ gifPreviewImg: currentVideoPreview
564
+ ? getCdnImageUrl(currentVideoPreview) : "",
565
+ } : {};
566
+
567
+ default:
568
+ return {};
569
+ }
570
+ }, [imageSrc, videoState, getCdnImageUrl, getCdnVideoUrl]);
571
+
572
+ // Generate preview data for template preview component
573
+ const getPreviewData = useCallback((mediaType, tabParam) => {
574
+ const currentImageSrc = tabParam === ANDROID ? imageSrc?.androidImageSrc : imageSrc?.iosImageSrc;
575
+ const videoProps = getVideoProperties(tabParam === ANDROID);
576
+ const currentVideoSrc = videoState?.[videoProps.videoSrc];
577
+ const currentVideoPreview = videoState?.[videoProps.videoPreview];
578
+ const currentVideoDuration = videoState?.[videoProps.videoDuration];
579
+
580
+ switch (mediaType) {
581
+ case IMAGE:
582
+ return {
583
+ imageSrc: currentImageSrc,
584
+ };
585
+
586
+ case VIDEO:
587
+ return {
588
+ videoSrc: currentVideoSrc,
589
+ videoPreviewImg: currentVideoPreview,
590
+ duration: currentVideoDuration,
591
+ };
592
+
593
+ case GIF:
594
+ return {
595
+ gifSrc: currentVideoSrc,
596
+ gifPreviewImg: currentVideoPreview,
597
+ };
598
+
599
+ default:
600
+ return {};
601
+ }
602
+ }, [imageSrc, videoState]);
603
+
604
+ // Check if media is uploaded for validation
605
+ const isMediaUploaded = useCallback((mediaType, tabParam) => {
606
+ switch (mediaType) {
607
+ case IMAGE: {
608
+ const currentImageSrc = tabParam === ANDROID ? imageSrc?.androidImageSrc : imageSrc?.iosImageSrc;
609
+ return !!currentImageSrc;
610
+ }
611
+
612
+ case VIDEO:
613
+ case GIF: {
614
+ const currentVideoSrc = tabParam === ANDROID ? videoState?.androidVideoSrc : videoState?.iosVideoSrc;
615
+ return !!currentVideoSrc;
616
+ }
617
+
618
+ default:
619
+ return true; // For TEXT or NONE media types
620
+ }
621
+ }, [imageSrc, videoState]);
622
+
623
+ // Reset function to clear all upload states
624
+ const resetUploadStates = useCallback(() => {
625
+ setImageSrc({
626
+ androidImageSrc: "",
627
+ iosImageSrc: "",
628
+ });
629
+ setMpushVideoSrcAndPreview({
630
+ mpushVideoSrc: "",
631
+ mpushVideoPreviewImg: "",
632
+ duration: 0,
633
+ });
634
+ setVideoState({
635
+ androidVideoSrc: "",
636
+ iosVideoSrc: "",
637
+ androidVideoPreview: "",
638
+ iosVideoPreview: "",
639
+ androidVideoDuration: 0,
640
+ iosVideoDuration: 0,
641
+ });
642
+ updateAndroidAssetListPatched([]);
643
+ updateIosAssetListPatched([]);
644
+ }, []);
645
+
646
+ // Initialize from edit data
647
+ useEffect(() => {
648
+ if (editData?.templateDetails) {
649
+ const { versions = {} } = editData.templateDetails;
650
+ const editContent = versions?.base?.content || {};
651
+
652
+ if (editContent.ANDROID) {
653
+ const { expandableDetails = {} } = editContent.ANDROID;
654
+ if (expandableDetails.image) {
655
+ setImageSrc((prev) => ({
656
+ ...prev,
657
+ androidImageSrc: expandableDetails.image,
658
+ }));
659
+ }
660
+ if (expandableDetails.video) {
661
+ // Update shared state for backward compatibility
662
+ setMpushVideoSrcAndPreview((prev) => ({
663
+ ...prev,
664
+ mpushVideoSrc: expandableDetails.video,
665
+ mpushVideoPreviewImg: expandableDetails.videoPreview || "",
666
+ }));
667
+
668
+ // Update platform-specific video state
669
+ setVideoState((prev) => ({
670
+ ...prev,
671
+ androidVideoSrc: expandableDetails.video,
672
+ androidVideoPreview: expandableDetails.videoPreview || "",
673
+ androidVideoDuration: expandableDetails.videoDuration || 0,
674
+ }));
675
+ }
676
+ }
677
+
678
+ if (editContent.IOS) {
679
+ const { expandableDetails = {} } = editContent.IOS;
680
+ if (expandableDetails.image) {
681
+ setImageSrc((prev) => ({
682
+ ...prev,
683
+ iosImageSrc: expandableDetails.image,
684
+ }));
685
+ }
686
+ if (expandableDetails.video) {
687
+ // Update platform-specific video state for iOS
688
+ setVideoState((prev) => ({
689
+ ...prev,
690
+ iosVideoSrc: expandableDetails.video,
691
+ iosVideoPreview: expandableDetails.videoPreview || "",
692
+ iosVideoDuration: expandableDetails.videoDuration || 0,
693
+ }));
694
+ }
695
+ }
696
+ }
697
+ }, [editData]);
698
+
699
+ return {
700
+ // States
701
+ androidAssetList,
702
+ iosAssetList,
703
+ mpushVideoSrcAndPreview,
704
+ imageSrc,
705
+ videoState,
706
+ uploadErrors,
707
+ assetList, // Expose for test assertions
708
+
709
+ // Functions
710
+ uploadMpushAsset,
711
+ updateOnMpushImageReUpload,
712
+ updateOnMpushVideoReUpload,
713
+ setUpdateMpushImageSrc,
714
+ setUpdateMpushVideoSrc,
715
+ resetUploadStates,
716
+ clearImageDataByMediaType,
717
+
718
+ // Validation functions
719
+ validateImageFile,
720
+ validateVideoFile,
721
+ validateGifFile,
722
+
723
+ // Utility functions
724
+ getMediaPayload,
725
+ getPreviewData,
726
+ isMediaUploaded,
727
+ getCdnImageUrl,
728
+ getCdnVideoUrl,
729
+
730
+ // State setters (for direct manipulation if needed)
731
+ updateAndroidAssetList,
732
+ updateIosAssetList,
733
+ setMpushVideoSrcAndPreview,
734
+ setImageSrc,
735
+ setVideoState,
736
+ setUploadErrors,
737
+ };
738
+ };
739
+
740
+ export default useUpload;