@capillarytech/creatives-library 8.0.130 → 8.0.132

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/containers/App/constants.js +1 -0
  2. package/containers/Login/index.js +1 -2
  3. package/package.json +1 -1
  4. package/services/api.js +5 -0
  5. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
  6. package/tests/integration/TemplateCreation/api-response.js +5 -0
  7. package/tests/integration/TemplateCreation/msw-handler.js +42 -63
  8. package/utils/common.js +7 -0
  9. package/utils/commonUtils.js +2 -6
  10. package/utils/createMobilePushPayload.js +322 -0
  11. package/utils/tests/createMobilePushPayload.test.js +1054 -0
  12. package/v2Components/CapDeviceContent/index.js +1 -1
  13. package/v2Components/CapImageUpload/index.js +57 -44
  14. package/v2Components/CapInAppCTA/index.js +1 -0
  15. package/v2Components/CapMpushCTA/constants.js +25 -0
  16. package/v2Components/CapMpushCTA/index.js +403 -0
  17. package/v2Components/CapMpushCTA/index.scss +95 -0
  18. package/v2Components/CapMpushCTA/messages.js +101 -0
  19. package/v2Components/CapTagList/index.js +178 -121
  20. package/v2Components/CapVideoUpload/constants.js +3 -0
  21. package/v2Components/CapVideoUpload/index.js +182 -115
  22. package/v2Components/CapVideoUpload/messages.js +16 -0
  23. package/v2Components/Carousel/index.js +15 -13
  24. package/v2Components/ErrorInfoNote/style.scss +1 -0
  25. package/v2Components/MobilePushPreviewV2/index.js +57 -12
  26. package/v2Components/TemplatePreview/_templatePreview.scss +218 -74
  27. package/v2Components/TemplatePreview/assets/images/Android_With_date_and_time.svg +29 -0
  28. package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
  29. package/v2Components/TemplatePreview/assets/images/iOS_With_date_and_time.svg +26 -0
  30. package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
  31. package/v2Components/TemplatePreview/index.js +234 -107
  32. package/v2Components/TemplatePreview/messages.js +4 -0
  33. package/v2Components/TemplatePreview/tests/__snapshots__/index.test.js.snap +10 -10
  34. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -62
  35. package/v2Containers/CreativesContainer/index.js +193 -136
  36. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -22
  37. package/v2Containers/InApp/constants.js +1 -0
  38. package/v2Containers/InApp/index.js +13 -13
  39. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +4748 -4658
  40. package/v2Containers/Login/index.js +1 -2
  41. package/v2Containers/MobilePush/Create/index.js +1 -0
  42. package/v2Containers/MobilePush/commonMethods.js +7 -14
  43. package/v2Containers/MobilePush/tests/commonMethods.test.js +401 -0
  44. package/v2Containers/MobilePushNew/actions.js +116 -0
  45. package/v2Containers/MobilePushNew/components/CtaButtons.js +183 -0
  46. package/v2Containers/MobilePushNew/components/MediaUploaders.js +835 -0
  47. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +346 -0
  48. package/v2Containers/MobilePushNew/components/index.js +5 -0
  49. package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +565 -0
  50. package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +3180 -0
  51. package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +654 -0
  52. package/v2Containers/MobilePushNew/constants.js +116 -0
  53. package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1462 -0
  54. package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1459 -0
  55. package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +366 -0
  56. package/v2Containers/MobilePushNew/hooks/useUpload.js +740 -0
  57. package/v2Containers/MobilePushNew/index.js +2158 -0
  58. package/v2Containers/MobilePushNew/index.scss +308 -0
  59. package/v2Containers/MobilePushNew/messages.js +272 -0
  60. package/v2Containers/MobilePushNew/reducer.js +160 -0
  61. package/v2Containers/MobilePushNew/sagas.js +193 -0
  62. package/v2Containers/MobilePushNew/selectors.js +55 -0
  63. package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
  64. package/v2Containers/MobilePushNew/tests/sagas.test.js +864 -0
  65. package/v2Containers/MobilePushNew/tests/selectors.test.js +665 -0
  66. package/v2Containers/MobilePushNew/tests/utils.test.js +421 -0
  67. package/v2Containers/MobilePushNew/utils.js +84 -0
  68. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1176 -976
  69. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +684 -424
  70. package/v2Containers/TagList/index.js +56 -10
  71. package/v2Containers/Templates/_templates.scss +100 -1
  72. package/v2Containers/Templates/index.js +170 -31
  73. package/v2Containers/Templates/messages.js +8 -0
  74. package/v2Containers/Templates/sagas.js +1 -0
  75. package/v2Containers/Whatsapp/constants.js +1 -0
  76. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +3992 -3677
  77. package/assets/loading_img.gif +0 -0
