@capillarytech/creatives-library 8.0.130 → 8.0.132

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/containers/App/constants.js +1 -0
  2. package/containers/Login/index.js +1 -2
  3. package/package.json +1 -1
  4. package/services/api.js +5 -0
  5. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
  6. package/tests/integration/TemplateCreation/api-response.js +5 -0
  7. package/tests/integration/TemplateCreation/msw-handler.js +42 -63
  8. package/utils/common.js +7 -0
  9. package/utils/commonUtils.js +2 -6
  10. package/utils/createMobilePushPayload.js +322 -0
  11. package/utils/tests/createMobilePushPayload.test.js +1054 -0
  12. package/v2Components/CapDeviceContent/index.js +1 -1
  13. package/v2Components/CapImageUpload/index.js +57 -44
  14. package/v2Components/CapInAppCTA/index.js +1 -0
  15. package/v2Components/CapMpushCTA/constants.js +25 -0
  16. package/v2Components/CapMpushCTA/index.js +403 -0
  17. package/v2Components/CapMpushCTA/index.scss +95 -0
  18. package/v2Components/CapMpushCTA/messages.js +101 -0
  19. package/v2Components/CapTagList/index.js +178 -121
  20. package/v2Components/CapVideoUpload/constants.js +3 -0
  21. package/v2Components/CapVideoUpload/index.js +182 -115
  22. package/v2Components/CapVideoUpload/messages.js +16 -0
  23. package/v2Components/Carousel/index.js +15 -13
  24. package/v2Components/ErrorInfoNote/style.scss +1 -0
  25. package/v2Components/MobilePushPreviewV2/index.js +57 -12
  26. package/v2Components/TemplatePreview/_templatePreview.scss +218 -74
  27. package/v2Components/TemplatePreview/assets/images/Android_With_date_and_time.svg +29 -0
  28. package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
  29. package/v2Components/TemplatePreview/assets/images/iOS_With_date_and_time.svg +26 -0
  30. package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
  31. package/v2Components/TemplatePreview/index.js +234 -107
  32. package/v2Components/TemplatePreview/messages.js +4 -0
  33. package/v2Components/TemplatePreview/tests/__snapshots__/index.test.js.snap +10 -10
  34. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -62
  35. package/v2Containers/CreativesContainer/index.js +193 -136
  36. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -22
  37. package/v2Containers/InApp/constants.js +1 -0
  38. package/v2Containers/InApp/index.js +13 -13
  39. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +4748 -4658
  40. package/v2Containers/Login/index.js +1 -2
  41. package/v2Containers/MobilePush/Create/index.js +1 -0
  42. package/v2Containers/MobilePush/commonMethods.js +7 -14
  43. package/v2Containers/MobilePush/tests/commonMethods.test.js +401 -0
  44. package/v2Containers/MobilePushNew/actions.js +116 -0
  45. package/v2Containers/MobilePushNew/components/CtaButtons.js +183 -0
  46. package/v2Containers/MobilePushNew/components/MediaUploaders.js +835 -0
  47. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +346 -0
  48. package/v2Containers/MobilePushNew/components/index.js +5 -0
  49. package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +565 -0
  50. package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +3180 -0
  51. package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +654 -0
  52. package/v2Containers/MobilePushNew/constants.js +116 -0
  53. package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1462 -0
  54. package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1459 -0
  55. package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +366 -0
  56. package/v2Containers/MobilePushNew/hooks/useUpload.js +740 -0
  57. package/v2Containers/MobilePushNew/index.js +2158 -0
  58. package/v2Containers/MobilePushNew/index.scss +308 -0
  59. package/v2Containers/MobilePushNew/messages.js +272 -0
  60. package/v2Containers/MobilePushNew/reducer.js +160 -0
  61. package/v2Containers/MobilePushNew/sagas.js +193 -0
  62. package/v2Containers/MobilePushNew/selectors.js +55 -0
  63. package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
  64. package/v2Containers/MobilePushNew/tests/sagas.test.js +864 -0
  65. package/v2Containers/MobilePushNew/tests/selectors.test.js +665 -0
  66. package/v2Containers/MobilePushNew/tests/utils.test.js +421 -0
  67. package/v2Containers/MobilePushNew/utils.js +84 -0
  68. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1176 -976
  69. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +684 -424
  70. package/v2Containers/TagList/index.js +56 -10
  71. package/v2Containers/Templates/_templates.scss +100 -1
  72. package/v2Containers/Templates/index.js +170 -31
  73. package/v2Containers/Templates/messages.js +8 -0
  74. package/v2Containers/Templates/sagas.js +1 -0
  75. package/v2Containers/Whatsapp/constants.js +1 -0
  76. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +3992 -3677
  77. package/assets/loading_img.gif +0 -0
