@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.
- package/containers/App/constants.js +1 -0
- package/package.json +1 -1
- package/services/api.js +1 -1
- 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 +240 -0
- package/utils/tests/createPayload.test.js +761 -0
- package/v2Components/CapDeviceContent/index.js +1 -0
- package/v2Components/CapImageUpload/index.js +51 -45
- 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/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/v2Containers/CreativesContainer/SlideBoxContent.js +7 -8
- package/v2Containers/CreativesContainer/index.js +194 -138
- package/v2Containers/InApp/constants.js +1 -1
- package/v2Containers/InApp/index.js +13 -13
- package/v2Containers/MobilePush/Create/index.js +1 -0
- package/v2Containers/MobilePushNew/actions.js +116 -0
- package/v2Containers/MobilePushNew/components/CtaButtons.js +170 -0
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +686 -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 +709 -0
- package/v2Containers/MobilePushNew/index.js +1960 -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,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;
|