@@ -0,0 +1,835 @@
1
+ import React, {
2
+ useCallback,
3
+ useState,
4
+ useEffect,
5
+ useRef,
6
+ } from "react";
7
+ import PropTypes from "prop-types";
8
+ import { FormattedMessage } from "react-intl";
9
+ import CapButton from "@capillarytech/cap-ui-library/CapButton";
10
+ import CapCard from "@capillarytech/cap-ui-library/CapCard";
11
+ import CapColumn from "@capillarytech/cap-ui-library/CapColumn";
12
+ import CapDivider from "@capillarytech/cap-ui-library/CapDivider";
13
+ import CapHeading from "@capillarytech/cap-ui-library/CapHeading";
14
+ import CapIcon from "@capillarytech/cap-ui-library/CapIcon";
15
+ import CapRadioGroup from "@capillarytech/cap-ui-library/CapRadioGroup";
16
+ import CapRow from "@capillarytech/cap-ui-library/CapRow";
17
+ import CapTab from "@capillarytech/cap-ui-library/CapTab";
18
+ import CapCheckbox from "@capillarytech/cap-ui-library/CapCheckbox";
19
+ import CapLabel from "@capillarytech/cap-ui-library/CapLabel";
20
+ import CapSelect from "@capillarytech/cap-ui-library/CapSelect";
21
+ import CapInput from "@capillarytech/cap-ui-library/CapInput";
22
+ import CapError from "@capillarytech/cap-ui-library/CapError";
23
+ import CapImageUpload from "../../../v2Components/CapImageUpload";
24
+ import CapVideoUpload from "../../../v2Components/CapVideoUpload";
25
+ import {
26
+ ALLOWED_IMAGE_EXTENSIONS_REGEX,
27
+ MPUSH_IMG_SIZE,
28
+ MPUSH_IMG_MAX_WIDTH,
29
+ MPUSH_IMG_MAX_HEIGHT,
30
+ ALLOWED_EXTENSIONS_VIDEO_REGEX,
31
+ MPUSH_VIDEO_SIZE,
32
+ ALLOWED_GIF_REGEX,
33
+ MPUSH_GIF_SIZE,
34
+ MOBILE_PUSH_CHANNEL,
35
+ ANDROID,
36
+ GIF,
37
+ IMAGE,
38
+ VIDEO,
39
+ LINK_TYPE_OPTIONS,
40
+ DEEP_LINK,
41
+ EXTERNAL_LINK,
42
+ CAROUSEL,
43
+ } from "../constants";
44
+ import messages from "../messages";
45
+ import { validateExternalLink, validateDeepLink } from "../utils";
46
+
47
+ // Initial carousel data structure
48
+ const CAROUSEL_INITIAL_DATA = {
49
+ mediaType: IMAGE.toLowerCase(),
50
+ imageUrl: '',
51
+ videoSrc: '',
52
+ buttons: [{
53
+ actionOnClick: false,
54
+ linkType: DEEP_LINK,
55
+ deepLinkValue: '',
56
+ deepLinkKeys: [],
57
+ externalLinkValue: '',
58
+ }],
59
+ };
60
+
61
+ // Maximum number of carousel cards allowed
62
+ const MAX_CAROUSEL_ALLOWED = 10;
63
+
64
+ const MediaUploaders = ({
65
+ mediaType,
66
+ activeTab,
67
+ imageSrc,
68
+ uploadMpushAsset,
69
+ isFullMode,
70
+ setUpdateMpushImageSrc,
71
+ updateOnMpushImageReUpload,
72
+ imageData,
73
+ videoAssetList,
74
+ gifAssetList,
75
+ setUpdateMpushVideoSrc,
76
+ videoDataForVideo,
77
+ videoDataForGif,
78
+ videoSrc, // Add videoSrc prop for content state fallback
79
+ formatMessage,
80
+ linkProps,
81
+ clearImageDataByMediaType,
82
+ carouselData = [], // Carousel data from content state
83
+ onCarouselDataChange, // Callback to update carousel data in parent
84
+ mobilePushActions, // Mobile push actions for clearing assets
85
+ carouselActiveTabIndex,
86
+ setCarouselActiveTabIndex,
87
+ carouselLinkErrors = {}, // Carousel link errors from parent
88
+ updateCarouselLinkError, // Function to update carousel link errors in parent
89
+ }) => {
90
+ // Carousel state management - separate for Android and iOS
91
+ const [carouselMediaType, setCarouselMediaType] = useState(IMAGE.toLowerCase());
92
+
93
+ // Track re-upload mode to prevent automatic restoration during re-upload
94
+ const [isReUploading, setIsReUploading] = useState(false);
95
+
96
+ // Track previous media type to handle transitions
97
+ const previousMediaTypeRef = useRef(mediaType);
98
+
99
+ // Extract link props
100
+ const { deepLink } = linkProps || {};
101
+
102
+ // Get current carousel data based on active tab
103
+ const getCurrentCarouselData = useCallback(() => carouselData || [], [carouselData]);
104
+
105
+ // Set current carousel data based on active tab
106
+ const setCurrentCarouselData = useCallback((newData) => {
107
+ if (onCarouselDataChange) {
108
+ onCarouselDataChange(activeTab, newData);
109
+ }
110
+ }, [activeTab, onCarouselDataChange]);
111
+
112
+ // Clear previous media data when switching media types
113
+ useEffect(() => {
114
+ // Clear carousel data when switching to non-carousel media types
115
+ if (mediaType !== CAROUSEL) {
116
+ setCurrentCarouselData([]);
117
+ setCarouselActiveTabIndex(0);
118
+ }
119
+
120
+ // Clear image data when switching away from IMAGE media type
121
+ if (mediaType !== IMAGE) {
122
+ clearImageDataByMediaType(IMAGE);
123
+ }
124
+
125
+ // Clear image data when switching FROM CAROUSEL TO IMAGE
126
+ // This prevents carousel images from appearing in IMAGE media type
127
+ if (previousMediaTypeRef.current === CAROUSEL && mediaType === IMAGE) {
128
+ clearImageDataByMediaType(IMAGE);
129
+ }
130
+
131
+ // Reset re-upload mode when switching media types
132
+ setIsReUploading(false);
133
+
134
+ // Update the previous media type ref
135
+ previousMediaTypeRef.current = mediaType;
136
+ }, [mediaType, clearImageDataByMediaType, setCurrentCarouselData]);
137
+
138
+ // Handle platform tab changes for carousel data
139
+ useEffect(() => {
140
+ // Reset active tab index when switching platforms
141
+ setCarouselActiveTabIndex(0);
142
+ }, [activeTab]);
143
+
144
+ // Initialize carousel data if empty
145
+ useEffect(() => {
146
+ if (mediaType === CAROUSEL && (!carouselData || carouselData?.length === 0)) {
147
+ setCurrentCarouselData([CAROUSEL_INITIAL_DATA]);
148
+ }
149
+ }, [mediaType, carouselData, activeTab, setCurrentCarouselData]);
150
+
151
+ // Listen for upload success and update carousel data
152
+ useEffect(() => {
153
+ // Skip automatic updates during re-upload mode
154
+ if (isReUploading) {
155
+ return;
156
+ }
157
+
158
+ if (mediaType === CAROUSEL && imageData) {
159
+ const platformIndex = activeTab === ANDROID ? 0 : 1;
160
+ const uploadedAssetData = imageData[`uploadedAssetData${platformIndex}`];
161
+
162
+ // Only update if there's new upload data and it's different from current carousel data
163
+ if (uploadedAssetData && uploadedAssetData.metaInfo && uploadedAssetData.metaInfo.secure_file_path) {
164
+ const currentData = getCurrentCarouselData();
165
+ if (currentData?.length > carouselActiveTabIndex) {
166
+ const currentCard = currentData[carouselActiveTabIndex];
167
+ const newImageUrl = uploadedAssetData.metaInfo.secure_file_path;
168
+
169
+ // Only update if the image URL is different from the current one and not empty
170
+ if (currentCard?.imageUrl !== newImageUrl && newImageUrl !== '') {
171
+ const updatedData = [...currentData];
172
+ updatedData[carouselActiveTabIndex] = {
173
+ ...updatedData[carouselActiveTabIndex],
174
+ imageUrl: newImageUrl,
175
+ };
176
+ setCurrentCarouselData(updatedData);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }, [imageData, mediaType, activeTab, carouselActiveTabIndex, getCurrentCarouselData, setCurrentCarouselData, isReUploading]);
182
+
183
+ const renderImageComponent = useCallback(() => {
184
+ let imageSource = '';
185
+
186
+ // Use platform-specific image source
187
+ const platformKey = activeTab === ANDROID ? "androidImageSrc" : "iosImageSrc";
188
+ imageSource = imageSrc[platformKey] || '';
189
+
190
+ return (
191
+ <CapImageUpload
192
+ style={{ paddingTop: "20px" }}
193
+ allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
194
+ imgSize={MPUSH_IMG_SIZE}
195
+ imgWidth={MPUSH_IMG_MAX_WIDTH}
196
+ imgHeight={MPUSH_IMG_MAX_HEIGHT}
197
+ uploadAsset={uploadMpushAsset}
198
+ isFullMode={isFullMode}
199
+ imageSrc={imageSource}
200
+ updateImageSrc={(src) => setUpdateMpushImageSrc(src, activeTab === ANDROID ? 0 : 1, IMAGE)}
201
+ updateOnReUpload={updateOnMpushImageReUpload}
202
+ index={activeTab === ANDROID ? 0 : 1}
203
+ className="cap-custom-image-upload"
204
+ key="mpush-uploaded-image"
205
+ imageData={imageData}
206
+ channel={MOBILE_PUSH_CHANNEL}
207
+ showReUploadButton
208
+ errorMessage={formatMessage(messages.imageErrorMessage)}
209
+ />
210
+ );
211
+ }, [
212
+ activeTab,
213
+ imageSrc,
214
+ uploadMpushAsset,
215
+ isFullMode,
216
+ setUpdateMpushImageSrc,
217
+ updateOnMpushImageReUpload,
218
+ imageData,
219
+ formatMessage,
220
+ ]);
221
+
222
+ const renderVideoComponent = useCallback(() => {
223
+ // Apply same platform isolation logic as images
224
+ let platformVideoData = {};
225
+ const currentIndex = activeTab === ANDROID ? 0 : 1;
226
+
227
+ // Use platform-specific video data
228
+ if (videoDataForVideo && videoDataForVideo[`uploadedAssetData${currentIndex}`] && Object.keys(videoDataForVideo[`uploadedAssetData${currentIndex}`]).length > 0) {
229
+ // ONLY include the current platform's uploadedAssetData to prevent cross-platform bleeding
230
+ platformVideoData = {
231
+ [`uploadedAssetData${currentIndex}`]: videoDataForVideo[`uploadedAssetData${currentIndex}`],
232
+ };
233
+ } else {
234
+ // No platform-specific video data in Redux - check if video exists in content state (like images do)
235
+ // This handles initialization from edit data where video is in content but not yet in Redux
236
+ platformVideoData = {};
237
+ }
238
+
239
+ // Add videoSrc content state as additional source for the component
240
+ let videoSource = '';
241
+ videoSource = activeTab === ANDROID ? videoSrc?.androidVideoSrc || '' : videoSrc?.iosVideoSrc || '';
242
+
243
+ return (
244
+ <CapVideoUpload
245
+ key="mpush-video-upload"
246
+ index={currentIndex}
247
+ allowedExtensionsRegex={ALLOWED_EXTENSIONS_VIDEO_REGEX}
248
+ videoSize={MPUSH_VIDEO_SIZE}
249
+ isFullMode={isFullMode}
250
+ uploadAsset={uploadMpushAsset}
251
+ uploadedAssetList={videoAssetList || {}}
252
+ onVideoUploadUpdateAssestList={setUpdateMpushVideoSrc}
253
+ videoData={platformVideoData}
254
+ videoSrc={videoSource} // Pass video content state as additional source
255
+ className="cap-custom-video-upload"
256
+ formClassName="mpush-video-upload"
257
+ channel={MOBILE_PUSH_CHANNEL}
258
+ errorMessage={formatMessage(messages.videoErrorMessage)}
259
+ showVideoNameAndDuration={false}
260
+ showReUploadButton
261
+ mediaType="video"
262
+ />
263
+ );
264
+ }, [
265
+ activeTab,
266
+ isFullMode,
267
+ uploadMpushAsset,
268
+ videoAssetList,
269
+ setUpdateMpushVideoSrc,
270
+ videoDataForVideo,
271
+ videoSrc,
272
+ formatMessage,
273
+ ]);
274
+
275
+ const renderGifComponent = useCallback(() => {
276
+ // Apply same platform isolation logic as images
277
+ let platformGifData = {};
278
+ const currentIndex = activeTab === ANDROID ? 0 : 1;
279
+
280
+ // Use platform-specific GIF data
281
+ if (videoDataForGif && videoDataForGif[`uploadedAssetData${currentIndex}`] && Object.keys(videoDataForGif[`uploadedAssetData${currentIndex}`]).length > 0) {
282
+ // ONLY include the current platform's uploadedAssetData to prevent cross-platform bleeding
283
+ platformGifData = {
284
+ [`uploadedAssetData${currentIndex}`]: videoDataForGif[`uploadedAssetData${currentIndex}`],
285
+ };
286
+ } else {
287
+ // No platform-specific GIF data in Redux - check if GIF exists in content state (like images do)
288
+ // This handles initialization from edit data where GIF is in content but not yet in Redux
289
+ platformGifData = {};
290
+ }
291
+ // Add videoSrc content state as additional source for the component
292
+ const gifSource = activeTab === ANDROID ? videoSrc?.androidVideoSrc || '' : videoSrc?.iosVideoSrc || '';
293
+
294
+ return (
295
+ <CapVideoUpload
296
+ key="mpush-gif-upload"
297
+ index={currentIndex}
298
+ allowedExtensionsRegex={ALLOWED_GIF_REGEX}
299
+ videoSize={MPUSH_GIF_SIZE}
300
+ isFullMode={isFullMode}
301
+ uploadAsset={uploadMpushAsset}
302
+ uploadedAssetList={gifAssetList || {}}
303
+ onVideoUploadUpdateAssestList={setUpdateMpushVideoSrc}
304
+ videoData={platformGifData}
305
+ videoSrc={gifSource} // Pass GIF content state as additional source
306
+ className="cap-custom-gif-upload"
307
+ formClassName="mpush-gif-upload"
308
+ channel={MOBILE_PUSH_CHANNEL}
309
+ errorMessage={formatMessage(messages.gifErrorMessage)}
310
+ showVideoNameAndDuration={false}
311
+ showReUploadButton
312
+ mediaType={GIF.toLowerCase()}
313
+ />
314
+ );
315
+ }, [
316
+ activeTab,
317
+ uploadMpushAsset,
318
+ gifAssetList,
319
+ setUpdateMpushVideoSrc,
320
+ videoDataForGif,
321
+ videoSrc,
322
+ ]);
323
+
324
+ const renderCarouselComponent = useCallback(() => {
325
+ const handleCarouselMediaOptions = ({ target: { value = '' } = {} }) => {
326
+ setCarouselMediaType(value);
327
+ const currentData = getCurrentCarouselData();
328
+ setCurrentCarouselData(currentData.map((card) => ({
329
+ ...card,
330
+ mediaType: value,
331
+ imageUrl: value === IMAGE.toLowerCase() ? card.imageUrl : '',
332
+ videoSrc: value === VIDEO.toLowerCase() ? card.videoSrc : '',
333
+ buttons: card.buttons || [{
334
+ actionOnClick: false,
335
+ linkType: DEEP_LINK,
336
+ deepLinkValue: '',
337
+ externalLinkValue: '',
338
+ }],
339
+ })));
340
+ };
341
+
342
+ const onTabChange = (index) => {
343
+ setCarouselActiveTabIndex(index);
344
+ };
345
+
346
+ const addCarouselCard = () => {
347
+ const currentData = getCurrentCarouselData();
348
+ if (currentData?.length < MAX_CAROUSEL_ALLOWED) {
349
+ setCurrentCarouselData([
350
+ ...currentData,
351
+ { ...CAROUSEL_INITIAL_DATA, mediaType: carouselMediaType },
352
+ ]);
353
+ }
354
+ };
355
+
356
+ const deleteCarouselCard = (index) => {
357
+ const currentData = getCurrentCarouselData();
358
+ if (currentData?.length > 1) {
359
+ setCurrentCarouselData(currentData.filter((_, i) => i !== index));
360
+ if (carouselActiveTabIndex >= currentData?.length - 1) {
361
+ setCarouselActiveTabIndex(Math.max(0, currentData?.length - 2));
362
+ }
363
+ }
364
+ };
365
+
366
+ const updateCarouselCard = (index, fields) => {
367
+ const currentData = getCurrentCarouselData();
368
+ setCurrentCarouselData(currentData.map((card, i) => (
369
+ i === index ? { ...card, ...fields } : card
370
+ )));
371
+ // Don't clear assets for carousel cards since they store data in component state, not Redux
372
+ // Only clear assets for non-carousel media types
373
+ };
374
+
375
+ const handleCarouselImageChange = (image) => {
376
+ updateCarouselCard(parseInt(carouselActiveTabIndex, 10), { imageUrl: image });
377
+ };
378
+
379
+ // Carousel-specific re-upload handler that clears both Redux and component state
380
+ const handleCarouselImageReUpload = () => {
381
+ // Set re-upload mode to prevent automatic restoration
382
+ setIsReUploading(true);
383
+
384
+ // Clear Redux state (same as updateOnMpushImageReUpload)
385
+ updateOnMpushImageReUpload();
386
+
387
+ // Clear Redux uploadedAssetData to prevent automatic restoration
388
+ const platformIndex = activeTab === ANDROID ? 0 : 1;
389
+ mobilePushActions.clearAsset(platformIndex);
390
+
391
+ // Clear carousel component state for the current card
392
+ const currentData = getCurrentCarouselData();
393
+ if (currentData?.length > carouselActiveTabIndex) {
394
+ const updatedData = [...currentData];
395
+ updatedData[carouselActiveTabIndex] = {
396
+ ...updatedData[carouselActiveTabIndex],
397
+ imageUrl: '',
398
+ };
399
+ setCurrentCarouselData(updatedData);
400
+ }
401
+
402
+ // Reset re-upload mode after a longer delay to allow for upload completion
403
+ setTimeout(() => {
404
+ setIsReUploading(false);
405
+ }, 3000); // Increased from 1000ms to 3000ms
406
+ };
407
+
408
+ // Action link handlers for carousel cards
409
+ const handleCarouselActionOnClickChange = (cardIndex, checked) => {
410
+ const currentData = getCurrentCarouselData();
411
+ const card = currentData[cardIndex];
412
+ const updatedButtons = [...card?.buttons];
413
+ updatedButtons[0] = { ...updatedButtons[0], actionOnClick: checked };
414
+ updateCarouselCard(cardIndex, { buttons: updatedButtons });
415
+ };
416
+
417
+ const handleCarouselLinkTypeChange = (cardIndex, value) => {
418
+ const currentData = getCurrentCarouselData();
419
+ const card = currentData[cardIndex];
420
+ const updatedButtons = [...card?.buttons];
421
+ updatedButtons[0] = { ...updatedButtons[0], linkType: value };
422
+ updateCarouselCard(cardIndex, { buttons: updatedButtons });
423
+ // Clear errors when link type changes
424
+ updateCarouselLinkError(cardIndex, 'deepLink', null);
425
+ updateCarouselLinkError(cardIndex, 'externalLink', null);
426
+ };
427
+
428
+ const handleCarouselDeepLinkChange = (cardIndex, value) => {
429
+ const currentData = getCurrentCarouselData();
430
+ const card = currentData[cardIndex];
431
+ const updatedButtons = [...card?.buttons];
432
+ updatedButtons[0] = { ...updatedButtons[0], deepLinkValue: value };
433
+ updateCarouselCard(cardIndex, { buttons: updatedButtons });
434
+ // Validate deep link
435
+ const error = validateDeepLink(value, formatMessage, messages);
436
+ updateCarouselLinkError(cardIndex, 'deepLink', error);
437
+ };
438
+
439
+ const handleCarouselExternalLinkChange = (cardIndex, value) => {
440
+ const currentData = getCurrentCarouselData();
441
+ const card = currentData[cardIndex];
442
+ const updatedButtons = [...card?.buttons];
443
+ updatedButtons[0] = { ...updatedButtons[0], externalLinkValue: value };
444
+ updateCarouselCard(cardIndex, { buttons: updatedButtons });
445
+ // Validate external link
446
+ const error = validateExternalLink(value, formatMessage, messages);
447
+ updateCarouselLinkError(cardIndex, 'externalLink', error);
448
+ };
449
+
450
+ const handleCarouselDeepLinkKeysChange = (cardIndex, value) => {
451
+ const currentData = getCurrentCarouselData();
452
+ const card = currentData[cardIndex];
453
+ const updatedButtons = [...card?.buttons];
454
+ // Parse comma-separated values into array
455
+ const keysArray = value?.split(',')?.map((key) => key?.trim())?.filter((key) => key?.length > 0);
456
+ updatedButtons[0] = { ...updatedButtons[0], deepLinkKeys: keysArray };
457
+ updateCarouselCard(cardIndex, { buttons: updatedButtons });
458
+ // Validate deep link keys
459
+ const error = keysArray?.length === 0 ? formatMessage(messages.deepLinkKeysRequired) : null;
460
+ updateCarouselLinkError(cardIndex, 'deepLinkKeys', error);
461
+ };
462
+
463
+ const renderCarouselImageComponent = (cardIndex) => {
464
+ const currentData = getCurrentCarouselData();
465
+ const card = currentData[cardIndex];
466
+
467
+ // Each carousel card maintains its own imageUrl
468
+ const imageSource = card?.imageUrl || '';
469
+
470
+ return (
471
+ <CapImageUpload
472
+ style={{ paddingTop: "20px" }}
473
+ allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
474
+ imgSize={MPUSH_IMG_SIZE}
475
+ imgWidth={MPUSH_IMG_MAX_WIDTH}
476
+ imgHeight={MPUSH_IMG_MAX_HEIGHT}
477
+ uploadAsset={uploadMpushAsset}
478
+ isFullMode={isFullMode}
479
+ imageSrc={imageSource}
480
+ updateImageSrc={(src) => {
481
+ // Update only this specific carousel card's image
482
+ handleCarouselImageChange(src);
483
+ }}
484
+ updateOnReUpload={handleCarouselImageReUpload}
485
+ index={activeTab === ANDROID ? 0 : 1}
486
+ className="cap-custom-image-upload"
487
+ key={`carousel-image-${activeTab}-${cardIndex}`}
488
+ imageData={{}} // Empty object for carousel images since they're stored in component state
489
+ channel={MOBILE_PUSH_CHANNEL}
490
+ showReUploadButton
491
+ errorMessage={formatMessage(messages.imageErrorMessage)}
492
+ disableAutoRestore // Disable automatic restoration for carousel images
493
+ />
494
+ );
495
+ };
496
+
497
+ const renderCarouselVideoComponent = (cardIndex) => (
498
+ <CapVideoUpload
499
+ index={activeTab === ANDROID ? 0 : 1}
500
+ allowedExtensionsRegex={ALLOWED_EXTENSIONS_VIDEO_REGEX}
501
+ videoSize={MPUSH_VIDEO_SIZE}
502
+ isFullMode={isFullMode}
503
+ uploadAsset={uploadMpushAsset}
504
+ uploadedAssetList={videoAssetList}
505
+ onVideoUploadUpdateAssestList={(src) => updateCarouselCard(cardIndex, { videoSrc: src })}
506
+ videoData={videoDataForVideo}
507
+ className="cap-custom-video-upload"
508
+ formClassName="mpush-video-upload"
509
+ channel={MOBILE_PUSH_CHANNEL}
510
+ errorMessage={formatMessage(messages.videoErrorMessage)}
511
+ showVideoNameAndDuration={false}
512
+ showReUploadButton
513
+ mediaType="video"
514
+ />
515
+ );
516
+
517
+ const renderCarouselActionLinks = (cardIndex) => {
518
+ const currentData = getCurrentCarouselData();
519
+ const card = currentData[cardIndex];
520
+
521
+ // Ensure buttons array exists and has at least one element
522
+ if (!card?.buttons || !Array.isArray(card?.buttons) || card?.buttons?.length === 0) {
523
+ return null;
524
+ }
525
+
526
+ const button = card?.buttons[0]; // We're handling only one button for now
527
+
528
+ return (
529
+ <>
530
+ <CapDivider />
531
+ <CapRow className="creatives-mpush-actions">
532
+ <CapRow className="mpush-actions-main">
533
+ <CapHeading type="h4" className="mpush-actions">
534
+ <FormattedMessage {...messages.buttonsAndLinks} />
535
+ </CapHeading>
536
+ <CapLabel className="optional-text">
537
+ <FormattedMessage {...messages.optionalText} />
538
+ </CapLabel>
539
+ </CapRow>
540
+ <CapCheckbox
541
+ checked={button.actionOnClick}
542
+ onChange={(e) => handleCarouselActionOnClickChange(cardIndex, e.target.checked)}
543
+ className="action-on-click-checkbox"
544
+ >
545
+ <FormattedMessage {...messages.actionOnClickBody} />
546
+ </CapCheckbox>
547
+ <CapRow>
548
+ <CapLabel className="action-description">
549
+ <FormattedMessage {...messages.actionDescription} />
550
+ </CapLabel>
551
+ </CapRow>
552
+ {button.actionOnClick && (
553
+ <CapRow style={{ display: "flex", justifyContent: "space-around" }}>
554
+ <CapColumn span={6}>
555
+ <CapHeading type="h4" className="buttons-heading">
556
+ <FormattedMessage {...messages.linkType} />
557
+ </CapHeading>
558
+ <CapSelect.CapCustomSelect
559
+ options={LINK_TYPE_OPTIONS}
560
+ value={button.linkType}
561
+ onChange={(value) => handleCarouselLinkTypeChange(cardIndex, value)}
562
+ key={`mobile-push-link-type-${activeTab}-${cardIndex}`}
563
+ selectPlaceholder={formatMessage(messages.selectDeepLink)}
564
+ />
565
+ </CapColumn>
566
+ {button.linkType === DEEP_LINK && (
567
+ <CapColumn span={14}>
568
+ <CapHeading type="h4" className="buttons-heading">
569
+ {formatMessage(messages.deepLink)}
570
+ </CapHeading>
571
+ <CapSelect.CapCustomSelect
572
+ options={deepLink || []}
573
+ value={button.deepLinkValue}
574
+ onChange={(value) => handleCarouselDeepLinkChange(cardIndex, value)}
575
+ key={`mobile-push-deep-link-type-${activeTab}-${cardIndex}`}
576
+ placeholder={formatMessage(messages.selectDeepLink)}
577
+ style={{ marginTop: "10px" }}
578
+ />
579
+ {carouselLinkErrors[`${cardIndex}-deepLink`] && (
580
+ <CapError className="mobile-push-carousel-deep-link-error">
581
+ {carouselLinkErrors[`${cardIndex}-deepLink`]}
582
+ </CapError>
583
+ )}
584
+ </CapColumn>
585
+ )}
586
+ {button.linkType === EXTERNAL_LINK && (
587
+ <CapColumn span={14}>
588
+ <CapHeading type="h4" className="buttons-heading">
589
+ {formatMessage(messages.externalLink)}
590
+ </CapHeading>
591
+ <CapInput
592
+ id={`mobile-push-external-link-input-${activeTab}-${cardIndex}`}
593
+ onChange={(e) => handleCarouselExternalLinkChange(cardIndex, e.target.value)}
594
+ placeholder={formatMessage(messages.enterExternalLink)}
595
+ value={button.externalLinkValue}
596
+ size="default"
597
+ isRequired
598
+ />
599
+ {carouselLinkErrors[`${cardIndex}-externalLink`] && (
600
+ <CapError className="mobile-push-carousel-external-link-error">
601
+ {carouselLinkErrors[`${cardIndex}-externalLink`]}
602
+ </CapError>
603
+ )}
604
+ </CapColumn>
605
+ )}
606
+ </CapRow>
607
+ )}
608
+ {button.linkType === DEEP_LINK && button.deepLinkValue && (() => {
609
+ const selectedDeepLink = deepLink?.find((link) => link?.value === button.deepLinkValue);
610
+ const deepLinkKeysFromSelection = selectedDeepLink?.keys;
611
+ let deepLinkKeysFromSelectionArray = [];
612
+ if (Array.isArray(deepLinkKeysFromSelection)) {
613
+ deepLinkKeysFromSelectionArray = deepLinkKeysFromSelection;
614
+ } else if (deepLinkKeysFromSelection) {
615
+ deepLinkKeysFromSelectionArray = [deepLinkKeysFromSelection];
616
+ }
617
+ return deepLinkKeysFromSelectionArray?.length > 0;
618
+ })() && (
619
+ <CapRow style={{ marginTop: "10px", left: "6%", width: '115%'}}>
620
+ <CapColumn span={7}>
621
+ <CapHeading type="h4" className="deep-link-keys-heading">
622
+ {formatMessage(messages.deepLinkKeys)}
623
+ </CapHeading>
624
+ <CapLabel type="label2" className="deep-link-keys-value">
625
+ {(() => {
626
+ const selectedDeepLink = deepLink?.find((link) => link?.value === button.deepLinkValue);
627
+ const deepLinkKeysFromSelection = selectedDeepLink?.keys;
628
+ let deepLinkKeysArray = [];
629
+ if (Array.isArray(button?.deepLinkKeys)) {
630
+ deepLinkKeysArray = button?.deepLinkKeys;
631
+ } else if (button?.deepLinkKeys) {
632
+ deepLinkKeysArray = [button?.deepLinkKeys];
633
+ }
634
+ let deepLinkKeysFromSelectionArray = [];
635
+ if (Array.isArray(deepLinkKeysFromSelection)) {
636
+ deepLinkKeysFromSelectionArray = deepLinkKeysFromSelection;
637
+ } else if (deepLinkKeysFromSelection) {
638
+ deepLinkKeysFromSelectionArray = [deepLinkKeysFromSelection];
639
+ }
640
+
641
+ if (deepLinkKeysFromSelectionArray?.length > 0) {
642
+ return deepLinkKeysFromSelectionArray.join(', ');
643
+ }
644
+ if (deepLinkKeysArray?.length > 0) {
645
+ return deepLinkKeysArray.join(', ');
646
+ }
647
+ return "No value set";
648
+ })()}
649
+ </CapLabel>
650
+ <CapInput
651
+ id={`mobile-push-carousel-deep-link-keys-input-${activeTab}-${cardIndex}`}
652
+ onChange={(e) => handleCarouselDeepLinkKeysChange(cardIndex, e.target.value)}
653
+ placeholder={formatMessage(messages.deepLinkKeysPlaceholder, {
654
+ key: (() => {
655
+ const selectedDeepLink = deepLink?.find((link) => link?.value === button.deepLinkValue);
656
+ const deepLinkKeysFromSelection = selectedDeepLink?.keys;
657
+ let deepLinkKeysFromSelectionArray = [];
658
+ if (Array.isArray(deepLinkKeysFromSelection)) {
659
+ deepLinkKeysFromSelectionArray = deepLinkKeysFromSelection;
660
+ } else if (deepLinkKeysFromSelection) {
661
+ deepLinkKeysFromSelectionArray = [deepLinkKeysFromSelection];
662
+ }
663
+ return deepLinkKeysFromSelectionArray.join(', ') || 'deep link keys';
664
+ })(),
665
+ })}
666
+ value={Array.isArray(button?.deepLinkKeys) ? button?.deepLinkKeys?.join(', ') : (button?.deepLinkKeys || "")}
667
+ size="default"
668
+ style={{ marginTop: "10px" }}
669
+ error={carouselLinkErrors[`${cardIndex}-deepLinkKeys`]}
670
+ />
671
+ </CapColumn>
672
+ </CapRow>
673
+ )}
674
+ </CapRow>
675
+ </>
676
+ );
677
+ };
678
+
679
+ const getTabPanes = () => {
680
+ const currentData = getCurrentCarouselData();
681
+ return currentData?.map((data, index) => ({
682
+ key: index,
683
+ tab: index + 1,
684
+ content: (
685
+ <CapCard
686
+ title={`${formatMessage(messages.card)} ${index + 1}`}
687
+ extra={(
688
+ <CapButton
689
+ type="flat"
690
+ onClick={() => deleteCarouselCard(index)}
691
+ disabled={currentData?.length === 1}
692
+ >
693
+ <CapIcon type="delete" />
694
+ </CapButton>
695
+ )}
696
+ className="mobile-push-carousel-card"
697
+ >
698
+ <CapRow>
699
+ {carouselMediaType === IMAGE.toLowerCase() ? (
700
+ <CapRow>
701
+ <CapHeading type="h4">
702
+ {formatMessage(messages.mediaImage)}
703
+ </CapHeading>
704
+ {renderCarouselImageComponent(index)}
705
+ </CapRow>
706
+ ) : (
707
+ <CapRow>
708
+ <CapHeading type="h4">
709
+ {formatMessage(messages.mediaVideo)}
710
+ </CapHeading>
711
+ {renderCarouselVideoComponent(index)}
712
+ </CapRow>
713
+ )}
714
+ </CapRow>
715
+ {renderCarouselActionLinks(index)}
716
+ </CapCard>
717
+ ),
718
+ }));
719
+ };
720
+
721
+ const operations = (
722
+ <>
723
+ <CapDivider type="vertical" />
724
+ <CapButton
725
+ onClick={addCarouselCard}
726
+ type="flat"
727
+ className="add-carousel-content-button"
728
+ disabled={getCurrentCarouselData()?.length >= MAX_CAROUSEL_ALLOWED}
729
+ >
730
+ <CapIcon type="plus" />
731
+ </CapButton>
732
+ </>
733
+ );
734
+
735
+ return (
736
+ <div>
737
+ <CapRow>
738
+ <CapRow className="carousel-media-selection">
739
+ <CapColumn className="carousel-media-selection-heading">
740
+ <CapHeading type="h4">
741
+ {formatMessage(messages.carouselMediaType)}
742
+ </CapHeading>
743
+ </CapColumn>
744
+ <CapColumn>
745
+ <div className="carousel-radio-wrapper">
746
+ <CapRadioGroup
747
+ id="carousel-media-radio"
748
+ options={[
749
+ {
750
+ value: IMAGE.toLowerCase(),
751
+ label: formatMessage(messages.mediaImage),
752
+ },
753
+ {
754
+ value: VIDEO.toLowerCase(),
755
+ label: formatMessage(messages.mediaVideo),
756
+ disabled: true,
757
+ },
758
+ ]}
759
+ value={carouselMediaType}
760
+ onChange={handleCarouselMediaOptions}
761
+ className="mobile-push-media-radio"
762
+ />
763
+ </div>
764
+ </CapColumn>
765
+ </CapRow>
766
+ <CapRow className="mobile-push-carousel-tab">
767
+ <CapTab
768
+ defaultActiveKey="0"
769
+ activeKey={carouselActiveTabIndex.toString()}
770
+ tabBarExtraContent={operations}
771
+ onChange={onTabChange}
772
+ panes={getTabPanes()}
773
+ />
774
+ </CapRow>
775
+ </CapRow>
776
+ </div>
777
+ );
778
+ }, [
779
+ carouselMediaType,
780
+ activeTab,
781
+ getCurrentCarouselData,
782
+ setCurrentCarouselData,
783
+ carouselActiveTabIndex,
784
+ uploadMpushAsset,
785
+ isFullMode,
786
+ setUpdateMpushImageSrc,
787
+ updateOnMpushImageReUpload,
788
+ imageData,
789
+ videoAssetList,
790
+ setUpdateMpushVideoSrc,
791
+ videoDataForVideo,
792
+ formatMessage,
793
+ clearImageDataByMediaType,
794
+ mobilePushActions,
795
+ carouselLinkErrors,
796
+ updateCarouselLinkError,
797
+ carouselActiveTabIndex,
798
+ setCarouselActiveTabIndex,
799
+ ]);
800
+
801
+ if (mediaType === IMAGE) return renderImageComponent();
802
+ if (mediaType === VIDEO) return renderVideoComponent();
803
+ if (mediaType === GIF) return renderGifComponent();
804
+ if (mediaType === CAROUSEL) return renderCarouselComponent();
805
+ return null;
806
+ };
807
+
808
+ MediaUploaders.propTypes = {
809
+ mediaType: PropTypes.string.isRequired,
810
+ activeTab: PropTypes.string.isRequired,
811
+ imageSrc: PropTypes.object.isRequired,
812
+ uploadMpushAsset: PropTypes.func.isRequired,
813
+ isFullMode: PropTypes.bool.isRequired,
814
+ setUpdateMpushImageSrc: PropTypes.func.isRequired,
815
+ updateOnMpushImageReUpload: PropTypes.func.isRequired,
816
+ imageData: PropTypes.object.isRequired,
817
+ videoAssetList: PropTypes.object.isRequired,
818
+ gifAssetList: PropTypes.object.isRequired,
819
+ setUpdateMpushVideoSrc: PropTypes.func.isRequired,
820
+ videoDataForVideo: PropTypes.object.isRequired,
821
+ videoDataForGif: PropTypes.object.isRequired,
822
+ videoSrc: PropTypes.object, // Add videoSrc prop for content state fallback
823
+ formatMessage: PropTypes.func.isRequired,
824
+ linkProps: PropTypes.object,
825
+ clearImageDataByMediaType: PropTypes.func.isRequired,
826
+ carouselData: PropTypes.array,
827
+ onCarouselDataChange: PropTypes.func,
828
+ mobilePushActions: PropTypes.object.isRequired,
829
+ carouselActiveTabIndex: PropTypes.number.isRequired,
830
+ setCarouselActiveTabIndex: PropTypes.func.isRequired,
831
+ carouselLinkErrors: PropTypes.object,
832
+ updateCarouselLinkError: PropTypes.func,
833
+ };
834
+
835
+ export default MediaUploaders;