@@ -3,7 +3,9 @@
3
3
  * CapVideoUpload
4
4
  *
5
5
  */
6
- import React, { useCallback, useState, Fragment, useRef, useEffect } from 'react';
6
+ import React, {
7
+ useCallback, useState, Fragment, useRef, useEffect,
8
+ } from 'react';
7
9
  import PropTypes from "prop-types";
8
10
  import get from 'lodash/get';
9
11
  import CapDrawer from '@capillarytech/cap-ui-library/CapDrawer';
@@ -19,9 +21,12 @@ import {
19
21
  } from '@capillarytech/cap-ui-library';
20
22
  import messages from './messages';
21
23
  import {bytes2Size, getDecodedFileName} from '../../utils/common';
22
- import { SUPPORTED_FILE_FORMATS, WHATSAPP, DEFAULT, VIBER_MAX_DURATION } from './constants';
24
+ import {
25
+ SUPPORTED_FILE_FORMATS, WHATSAPP, DEFAULT, MAX_DURATION, MOBILEPUSH,
26
+ } from './constants';
23
27
  import './index.scss';
24
28
  import { VIBER } from '../../v2Containers/CreativesContainer/constants';
29
+ import { GIF, VIDEO } from '../../v2Containers/MobilePushNew/constants';
25
30
 
26
31
  function CapVideoUpload(props) {
27
32
  const {
@@ -40,6 +45,9 @@ function CapVideoUpload(props) {
40
45
  showVideoNameAndDuration = true,
41
46
  formClassName = 'video-form',
42
47
  showReUploadButton = true,
48
+ mediaType = VIDEO.toLowerCase(),
49
+ assetUploading = false, // Add assetUploading prop
50
+ videoSrc: propVideoSrc = '', // Add videoSrc prop for fallback
43
51
  } = props;
44
52
  const [isVideoError, updateVideoErrorMessage] = useState(false);
45
53
  const [isVideo, updateVideoStatus] = useState(false);
@@ -50,7 +58,7 @@ function CapVideoUpload(props) {
50
58
 
51
59
  const videoEl = useRef(null);
52
60
  const {
53
- videoSrc = '',
61
+ videoSrc: uploadedVideoSrc = '',
54
62
  videoName,
55
63
  previewUrl,
56
64
  videoHeight,
@@ -59,6 +67,10 @@ function CapVideoUpload(props) {
59
67
  fileHandle = '',
60
68
  } = uploadedAssetList;
61
69
 
70
+ // Use uploadedAssetList.videoSrc first, then fall back to prop videoSrc
71
+ // Only use propVideoSrc if uploadedVideoSrc is empty to avoid conflicts with upload logic
72
+ const videoSrc = uploadedVideoSrc || (uploadedVideoSrc === '' ? propVideoSrc : '');
73
+
62
74
  const [isTemplateDrawerRequired, updateTemplateDrawerRequirement] = useState(false);
63
75
 
64
76
  const uploadVideo = useCallback((e, { files }) => {
@@ -68,6 +80,7 @@ function CapVideoUpload(props) {
68
80
  const _URL = window.URL || window.webkitURL;
69
81
  let incorrectFile = false;
70
82
  const file = files[0];
83
+
71
84
  if (!allowedExtensionsRegex.test(file.name)) {
72
85
  incorrectFile = true;
73
86
  }
@@ -79,7 +92,7 @@ function CapVideoUpload(props) {
79
92
  error: false,
80
93
  isGenerateVideoThumbnail: true,
81
94
  };
82
- submitVideoAction({ file, type: 'video', fileParams }, incorrectFile);
95
+ submitVideoAction({ file, type: mediaType, fileParams }, incorrectFile);
83
96
  if (e) {
84
97
  const event = e;
85
98
  event.target.value = null;
@@ -87,10 +100,18 @@ function CapVideoUpload(props) {
87
100
  }, []);
88
101
 
89
102
  useEffect(() => {
90
- if (videoData[`uploadedAssetData${index}`] && Object.keys(videoData[`uploadedAssetData${index}`]).length) {
91
- const metaVideoSrc = get(videoData, `uploadedAssetData${index}.metaInfo.secure_file_path`, '');
92
- const previewImgUrl = get(videoData, `uploadedAssetData${index}.metaInfo.video_file_path_preview`, '');
93
- const metaVideoName = get(videoData, `uploadedAssetData${index}.metaInfo.name`, '');
103
+ }, [videoData, index]);
104
+
105
+ useEffect(() => {
106
+ if (!isEmpty(videoData[`uploadedAssetData${index}`])) {
107
+ const {
108
+ metaInfo: {
109
+ secure_file_path: metaVideoSrc = '',
110
+ file_name: metaVideoName = '',
111
+ preview_image_url: previewImgUrl = '',
112
+ } = {},
113
+ } = videoData[`uploadedAssetData${index}`] || {};
114
+
94
115
  const metaVideoId = get(videoData, `uploadedAssetData${index}.videoIdResponse.fbVideo.id`, '');
95
116
  const karixFileHandle = get(videoData, `uploadedAssetData${index}.metaInfo.karixFileHandle`, '');
96
117
 
@@ -102,18 +123,15 @@ function CapVideoUpload(props) {
102
123
  videoId: metaVideoId,
103
124
  fileHandle: karixFileHandle,
104
125
  videoDuration: null,
105
- },
106
- );
126
+ });
107
127
  }
108
128
  }
109
- }, [videoData[`uploadedAssetData${index}`]]);
129
+ }, [videoData, index, videoSrc, onVideoUploadUpdateAssestList]);
110
130
 
111
131
  useEffect(() => {
112
- if (videoData[`assetUploadingVideo_${index}`] && videoData[`assetUploading`] !== false) {
113
- const isSpinner = get(videoData, `assetUploadingVideo_${index}`, false);
114
- setSpinning(isSpinner);
115
- }
116
- }, [videoData[`assetUploadingVideo_${index}`]]);
132
+ // Use the assetUploading prop directly for loading state
133
+ setSpinning(assetUploading);
134
+ }, [assetUploading]);
117
135
 
