@capillarytech/creatives-library 8.0.123 → 8.0.124

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