@capillarytech/creatives-library 8.0.126 → 8.0.127-alpha.1

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