118
136
 
119
137
  useEffect(() => {
@@ -123,7 +141,12 @@ function CapVideoUpload(props) {
123
141
  } = uploadedAssetList || {};
124
142
  if (metaVideoSrc) {
125
143
  updateVideoStatus(true);
144
+ } else {
145
+ updateVideoStatus(false);
126
146
  }
147
+ } else {
148
+ // When uploadedAssetList is empty, reset video status to show upload interface
149
+ updateVideoStatus(false);
127
150
  }
128
151
  }, [uploadedAssetList]);
129
152
 
@@ -136,6 +159,7 @@ function CapVideoUpload(props) {
136
159
  fileParams,
137
160
  type,
138
161
  } = data;
162
+
139
163
  if (incorrectFile || size > videoSize) {
140
164
  if (errorMessage) {
141
165
  updateVideoErrorMessage(errorMessage);
@@ -155,11 +179,7 @@ function CapVideoUpload(props) {
155
179
 
156
180
  const {
157
181
  formatMessage,
158
- } = intl || {};
159
-
160
- const capUploaderCustomVideoRequest = useCallback((uploadData) => {
161
- uploadVideo(undefined, { files: [uploadData.file] });
162
- }, []);
182
+ } = intl || {};
163
183
 
164
184
  const playVideo = useCallback((e) => {
165
185
  if (e.target.paused && !isPlaying) {
@@ -173,78 +193,90 @@ function CapVideoUpload(props) {
173
193
  }
174
194
  }, [isPlaying]);
175
195
 
196
+ const onReUpload = useCallback(() => {
197
+ updateVideoStatus(false);
198
+ updateVideoDuration(null);
199
+ onVideoUploadUpdateAssestList(
200
+ index,
201
+ {
202
+ previewUrl: "",
203
+ videoName: "",
204
+ videoHeight: "",
205
+ videoWidth: "",
206
+ videoSrc: "",
207
+ videoDuration: null,
208
+ videoId: "",
209
+ fileHandle: "",
210
+ }
211
+ );
212
+
213
+ // Reset file input value to allow re-upload of same file
214
+ setTimeout(() => {
215
+ const fileInput = document.getElementById("videoFileName");
216
+ if (fileInput) {
217
+ // eslint-disable-next-line no-param-reassign
218
+ fileInput.value = '';
219
+ fileInput.click();
220
+ }
221
+ }, 100);
222
+ }, [index, onVideoUploadUpdateAssestList]);
223
+
224
+ const updateMetadataLoaded = useCallback(() => {
225
+ const { current: { duration, videoHeight: metaVideoHeight, videoWidth: metaVideoWidth } = {} } = videoEl;
226
+ if (duration) {
227
+ updateVideoDuration(duration);
228
+ if (videoWidth === undefined && videoHeight === undefined) {
229
+ if (duration <= MAX_DURATION) {
230
+ onVideoUploadUpdateAssestList(index, {
231
+ videoSrc,
232
+ videoId,
233
+ videoName,
234
+ videoHeight: metaVideoHeight,
235
+ videoWidth: metaVideoWidth,
236
+ previewUrl,
237
+ fileHandle,
238
+ videoDuration: duration,
239
+ });
240
+ } else {
241
+ onReUpload();
242
+ updateVideoErrorMessage(formatMessage(messages.videoDurationError));
243
+ }
244
+ }
245
+ }
246
+ }, [videoSrc, videoId, videoName, previewUrl, fileHandle, videoWidth, videoHeight, index, onVideoUploadUpdateAssestList, formatMessage, onReUpload]);
247
+
248
+ const videoDurationValue = videoDuration ? moment('1900-01-01 00:00:00').add(videoDuration, 'seconds').format("HH.mm.ss") : '';
249
+
250
+ const capUploaderCustomVideoRequest = useCallback((uploadData) => {
251
+ uploadVideo(undefined, { files: [uploadData.file] });
252
+ }, [uploadVideo]);
253
+
176
254
  const getVideoSection = () => {
177
255
  if (!isVideo) {
178
256
  return (
179
- (
180
- <>
181
- <CapUploader.CapDragger
182
- customRequest={(data) => capUploaderCustomVideoRequest(data)}
183
- className="form-builder-dragger whatsapp-background"
184
- showUploadList={!isVideoError}
185
- >
186
- <CapHeading className="dragger-title" type="h7">
187
- <FormattedMessage {...messages.dragAndDrop} />
188
- </CapHeading>
189
- <CapHeading className="dragger-or" type="label6">
190
- <FormattedMessage {...messages.or} />
191
- </CapHeading>
192
- <CapButton className="dragger-button upload-video" type="secondary">
193
- <FormattedMessage {...messages.selectFromComputer} />
194
- </CapButton>
195
- </CapUploader.CapDragger>
196
- <CapError type="error" className="upload-video-error">
197
- {isVideoError}
198
- </CapError>
199
- </>
200
- )
257
+ <>
258
+ <CapUploader.CapDragger
259
+ customRequest={(data) => capUploaderCustomVideoRequest(data)}
260
+ className="form-builder-dragger whatsapp-background"
261
+ showUploadList={!isVideoError}
262
+ >
263
+ <CapHeading className="dragger-title" type="h7">
264
+ <FormattedMessage {...messages.dragAndDrop} />
265
+ </CapHeading>
266
+ <CapHeading className="dragger-or" type="label6">
267
+ <FormattedMessage {...messages.or} />
268
+ </CapHeading>
269
+ <CapButton className="dragger-button upload-video" type="secondary">
270
+ <FormattedMessage {...messages.selectFromComputer} />
271
+ </CapButton>
272
+ </CapUploader.CapDragger>
273
+ <CapError type="error" className="upload-video-error">
274
+ {isVideoError}
275
+ </CapError>
276
+ </>
201
277
  );
202
278
  }
203
- const onReUpload = () => {
204
- updateVideoStatus(false);
205
- updateVideoDuration(null);
206
- onVideoUploadUpdateAssestList(
207
- index,
208
- {
209
- previewUrl: "",
210
- videoName: "",
211
- videoHeight: "",
212
- videoWidth: "",
213
- videoSrc: "",
214
- videoDuration: null,
215
- videoId: "",
216
- fileHandle: "",
217
- }
218
- );
219
- };
220
279
 
221
- const updateMetadataLoaded = () => {
222
- const { current: { duration, videoHeight: metaVideoHeight, videoWidth: metaVideoWidth } = {} } = videoEl;
223
- if (duration) {
224
- updateVideoDuration(duration);
225
- if (videoWidth === undefined && videoHeight === undefined) {
226
- if (
227
- channel !== VIBER ||
228
- (channel === VIBER && duration <= VIBER_MAX_DURATION)
229
- ) {
230
- onVideoUploadUpdateAssestList(index, {
231
- videoSrc,
232
- videoId,
233
- videoName,
234
- videoHeight: metaVideoHeight,
235
- videoWidth: metaVideoWidth,
236
- previewUrl,
237
- fileHandle,
238
- videoDuration: duration,
239
- });
240
- } else {
241
- onReUpload();
242
- updateVideoErrorMessage(formatMessage(messages.videoDurationError));
243
- }
244
- }
245
- }
246
- };
247
- const videoDurationValue = moment('1900-01-01 00:00:00').add(videoDuration, 'seconds').format("HH.mm.ss"); // to get the duration of video
248
280
  return (
249
281
  <Fragment key={videoSrc}>
250
282
  {showReUploadButton && (
@@ -259,21 +291,35 @@ function CapVideoUpload(props) {
259
291
  </div>
260
292
  )}
261
293
  <div className={showVideoNameAndDuration ? 'video-panel' : 'video-panel-wp'}>
262
- <video
263
- width="230"
264
- poster={previewUrl}
265
- className="line-image-src"
266
- key={`${index}-video`}
267
- onLoadedMetadata={() => updateMetadataLoaded()}
268
- onPlaying={() => updateIsPlaying(true)}
269
- onPause={() => updateIsPlaying(false)}
270
- onMouseOver={playVideo}
271
- onMouseOut={pauseVideo}
272
- ref={videoEl}
273
- >
274
- <source src={videoSrc} type="video/mp4" />
275
- </video>
276
- {showVideoNameAndDuration &&
294
+ {mediaType === GIF.toLowerCase() ? (
295
+ <img
296
+ width="230"
297
+ src={videoSrc}
298
+ alt="GIF preview"
299
+ className="line-image-src"
300
+ key={`${index}-gif`}
301
+ style={{ maxWidth: '230px', maxHeight: '200px', objectFit: 'contain' }}
302
+ />
303
+ ) : (
304
+ <video
305
+ width="230"
306
+ poster={previewUrl}
307
+ className="line-image-src"
308
+ key={`${index}-video`}
309
+ onLoadedMetadata={updateMetadataLoaded}
310
+ onPlaying={() => updateIsPlaying(true)}
311
+ onPause={() => updateIsPlaying(false)}
312
+ onMouseOver={playVideo}
313
+ onMouseOut={pauseVideo}
314
+ onFocus={playVideo}
315
+ onBlur={pauseVideo}
316
+ ref={videoEl}
317
+ >
318
+ <source src={videoSrc} type="video/mp4" />
319
+ <track kind="captions" />
320
+ </video>
321
+ )}
322
+ {showVideoNameAndDuration && (
277
323
  <div className="video-info">
278
324
  <CapHeading type="h4">
279
325
  {getDecodedFileName(videoName)}
@@ -282,7 +328,7 @@ function CapVideoUpload(props) {
282
328
  {videoDurationValue}
283
329
  </CapHeading>
284
330
  </div>
285
- }
331
+ )}
286
332
  </div>
287
333
  </Fragment>
288
334
  );
@@ -300,23 +346,24 @@ function CapVideoUpload(props) {
300
346
  return (
301
347
  <CapSpin spinning={isSpinning}>
302
348
  <div style={style} className="cap-custom-video-upload">
303
- <form encType="multipart/form-data" id={`form`} className={formClassName}>
349
+ <form encType="multipart/form-data" id="form" className={formClassName}>
304
350
  <input
305
- key={`videoFile`}
351
+ key={`videoFile-${index}-${isVideo ? 'uploaded' : 'empty'}`}
306
352
  style={{ display: 'none' }}
307
- id="fileName"
353
+ id="videoFileName"
308
354
  type="file"
309
355
  onChange={(e) => uploadVideo(e, { files: e.target.files })}
310
- accept={supportedExtensions || "video/*"}
311
- />
356
+ accept={mediaType === GIF.toLowerCase() ? '.gif' : (supportedExtensions || "video/*")}
357
+ />
312
358
  {getVideoSection()}
313
359
  <CapDrawer
314
360
  visible={isTemplateDrawerRequired}
315
361
  width={624}
316
362
  onClose={() => updateTemplateDrawerRequirement(false)}
317
- />
363
+ />
318
364
  </form>
319
- {![WHATSAPP, VIBER].includes(channel) &&
365
+ {![WHATSAPP, VIBER, MOBILEPUSH].includes(channel)
366
+ && (
320
367
  <>
321
368
  <CapHeading.CapHeadingSpan type="h6" className="video-description">
322
369
  <FormattedMessage {...messages.videoRatioDescription} />
@@ -328,15 +375,18 @@ function CapVideoUpload(props) {
328
375
  <CapHeading.CapHeadingSpan type="h6" className="video-description video-details">
329
376
  <FormattedMessage {...messages.videoSizeDescription} values={{size: bytes2Size(videoSize)}} />
330
377
  </CapHeading.CapHeadingSpan>
331
- </>}
332
- {channel === WHATSAPP &&
378
+ </>
379
+ )}
380
+ {channel === WHATSAPP
381
+ && (
333
382
  <>
334
383
  {getVideoSizeDescription()}
335
384
  <CapHeading.CapHeadingSpan type="label2" className="whatsapp-format">
336
385
  <FormattedMessage {...messages.channelFileFormat} values={{ format: SUPPORTED_FILE_FORMATS[DEFAULT] }} />
337
386
  </CapHeading.CapHeadingSpan>
338
- </>}
339
- {channel === VIBER &&
387
+ </>
388
+ )}
389
+ {channel === VIBER && (
340
390
  <>
341
391
  {getVideoSizeDescription()}
342
392
  <CapHeading.CapHeadingSpan type="label2" className="whatsapp-format">
@@ -345,7 +395,20 @@ function CapVideoUpload(props) {
345
395
  <CapHeading.CapHeadingSpan type="label2" className="video-duration">
346
396
  <FormattedMessage {...messages.viberMaxDuration} />
347
397
  </CapHeading.CapHeadingSpan>
348
- </>}
398
+ </>
399
+ )}
400
+ {channel === MOBILEPUSH
401
+ && (
402
+ <>
403
+ {getVideoSizeDescription()}
404
+ <CapHeading.CapHeadingSpan type="label2" className="whatsapp-format">
405
+ <FormattedMessage {...messages.channelFileFormat} values={{ format: SUPPORTED_FILE_FORMATS.MOBILEPUSH }} />
406
+ </CapHeading.CapHeadingSpan>
407
+ <CapHeading.CapHeadingSpan type="label2" className="video-duration">
408
+ <FormattedMessage {...messages.maxDuration} />
409
+ </CapHeading.CapHeadingSpan>
410
+ </>
411
+ )}
349
412
  </div>
350
413
  </CapSpin>
351
414
  );
@@ -366,6 +429,10 @@ CapVideoUpload.propTypes = {
366
429
  channel: PropTypes.string,
367
430
  errorMessage: PropTypes.string,
368
431
  formClassName: PropTypes.string,
432
+ showReUploadButton: PropTypes.bool,
433
+ mediaType: PropTypes.string,
434
+ assetUploading: PropTypes.bool,
435
+ videoSrc: PropTypes.string,
369
436
  };
370
437
 
371
438
  export default injectIntl(CapVideoUpload);
@@ -63,8 +63,24 @@ export default defineMessages({
63
63
  id: `${scope}.viberMaxDuration`,
64
64
  defaultMessage: 'Max Duration: 600 seconds',
65
65
  },
66
+ maxDuration: {
67
+ id: `${scope}.maxDuration`,
68
+ defaultMessage: 'Max duration: 600 seconds',
69
+ },
66
70
  videoDurationError: {
67
71
  id: `${scope}.videoDurationError`,
68
72
  defaultMessage: 'Video duration should not exceed 600 seconds',
69
73
  },
74
+ uploadVideo: {
75
+ id: `${scope}.uploadVideo`,
76
+ defaultMessage: 'Upload Video',
77
+ },
78
+ reUpload: {
79
+ id: `${scope}.reUpload`,
80
+ defaultMessage: 'Re-upload',
81
+ },
82
+ videoDuration: {
83
+ id: `${scope}.videoDuration`,
84
+ defaultMessage: 'Duration: {duration}',
85
+ },
70
86
  });
@@ -1,11 +1,11 @@
1
- import React, { Component } from "react";
2
- import { Carousel, Icon } from 'antd';
1
+ import React from "react";
2
+ import { Carousel } from 'antd';
3
3
  import './style.scss';
4
4
  import CapImage from '@capillarytech/cap-ui-library/CapImage';
5
5
  import CapButton from '@capillarytech/cap-ui-library/CapButton';
6
6
  import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
7
+ import { CAP_WHITE } from '@capillarytech/cap-ui-library/styled/variables';
7
8
  const lineImgPlaceholder = require('../../assets/line-image-placeholder.svg');
8
- import { CAP_WHITE, CAP_G08 } from '@capillarytech/cap-ui-library/styled/variables';
9
9
 
10
10
  const arrowStyle = {
11
11
  background: 'rgba(0, 0, 0, 0.1)',
@@ -16,10 +16,12 @@ const arrowStyle = {
16
16
  height: 24,
17
17
  width: 24,
18
18
  borderRadius: '50%',
19
- }
19
+ };
20
20
 
21
- const Arrow = props => {
22
- const { className, style, onClick, extraStyle, type } = props
21
+ const Arrow = (props) => {
22
+ const {
23
+ className, style, onClick, extraStyle, type,
24
+ } = props;
23
25
  return (
24
26
  <CapButton
25
27
  className={className}
@@ -35,8 +37,8 @@ const Arrow = props => {
35
37
  type={type}
36
38
  />
37
39
  </CapButton>
38
- )
39
- }
40
+ );
41
+ };
40
42
 
41
43
  const settings = {
42
44
  nextArrow: (
@@ -56,7 +58,7 @@ const settings = {
56
58
  type="chevron-left"
57
59
  />
58
60
  ),
59
- }
61
+ };
60
62
 
61
63
  const CarouselComponent = ({
62
64
  imageCarousel,
@@ -81,11 +83,11 @@ const CarouselComponent = ({
81
83
 
82
84
  return (
83
85
  <div style={{ position: 'relative' }} className={`template-carousel ${imageCarouselLength ? 'single-img' : ''}`}>
84
- <Carousel ref={node => (carousel = node)} {...prop} {...settings}>
86
+ <Carousel ref={(node) => (carousel = node)} {...prop} {...settings}>
85
87
  {
86
88
  imageCarousel.map(({
87
89
  actionLabel,
88
- imageUrl = lineImgPlaceholder
90
+ imageUrl = lineImgPlaceholder,
89
91
  }) => (
90
92
  <div className="carousel-section">
91
93
  <CapImage
@@ -115,6 +117,6 @@ const CarouselComponent = ({
115
117
  </Carousel>
116
118
  </div>
117
119
  );
118
- }
120
+ };
119
121
 
120
- export default CarouselComponent;
122
+ export default CarouselComponent;
@@ -1,6 +1,7 @@
1
1
  @import "~@capillarytech/cap-ui-library/styles/_variables.scss";
2
2
 
3
3
  .error-container {
4
+ width: max-content;
4
5
  margin-bottom: $CAP_SPACE_08;
5
6
  margin-top: $CAP_SPACE_12;
6
7
  background-color: $CAP_COLOR_05;
@@ -8,13 +8,14 @@ import PropTypes from 'prop-types';
8
8
 
9
9
  import React from 'react';
10
10
  import { CapTab, CapIcon } from '@capillarytech/cap-ui-library';
11
- import { get, map, isEmpty} from 'lodash';
11
+ import { get, isEmpty} from 'lodash';
12
12
  import TemplatePreview from '../TemplatePreview';
13
13
  import '../PreviewSideBar/_previewsidebar.scss';
14
14
  import { MOBILE_PUSH } from '../../v2Containers/CreativesContainer/constants';
15
15
  import { INAPP } from '../../v2Containers/App/constants';
16
16
  import { ANDROID, IOS } from '../../v2Containers/InApp/constants';
17
17
  import { getCtaObject } from '../../v2Containers/InApp/utils';
18
+ import { CAROUSEL, VIDEO } from '../../v2Containers/MobilePushNew/constants';
18
19
 
19
20
  class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react/prefer-stateless-function
20
21
  constructor(props) {
@@ -64,25 +65,65 @@ class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react
64
65
  const androidContent = get(templateData, 'versions.base.ANDROID') || get(templateData, 'androidContent');
65
66
  const iosContent = get(templateData, 'versions.base.IOS') || get(templateData, 'iosContent');
66
67
 
67
- const data = device === "android" ? androidContent : iosContent;
68
- const { title = '', message = '', expandableDetails: { ctas = [], image = '' } = {}} = data || {};
68
+ const data = device?.toLowerCase() === "android" ? androidContent : iosContent;
69
+ const {
70
+ title = '', message = '', type = '', expandableDetails: {
71
+ ctas = [], image = '', media = [], carouselData = [],
72
+ } = {},
73
+ } = data || {};
74
+
75
+ // Handle video/GIF content from expandableDetails.media array (new format)
76
+ let bodyVideo = {};
77
+ let bodyGif = '';
78
+ let bodyImage = image;
79
+
80
+ if (media?.length > 0) {
81
+ const mediaItem = media[0];
82
+ const { type, url } = mediaItem;
83
+ if (type === VIDEO) {
84
+ // Distinguish between actual video and GIF based on URL extension
85
+ if (url && url?.toLowerCase()?.includes('.gif')) {
86
+ bodyGif = url;
87
+ } else {
88
+ bodyVideo = {
89
+ videoSrc: url,
90
+ videoPreview: url,
91
+ };
92
+ }
93
+ // Clear bodyImage when we have video/GIF content
94
+ bodyImage = '';
95
+ }
96
+ }
97
+
69
98
  content = {
70
99
  header: title,
71
100
  bodyText: message,
72
- bodyImage: image,
101
+ bodyImage,
102
+ bodyVideo,
103
+ bodyGif,
73
104
  actions: [],
74
105
  appName: templateData?.appName,
106
+ carouselData,
75
107
  };
76
- if (ctas && ctas?.length) {
77
- if (device === "android" ) {
78
- content.actions = map(ctas, (cta) => ({label: cta?.actionText}));
79
- } else {
80
- content.actions = [{label: ctas[0]?.actionLabel}, {label: ctas[0]?.actionLabel2}];
81
- }
108
+
109
+ // Map CTAs consistently for both Android and iOS
110
+ if (ctas.length > 0 && type !== CAROUSEL) {
111
+ content.actions = ctas.map((cta) => {
112
+ const {
113
+ actionText, type: Type, actionLink, deepLinkKeys,
114
+ } = cta || {};
115
+ return {
116
+ label: actionText || '',
117
+ type: Type || '',
118
+ url: actionLink || '',
119
+ deepLinkKeys: deepLinkKeys || [],
120
+ };
121
+ });
82
122
  }
83
123
  }
84
124
  return content;
85
125
  }
126
+
86
127
  getPreview(device) {
87
128
  const deviceParam = device === ANDROID.toLowerCase() ? ANDROID : IOS;
88
129
  return (
@@ -95,17 +136,21 @@ class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react
95
136
  />
96
137
  );
97
138
  }
139
+
98
140
  goToDuplicate() {
99
141
  this.props.onDuplicateClick(this.props.templateData);
100
142
  this.props.handleClose();
101
143
  }
144
+
102
145
  changeDevice(device) {
103
146
  const content = this.setContent(this.props.templateData, device, this.props.channel);
104
147
  this.setState({device, content });
105
148
  }
149
+
106
150
  goToEdit(e, path) {
107
151
  this.props.onEditClick(e, this.props.templateData._id, this.props.templateData.modeType, path);
108
152
  }
153
+
109
154
  render() {
110
155
  const {templateData} = this.props;
111
156
  let hasAndroid;
@@ -121,8 +166,8 @@ class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react
121
166
  return (
122
167
  <div>
123
168
  <div className="devices">
124
- {!isEmpty(templateData) &&
125
- ( hasBothAndroidAndIos ? (
169
+ {!isEmpty(templateData)
170
+ && ( hasBothAndroidAndIos ? (
126
171
  <CapTab
127
172
  panes={[
128
173
  {