@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.
- package/containers/App/constants.js +1 -0
- package/index.html +3 -1
- package/package.json +1 -1
- package/services/api.js +4 -4
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
- package/tests/integration/TemplateCreation/api-response.js +5 -0
- package/tests/integration/TemplateCreation/msw-handler.js +42 -63
- package/utils/common.js +7 -0
- package/utils/commonUtils.js +2 -6
- package/utils/createPayload.js +272 -0
- package/utils/tests/createPayload.test.js +761 -0
- package/v2Components/CapImageUpload/index.js +59 -46
- package/v2Components/CapInAppCTA/index.js +1 -0
- package/v2Components/CapMpushCTA/constants.js +25 -0
- package/v2Components/CapMpushCTA/index.js +332 -0
- package/v2Components/CapMpushCTA/index.scss +95 -0
- package/v2Components/CapMpushCTA/messages.js +89 -0
- package/v2Components/CapTagList/index.js +177 -120
- package/v2Components/CapVideoUpload/constants.js +3 -0
- package/v2Components/CapVideoUpload/index.js +167 -110
- package/v2Components/CapVideoUpload/messages.js +16 -0
- package/v2Components/Carousel/index.js +15 -13
- package/v2Components/CustomerSearchSection/index.js +12 -7
- package/v2Components/ErrorInfoNote/style.scss +1 -0
- package/v2Components/MobilePushPreviewV2/index.js +37 -5
- package/v2Components/TemplatePreview/_templatePreview.scss +114 -72
- package/v2Components/TemplatePreview/assets/images/Android _ With date and time.svg +29 -0
- package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
- package/v2Components/TemplatePreview/assets/images/iOS _ With date and time.svg +26 -0
- package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
- package/v2Components/TemplatePreview/index.js +178 -50
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +169 -0
- package/v2Components/TestAndPreviewSlidebox/LeftPanelContent.js +95 -0
- package/v2Components/TestAndPreviewSlidebox/PreviewSection.js +69 -0
- package/v2Components/TestAndPreviewSlidebox/SendTestMessage.js +68 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +67 -246
- package/v2Components/TestAndPreviewSlidebox/tests/CustomValuesEditor.test.js +425 -0
- package/v2Components/TestAndPreviewSlidebox/tests/LeftPanelContent.test.js +400 -0
- package/v2Components/TestAndPreviewSlidebox/tests/SendTestMessage.test.js +448 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +9 -9
- package/v2Containers/CreativesContainer/index.js +191 -136
- package/v2Containers/Email/index.js +15 -2
- package/v2Containers/InApp/constants.js +1 -0
- package/v2Containers/InApp/index.js +13 -13
- package/v2Containers/MobilePush/Create/index.js +1 -0
- package/v2Containers/MobilePush/commonMethods.js +7 -14
- package/v2Containers/MobilePushNew/actions.js +116 -0
- package/v2Containers/MobilePushNew/components/CtaButtons.js +170 -0
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +754 -0
- package/v2Containers/MobilePushNew/components/PlatformContentFields.js +279 -0
- package/v2Containers/MobilePushNew/components/index.js +5 -0
- package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +779 -0
- package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +2114 -0
- package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +343 -0
- package/v2Containers/MobilePushNew/constants.js +115 -0
- package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1299 -0
- package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1223 -0
- package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +246 -0
- package/v2Containers/MobilePushNew/hooks/useUpload.js +726 -0
- package/v2Containers/MobilePushNew/index.js +2280 -0
- package/v2Containers/MobilePushNew/index.scss +308 -0
- package/v2Containers/MobilePushNew/messages.js +226 -0
- package/v2Containers/MobilePushNew/reducer.js +160 -0
- package/v2Containers/MobilePushNew/sagas.js +198 -0
- package/v2Containers/MobilePushNew/selectors.js +55 -0
- package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
- package/v2Containers/MobilePushNew/tests/sagas.test.js +863 -0
- package/v2Containers/MobilePushNew/tests/selectors.test.js +425 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +322 -0
- package/v2Containers/MobilePushNew/utils.js +33 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +5 -5
- package/v2Containers/TagList/index.js +56 -10
- package/v2Containers/Templates/_templates.scss +101 -1
- package/v2Containers/Templates/index.js +147 -35
- package/v2Containers/Templates/messages.js +8 -0
- package/v2Containers/Templates/sagas.js +2 -0
- 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;
|