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