@capillarytech/creatives-library 8.0.242-alpha.0 → 8.0.242-alpha.10
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/assets/Android.png +0 -0
- package/assets/iOS.png +0 -0
- package/constants/unified.js +2 -1
- package/initialReducer.js +2 -0
- package/package.json +1 -1
- package/sagas/__tests__/assetPolling.test.js +74 -3
- package/sagas/assetPolling.js +8 -1
- package/services/api.js +10 -5
- package/services/tests/api.test.js +18 -0
- package/translations/en.json +0 -1
- package/utils/common.js +5 -0
- package/utils/commonUtils.js +14 -1
- package/utils/tests/commonUtil.test.js +224 -0
- package/utils/transformTemplateConfig.js +0 -10
- package/utils/transformerUtils.js +0 -42
- package/v2Components/CapDeviceContent/index.js +61 -56
- package/v2Components/CapImageUpload/constants.js +0 -2
- package/v2Components/CapImageUpload/index.js +14 -54
- package/v2Components/CapImageUpload/index.scss +1 -4
- package/v2Components/CapImageUpload/messages.js +0 -4
- package/v2Components/CapTagList/index.js +6 -1
- package/v2Components/CapTagListWithInput/index.js +5 -1
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
- package/v2Components/ErrorInfoNote/index.js +412 -72
- package/v2Components/ErrorInfoNote/messages.js +22 -0
- package/v2Components/ErrorInfoNote/style.scss +279 -2
- package/v2Components/HtmlEditor/HTMLEditor.js +220 -91
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1132 -133
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +17 -12
- package/v2Components/HtmlEditor/_htmlEditor.scss +107 -45
- package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +13 -101
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -139
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +10 -11
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +70 -72
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +362 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
- package/v2Components/HtmlEditor/constants.js +29 -20
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +95 -85
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +99 -101
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
- package/v2Components/HtmlEditor/utils/validationAdapter.js +34 -41
- package/v2Components/MobilePushPreviewV2/index.js +32 -7
- package/v2Components/TemplatePreview/_templatePreview.scss +44 -24
- package/v2Components/TemplatePreview/index.js +47 -32
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +31 -25
- package/v2Containers/App/constants.js +0 -5
- package/v2Containers/BeeEditor/index.js +82 -80
- package/v2Containers/BeePopupEditor/constants.js +10 -0
- package/v2Containers/BeePopupEditor/index.js +193 -0
- package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +0 -1
- package/v2Containers/CreativesContainer/SlideBoxContent.js +148 -120
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +9 -3
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -2
- package/v2Containers/CreativesContainer/constants.js +1 -2
- package/v2Containers/CreativesContainer/index.js +173 -193
- package/v2Containers/CreativesContainer/messages.js +4 -4
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +36 -0
- package/v2Containers/Email/actions.js +7 -0
- package/v2Containers/Email/constants.js +5 -1
- package/v2Containers/Email/index.js +13 -0
- package/v2Containers/Email/messages.js +32 -0
- package/v2Containers/Email/reducer.js +12 -1
- package/v2Containers/Email/sagas.js +41 -6
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1046 -0
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +193 -7
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
- package/v2Containers/EmailWrapper/constants.js +2 -0
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +436 -67
- package/v2Containers/EmailWrapper/index.js +99 -23
- package/v2Containers/EmailWrapper/messages.js +61 -1
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +111 -77
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
- package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
- package/v2Containers/InApp/actions.js +7 -0
- package/v2Containers/InApp/constants.js +20 -4
- package/v2Containers/InApp/index.js +801 -357
- package/v2Containers/InApp/index.scss +4 -3
- package/v2Containers/InApp/messages.js +7 -3
- package/v2Containers/InApp/reducer.js +21 -3
- package/v2Containers/InApp/sagas.js +29 -9
- package/v2Containers/InApp/selectors.js +25 -5
- package/v2Containers/InApp/tests/index.test.js +154 -50
- package/v2Containers/InApp/tests/reducer.test.js +34 -0
- package/v2Containers/InApp/tests/sagas.test.js +61 -9
- package/v2Containers/InApp/tests/selectors.test.js +612 -0
- package/v2Containers/InAppWrapper/components/InAppWrapperView.js +162 -0
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +9 -0
- package/v2Containers/InAppWrapper/constants.js +16 -0
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
- package/v2Containers/InAppWrapper/index.js +148 -0
- package/v2Containers/InAppWrapper/messages.js +49 -0
- package/v2Containers/InappAdvance/index.js +1099 -0
- package/v2Containers/InappAdvance/index.scss +10 -0
- package/v2Containers/InappAdvance/tests/index.test.js +448 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -3
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -2
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -25
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -18
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -46
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +0 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -8
- package/v2Containers/TagList/index.js +67 -1
- package/v2Containers/Templates/ChannelTypeIllustration.js +1 -13
- package/v2Containers/Templates/_templates.scss +56 -200
- package/v2Containers/Templates/actions.js +1 -2
- package/v2Containers/Templates/constants.js +0 -1
- package/v2Containers/Templates/index.js +124 -277
- package/v2Containers/Templates/messages.js +4 -24
- package/v2Containers/Templates/reducer.js +0 -2
- package/v2Containers/Templates/tests/index.test.js +0 -10
- package/v2Containers/TemplatesV2/index.js +2 -3
- package/v2Containers/TemplatesV2/messages.js +0 -4
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +35 -132
- package/v2Components/CapImageUrlUpload/constants.js +0 -19
- package/v2Components/CapImageUrlUpload/index.js +0 -455
- package/v2Components/CapImageUrlUpload/index.scss +0 -35
- package/v2Components/CapImageUrlUpload/messages.js +0 -47
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
- package/v2Containers/WebPush/Create/components/ButtonForm.js +0 -175
- package/v2Containers/WebPush/Create/components/ButtonItem.js +0 -101
- package/v2Containers/WebPush/Create/components/ButtonList.js +0 -144
- package/v2Containers/WebPush/Create/components/_buttons.scss +0 -246
- package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +0 -554
- package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +0 -607
- package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +0 -633
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +0 -666
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +0 -74
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +0 -80
- package/v2Containers/WebPush/Create/index.js +0 -1755
- package/v2Containers/WebPush/Create/index.scss +0 -123
- package/v2Containers/WebPush/Create/messages.js +0 -199
- package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +0 -241
- package/v2Containers/WebPush/Create/preview/NotificationContainer.js +0 -290
- package/v2Containers/WebPush/Create/preview/PreviewContent.js +0 -81
- package/v2Containers/WebPush/Create/preview/PreviewControls.js +0 -240
- package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +0 -23
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +0 -144
- package/v2Containers/WebPush/Create/preview/assets/Light.svg +0 -53
- package/v2Containers/WebPush/Create/preview/assets/Top.svg +0 -5
- package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +0 -106
- package/v2Containers/WebPush/Create/preview/assets/iOS.svg +0 -26
- package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +0 -18
- package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +0 -29
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +0 -44
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +0 -110
- package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +0 -45
- package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +0 -72
- package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +0 -55
- package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +0 -70
- package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +0 -512
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +0 -77
- package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +0 -527
- package/v2Containers/WebPush/Create/preview/constants.js +0 -162
- package/v2Containers/WebPush/Create/preview/notification-container.scss +0 -104
- package/v2Containers/WebPush/Create/preview/preview.scss +0 -409
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +0 -300
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +0 -12
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +0 -12
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +0 -12
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +0 -303
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +0 -11
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +0 -11
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +0 -11
- package/v2Containers/WebPush/Create/preview/styles/_base.scss +0 -188
- package/v2Containers/WebPush/Create/preview/styles/_ios.scss +0 -106
- package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +0 -107
- package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +0 -75
- package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +0 -174
- package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +0 -909
- package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +0 -1077
- package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +0 -723
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +0 -943
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +0 -128
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +0 -121
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +0 -144
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +0 -127
- package/v2Containers/WebPush/Create/utils/urlValidation.js +0 -116
- package/v2Containers/WebPush/Create/utils/urlValidation.test.js +0 -449
- package/v2Containers/WebPush/actions.js +0 -60
- package/v2Containers/WebPush/constants.js +0 -108
- package/v2Containers/WebPush/index.js +0 -2
- package/v2Containers/WebPush/reducer.js +0 -104
- package/v2Containers/WebPush/sagas.js +0 -119
- package/v2Containers/WebPush/selectors.js +0 -65
- package/v2Containers/WebPush/tests/reducer.test.js +0 -863
- package/v2Containers/WebPush/tests/sagas.test.js +0 -566
- package/v2Containers/WebPush/tests/selectors.test.js +0 -960
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// Default allowed content types for image URL validation
|
|
2
|
-
export const DEFAULT_ALLOWED_CONTENT_TYPES = ['image/jpeg', 'image/jpg', 'image/png'];
|
|
3
|
-
|
|
4
|
-
// Default maximum file size (5MB)
|
|
5
|
-
export const DEFAULT_MAX_SIZE = 5000000;
|
|
6
|
-
|
|
7
|
-
// Default allowed extensions regex
|
|
8
|
-
export const DEFAULT_ALLOWED_EXTENSIONS_REGEX = /\.(jpe?g|png)$/i;
|
|
9
|
-
|
|
10
|
-
// MIME type to file extension mapping
|
|
11
|
-
export const MIME_TYPE_TO_EXTENSION = {
|
|
12
|
-
'image/jpeg': 'jpg',
|
|
13
|
-
'image/jpg': 'jpg',
|
|
14
|
-
'image/png': 'png',
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// Default image extension fallback
|
|
18
|
-
export const DEFAULT_IMAGE_EXTENSION = 'png';
|
|
19
|
-
|
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* CapImageUrlUpload
|
|
4
|
-
*
|
|
5
|
-
* A modular component for uploading images from a URL.
|
|
6
|
-
* Validates URL format, image type, and size before uploading to gallery.
|
|
7
|
-
* Can be used in any form context (creatives, profiles, etc.)
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
|
11
|
-
import PropTypes from 'prop-types';
|
|
12
|
-
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
|
|
13
|
-
import CapInput from '@capillarytech/cap-ui-library/CapInput';
|
|
14
|
-
import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
|
|
15
|
-
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
16
|
-
import CapError from '@capillarytech/cap-ui-library/CapError';
|
|
17
|
-
import messages from './messages';
|
|
18
|
-
import { DEFAULT_ALLOWED_CONTENT_TYPES, DEFAULT_MAX_SIZE, MIME_TYPE_TO_EXTENSION, DEFAULT_IMAGE_EXTENSION } from './constants';
|
|
19
|
-
import './index.scss';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Helper function to upload image from URL
|
|
23
|
-
* Fetches image, validates content type and size, converts to File, and uploads via uploadAsset
|
|
24
|
-
*/
|
|
25
|
-
const uploadImageFromUrlHelper = async (
|
|
26
|
-
url,
|
|
27
|
-
formatMessage,
|
|
28
|
-
messages,
|
|
29
|
-
uploadAssetFn,
|
|
30
|
-
fileNamePrefix,
|
|
31
|
-
maxSize,
|
|
32
|
-
allowedContentTypes = DEFAULT_ALLOWED_CONTENT_TYPES,
|
|
33
|
-
) => {
|
|
34
|
-
const trimmedUrl = url?.trim() || '';
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const response = await fetch(trimmedUrl, {
|
|
38
|
-
method: 'GET',
|
|
39
|
-
redirect: 'follow',
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
return {
|
|
44
|
-
success: false,
|
|
45
|
-
error: formatMessage(messages.imageLoadError),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Validate Content-Type
|
|
50
|
-
const contentType = response.headers?.get('Content-Type') || '';
|
|
51
|
-
const normalizedContentType = contentType.split(';')[0].toLowerCase().trim();
|
|
52
|
-
|
|
53
|
-
if (!allowedContentTypes.includes(normalizedContentType)) {
|
|
54
|
-
return {
|
|
55
|
-
success: false,
|
|
56
|
-
error: formatMessage(messages.imageTypeInvalid),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const blob = await response.blob();
|
|
61
|
-
|
|
62
|
-
if (blob.size > maxSize) {
|
|
63
|
-
return {
|
|
64
|
-
success: false,
|
|
65
|
-
error: formatMessage(messages.imageSizeInvalid),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Load image to get dimensions and verify validity
|
|
70
|
-
return new Promise((resolve) => {
|
|
71
|
-
const img = new Image();
|
|
72
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
73
|
-
|
|
74
|
-
img.onload = () => {
|
|
75
|
-
const extension = MIME_TYPE_TO_EXTENSION[normalizedContentType] || DEFAULT_IMAGE_EXTENSION;
|
|
76
|
-
const fileName = `${fileNamePrefix}.${extension}`;
|
|
77
|
-
const file = new File([blob], fileName, { type: blob.type });
|
|
78
|
-
const fileParams = {
|
|
79
|
-
width: img.width,
|
|
80
|
-
height: img.height,
|
|
81
|
-
error: false,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
uploadAssetFn(file, 'image', fileParams);
|
|
85
|
-
URL.revokeObjectURL(objectUrl);
|
|
86
|
-
|
|
87
|
-
resolve({
|
|
88
|
-
success: true,
|
|
89
|
-
error: '',
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
img.onerror = () => {
|
|
94
|
-
URL.revokeObjectURL(objectUrl);
|
|
95
|
-
resolve({
|
|
96
|
-
success: false,
|
|
97
|
-
error: formatMessage(messages.imageLoadError),
|
|
98
|
-
});
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
img.src = objectUrl;
|
|
102
|
-
});
|
|
103
|
-
} catch (error) {
|
|
104
|
-
return {
|
|
105
|
-
success: false,
|
|
106
|
-
error: formatMessage(messages.imageLoadError),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
function CapImageUrlUpload(props) {
|
|
112
|
-
const {
|
|
113
|
-
intl,
|
|
114
|
-
uploadAsset,
|
|
115
|
-
imgSize = DEFAULT_MAX_SIZE,
|
|
116
|
-
allowedContentTypes = DEFAULT_ALLOWED_CONTENT_TYPES,
|
|
117
|
-
recommendedDimensions = [],
|
|
118
|
-
sizeLabel = '',
|
|
119
|
-
formatLabel = '',
|
|
120
|
-
imageUrl = '',
|
|
121
|
-
imageSrc = '', // Secure file path from parent after upload completes
|
|
122
|
-
onUrlChange,
|
|
123
|
-
onValidationStateChange, // Callback to notify parent of validation state
|
|
124
|
-
onUploadStateChange, // Callback to notify parent of upload state
|
|
125
|
-
isExternalUploading = false, // Upload state from parent (e.g., redux)
|
|
126
|
-
className = '',
|
|
127
|
-
placeholder,
|
|
128
|
-
disabled = false,
|
|
129
|
-
fileNamePrefix = 'image',
|
|
130
|
-
} = props;
|
|
131
|
-
|
|
132
|
-
const { formatMessage } = intl ?? {};
|
|
133
|
-
const { CapHeadingSpan } = CapHeading;
|
|
134
|
-
|
|
135
|
-
const [isValidating, setIsValidating] = useState(false);
|
|
136
|
-
const [isInternalUploading, setIsInternalUploading] = useState(false);
|
|
137
|
-
const [error, setError] = useState('');
|
|
138
|
-
const [isWaitingForUploadComplete, setIsWaitingForUploadComplete] = useState(false);
|
|
139
|
-
|
|
140
|
-
// Track if we're waiting for imageSrc after triggering upload
|
|
141
|
-
const uploadTriggeredRef = useRef(false);
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Validate image URL
|
|
145
|
-
* Checks URL format, fetches as Blob, validates Content-Type, file size, and image validity
|
|
146
|
-
*/
|
|
147
|
-
const validateImageUrl = useCallback(async (url) => {
|
|
148
|
-
const trimmedUrl = url?.trim() || '';
|
|
149
|
-
|
|
150
|
-
if (!trimmedUrl) {
|
|
151
|
-
return { isValid: false, error: '' };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
setIsValidating(true);
|
|
155
|
-
setError('');
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
// Validate URL format
|
|
159
|
-
let urlObj;
|
|
160
|
-
try {
|
|
161
|
-
urlObj = new URL(trimmedUrl);
|
|
162
|
-
if (!['http:', 'https:'].includes(urlObj.protocol)) {
|
|
163
|
-
setIsValidating(false);
|
|
164
|
-
return {
|
|
165
|
-
isValid: false,
|
|
166
|
-
error: formatMessage(messages.imageUrlInvalid),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
} catch (urlError) {
|
|
170
|
-
setIsValidating(false);
|
|
171
|
-
return {
|
|
172
|
-
isValid: false,
|
|
173
|
-
error: formatMessage(messages.imageUrlInvalid),
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Fetch image as Blob to check file size
|
|
178
|
-
let response;
|
|
179
|
-
try {
|
|
180
|
-
response = await fetch(trimmedUrl, {
|
|
181
|
-
method: 'GET',
|
|
182
|
-
redirect: 'follow',
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
if (!response.ok) {
|
|
186
|
-
setIsValidating(false);
|
|
187
|
-
return {
|
|
188
|
-
isValid: false,
|
|
189
|
-
error: formatMessage(messages.imageLoadError),
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
} catch (fetchError) {
|
|
193
|
-
setIsValidating(false);
|
|
194
|
-
return {
|
|
195
|
-
isValid: false,
|
|
196
|
-
error: formatMessage(messages.imageLoadError),
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Validate Content-Type
|
|
201
|
-
const contentType = response.headers?.get('Content-Type') || '';
|
|
202
|
-
const normalizedContentType = contentType.split(';')[0].toLowerCase().trim();
|
|
203
|
-
|
|
204
|
-
if (!allowedContentTypes.includes(normalizedContentType)) {
|
|
205
|
-
setIsValidating(false);
|
|
206
|
-
return {
|
|
207
|
-
isValid: false,
|
|
208
|
-
error: formatMessage(messages.imageTypeInvalid),
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Validate file size
|
|
213
|
-
const blob = await response.blob();
|
|
214
|
-
if (blob.size > imgSize) {
|
|
215
|
-
setIsValidating(false);
|
|
216
|
-
return {
|
|
217
|
-
isValid: false,
|
|
218
|
-
error: formatMessage(messages.imageSizeInvalid),
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Verify image validity by loading in Image object
|
|
223
|
-
return new Promise((resolve) => {
|
|
224
|
-
const img = new Image();
|
|
225
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
226
|
-
|
|
227
|
-
img.onload = () => {
|
|
228
|
-
URL.revokeObjectURL(objectUrl);
|
|
229
|
-
setIsValidating(false);
|
|
230
|
-
resolve({
|
|
231
|
-
isValid: true,
|
|
232
|
-
error: '',
|
|
233
|
-
});
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
img.onerror = () => {
|
|
237
|
-
URL.revokeObjectURL(objectUrl);
|
|
238
|
-
setIsValidating(false);
|
|
239
|
-
resolve({
|
|
240
|
-
isValid: false,
|
|
241
|
-
error: formatMessage(messages.imageLoadError),
|
|
242
|
-
});
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
img.src = objectUrl;
|
|
246
|
-
});
|
|
247
|
-
} catch (error) {
|
|
248
|
-
setIsValidating(false);
|
|
249
|
-
return {
|
|
250
|
-
isValid: false,
|
|
251
|
-
error: formatMessage(messages.imageLoadError),
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
}, [formatMessage, allowedContentTypes, imgSize]);
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Upload image from URL to media gallery
|
|
258
|
-
* Fetches image, converts to File, uploads via uploadAsset
|
|
259
|
-
*/
|
|
260
|
-
const uploadImageFromUrl = useCallback(async (url) => {
|
|
261
|
-
setIsInternalUploading(true);
|
|
262
|
-
uploadTriggeredRef.current = true;
|
|
263
|
-
setIsWaitingForUploadComplete(true);
|
|
264
|
-
|
|
265
|
-
// Notify parent that upload is starting
|
|
266
|
-
if (onUploadStateChange) {
|
|
267
|
-
onUploadStateChange(true);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const result = await uploadImageFromUrlHelper(
|
|
271
|
-
url,
|
|
272
|
-
formatMessage,
|
|
273
|
-
messages,
|
|
274
|
-
uploadAsset,
|
|
275
|
-
fileNamePrefix,
|
|
276
|
-
imgSize,
|
|
277
|
-
allowedContentTypes,
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
setIsInternalUploading(false);
|
|
281
|
-
|
|
282
|
-
// Don't set waiting to false yet - wait for imageSrc to be populated
|
|
283
|
-
return result;
|
|
284
|
-
}, [formatMessage, uploadAsset, fileNamePrefix, imgSize, allowedContentTypes, onUploadStateChange]);
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Handle image URL change
|
|
288
|
-
* Validates URL and triggers upload on success
|
|
289
|
-
*/
|
|
290
|
-
const handleImageUrlChange = useCallback(async (e) => {
|
|
291
|
-
const url = e.target.value;
|
|
292
|
-
const trimmedUrl = url?.trim() || '';
|
|
293
|
-
|
|
294
|
-
// Reset upload completion tracking when URL changes
|
|
295
|
-
uploadTriggeredRef.current = false;
|
|
296
|
-
setIsWaitingForUploadComplete(false);
|
|
297
|
-
|
|
298
|
-
// Call parent's onUrlChange if provided
|
|
299
|
-
if (onUrlChange) {
|
|
300
|
-
onUrlChange(url);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (trimmedUrl !== '') {
|
|
304
|
-
// Notify parent that validation is starting
|
|
305
|
-
if (onValidationStateChange) {
|
|
306
|
-
onValidationStateChange(true);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const validation = await validateImageUrl(trimmedUrl);
|
|
310
|
-
|
|
311
|
-
// Notify parent that validation is complete
|
|
312
|
-
if (onValidationStateChange) {
|
|
313
|
-
onValidationStateChange(false);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (!validation.isValid) {
|
|
317
|
-
setError(validation.error);
|
|
318
|
-
if (onUploadStateChange) {
|
|
319
|
-
onUploadStateChange(false);
|
|
320
|
-
}
|
|
321
|
-
} else {
|
|
322
|
-
setError('');
|
|
323
|
-
// Upload image from URL when validation succeeds
|
|
324
|
-
const uploadResult = await uploadImageFromUrl(trimmedUrl);
|
|
325
|
-
if (!uploadResult.success) {
|
|
326
|
-
setError(uploadResult.error);
|
|
327
|
-
setIsWaitingForUploadComplete(false);
|
|
328
|
-
if (onUploadStateChange) {
|
|
329
|
-
onUploadStateChange(false);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
} else {
|
|
334
|
-
setError('');
|
|
335
|
-
setIsWaitingForUploadComplete(false);
|
|
336
|
-
if (onValidationStateChange) {
|
|
337
|
-
onValidationStateChange(false);
|
|
338
|
-
}
|
|
339
|
-
if (onUploadStateChange) {
|
|
340
|
-
onUploadStateChange(false);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}, [validateImageUrl, uploadImageFromUrl, onUrlChange, onValidationStateChange, onUploadStateChange]);
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Monitor imageSrc to detect when upload is complete
|
|
347
|
-
* Once imageSrc is populated after upload, we can stop showing "uploading"
|
|
348
|
-
*/
|
|
349
|
-
useEffect(() => {
|
|
350
|
-
if (uploadTriggeredRef.current && imageSrc && imageSrc !== '') {
|
|
351
|
-
// Upload is complete - we have the secure file path
|
|
352
|
-
setIsWaitingForUploadComplete(false);
|
|
353
|
-
uploadTriggeredRef.current = false;
|
|
354
|
-
|
|
355
|
-
// Notify parent that upload is complete
|
|
356
|
-
if (onUploadStateChange) {
|
|
357
|
-
onUploadStateChange(false);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}, [imageSrc, onUploadStateChange]);
|
|
361
|
-
|
|
362
|
-
// Determine if we should show "uploading" state
|
|
363
|
-
// Show uploading when:
|
|
364
|
-
// 1. Internal upload is in progress, OR
|
|
365
|
-
// 2. External upload is in progress (from parent's redux), OR
|
|
366
|
-
// 3. We're waiting for imageSrc after upload was triggered
|
|
367
|
-
const isActuallyUploading = isInternalUploading || isExternalUploading || isWaitingForUploadComplete;
|
|
368
|
-
|
|
369
|
-
return (
|
|
370
|
-
<div className={`cap-image-url-upload ${className}`}>
|
|
371
|
-
<CapInput
|
|
372
|
-
className="image-url-input"
|
|
373
|
-
placeholder={placeholder || formatMessage(messages.imageUrlPlaceholder)}
|
|
374
|
-
value={imageUrl}
|
|
375
|
-
onChange={handleImageUrlChange}
|
|
376
|
-
size="default"
|
|
377
|
-
errorMessage={
|
|
378
|
-
error && (
|
|
379
|
-
<CapError className="image-url-error">
|
|
380
|
-
{error}
|
|
381
|
-
</CapError>
|
|
382
|
-
)
|
|
383
|
-
}
|
|
384
|
-
disabled={disabled || isValidating || isActuallyUploading}
|
|
385
|
-
/>
|
|
386
|
-
|
|
387
|
-
{isValidating && (
|
|
388
|
-
<CapLabel type="label2" className="validation-label">
|
|
389
|
-
<FormattedMessage {...messages.validatingUrl} />
|
|
390
|
-
</CapLabel>
|
|
391
|
-
)}
|
|
392
|
-
|
|
393
|
-
{isActuallyUploading && !isValidating && (
|
|
394
|
-
<CapLabel type="label2" className="uploading-label">
|
|
395
|
-
<FormattedMessage {...messages.uploadingImage} />
|
|
396
|
-
</CapLabel>
|
|
397
|
-
)}
|
|
398
|
-
|
|
399
|
-
<div className="webpush-image-specs">
|
|
400
|
-
{recommendedDimensions && recommendedDimensions.length > 0 && (
|
|
401
|
-
<CapHeadingSpan type="label2" className="image-dimension">
|
|
402
|
-
<FormattedMessage
|
|
403
|
-
{...messages.recommendedDimensions}
|
|
404
|
-
values={{
|
|
405
|
-
dimensions: recommendedDimensions
|
|
406
|
-
.map((dim) => `${dim.width} x ${dim.height}px`)
|
|
407
|
-
.join(', '),
|
|
408
|
-
}}
|
|
409
|
-
/>
|
|
410
|
-
</CapHeadingSpan>
|
|
411
|
-
)}
|
|
412
|
-
|
|
413
|
-
{sizeLabel && (
|
|
414
|
-
<CapHeadingSpan type="label2" className="image-size">
|
|
415
|
-
{sizeLabel}
|
|
416
|
-
</CapHeadingSpan>
|
|
417
|
-
)}
|
|
418
|
-
|
|
419
|
-
{formatLabel && (
|
|
420
|
-
<CapHeadingSpan type="label2" className="image-format">
|
|
421
|
-
{formatLabel}
|
|
422
|
-
</CapHeadingSpan>
|
|
423
|
-
)}
|
|
424
|
-
</div>
|
|
425
|
-
</div>
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
CapImageUrlUpload.propTypes = {
|
|
430
|
-
intl: intlShape.isRequired,
|
|
431
|
-
uploadAsset: PropTypes.func.isRequired,
|
|
432
|
-
imgSize: PropTypes.number,
|
|
433
|
-
allowedContentTypes: PropTypes.arrayOf(PropTypes.string),
|
|
434
|
-
recommendedDimensions: PropTypes.arrayOf(
|
|
435
|
-
PropTypes.shape({
|
|
436
|
-
width: PropTypes.number.isRequired,
|
|
437
|
-
height: PropTypes.number.isRequired,
|
|
438
|
-
})
|
|
439
|
-
),
|
|
440
|
-
sizeLabel: PropTypes.string,
|
|
441
|
-
formatLabel: PropTypes.string,
|
|
442
|
-
imageUrl: PropTypes.string,
|
|
443
|
-
imageSrc: PropTypes.string, // Secure file path from parent after upload completes
|
|
444
|
-
onUrlChange: PropTypes.func,
|
|
445
|
-
onValidationStateChange: PropTypes.func, // Callback(isValidating: bool)
|
|
446
|
-
onUploadStateChange: PropTypes.func, // Callback(isUploading: bool)
|
|
447
|
-
isExternalUploading: PropTypes.bool, // Upload state from parent (e.g., redux)
|
|
448
|
-
className: PropTypes.string,
|
|
449
|
-
placeholder: PropTypes.string,
|
|
450
|
-
disabled: PropTypes.bool,
|
|
451
|
-
fileNamePrefix: PropTypes.string,
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
export default injectIntl(CapImageUrlUpload);
|
|
455
|
-
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
@import '~@capillarytech/cap-ui-library/styles/_variables.scss';
|
|
2
|
-
|
|
3
|
-
@mixin cap-image-url-spec-text {
|
|
4
|
-
.image-dimension,
|
|
5
|
-
.image-size,
|
|
6
|
-
.image-format {
|
|
7
|
-
color: $CAP_G01;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.cap-image-url-upload {
|
|
12
|
-
.image-url-input {
|
|
13
|
-
width: 100%;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.validation-label,
|
|
17
|
-
.uploading-label {
|
|
18
|
-
margin-top: $CAP_SPACE_08;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.image-url-error {
|
|
22
|
-
margin-top: $CAP_SPACE_04;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.webpush-image-specs {
|
|
26
|
-
@include cap-image-url-spec-text;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.webpush-container & {
|
|
30
|
-
.webpush-image-specs {
|
|
31
|
-
@include cap-image-url-spec-text;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { defineMessages } from 'react-intl';
|
|
2
|
-
|
|
3
|
-
const scope = 'app.v2Components.CapImageUrlUpload';
|
|
4
|
-
|
|
5
|
-
export default defineMessages({
|
|
6
|
-
imageUrlPlaceholder: {
|
|
7
|
-
id: `${scope}.imageUrlPlaceholder`,
|
|
8
|
-
defaultMessage: 'Enter image URL',
|
|
9
|
-
},
|
|
10
|
-
imageUrlInvalid: {
|
|
11
|
-
id: `${scope}.imageUrlInvalid`,
|
|
12
|
-
defaultMessage: 'Please enter a valid image URL',
|
|
13
|
-
},
|
|
14
|
-
imageTypeInvalid: {
|
|
15
|
-
id: `${scope}.imageTypeInvalid`,
|
|
16
|
-
defaultMessage: 'Invalid image type. Only JPEG, JPG, and PNG formats are allowed',
|
|
17
|
-
},
|
|
18
|
-
imageSizeInvalid: {
|
|
19
|
-
id: `${scope}.imageSizeInvalid`,
|
|
20
|
-
defaultMessage: 'Image size exceeds the maximum limit',
|
|
21
|
-
},
|
|
22
|
-
imageLoadError: {
|
|
23
|
-
id: `${scope}.imageLoadError`,
|
|
24
|
-
defaultMessage: 'Failed to load image. Please check the URL and try again',
|
|
25
|
-
},
|
|
26
|
-
validatingUrl: {
|
|
27
|
-
id: `${scope}.validatingUrl`,
|
|
28
|
-
defaultMessage: 'Validating image URL...',
|
|
29
|
-
},
|
|
30
|
-
uploadingImage: {
|
|
31
|
-
id: `${scope}.uploadingImage`,
|
|
32
|
-
defaultMessage: 'Uploading image to gallery...',
|
|
33
|
-
},
|
|
34
|
-
recommendedDimensions: {
|
|
35
|
-
id: `${scope}.recommendedDimensions`,
|
|
36
|
-
defaultMessage: 'Recommended dimensions: {dimensions}',
|
|
37
|
-
},
|
|
38
|
-
sizeLimit: {
|
|
39
|
-
id: `${scope}.sizeLimit`,
|
|
40
|
-
defaultMessage: 'Size upto: {size}',
|
|
41
|
-
},
|
|
42
|
-
formatTypes: {
|
|
43
|
-
id: `${scope}.formatTypes`,
|
|
44
|
-
defaultMessage: 'Format: {formats}',
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|