@capillarytech/creatives-library 8.0.242-alpha.1 → 8.0.242-alpha.11
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/config/app.js +1 -1
- package/constants/unified.js +2 -2
- package/initialReducer.js +0 -2
- package/package.json +1 -1
- package/services/api.js +5 -10
- package/services/tests/api.test.js +0 -18
- package/translations/en.json +4 -3
- package/utils/common.js +6 -5
- package/utils/commonUtils.js +1 -14
- package/utils/imageUrlUpload.js +141 -0
- package/utils/tests/commonUtil.test.js +0 -224
- package/utils/transformTemplateConfig.js +10 -0
- package/v2Components/CapDeviceContent/index.js +56 -61
- package/v2Components/CapImageUpload/constants.js +2 -0
- package/v2Components/CapImageUpload/index.js +65 -16
- package/v2Components/CapImageUpload/index.scss +4 -1
- package/v2Components/CapImageUpload/messages.js +5 -1
- package/v2Components/CapImageUrlUpload/constants.js +26 -0
- package/v2Components/CapImageUrlUpload/index.js +365 -0
- package/v2Components/CapImageUrlUpload/index.scss +35 -0
- package/v2Components/CapImageUrlUpload/messages.js +47 -0
- package/v2Components/CapTagList/index.js +1 -6
- package/v2Components/CapTagListWithInput/index.js +1 -5
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
- package/v2Components/ErrorInfoNote/index.js +72 -412
- package/v2Components/ErrorInfoNote/messages.js +0 -22
- package/v2Components/ErrorInfoNote/style.scss +2 -279
- package/v2Components/HtmlEditor/HTMLEditor.js +89 -210
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1132
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +12 -17
- package/v2Components/HtmlEditor/_htmlEditor.scss +23 -8
- package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +101 -13
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +139 -148
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -1
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +7 -4
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +45 -35
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +3 -1
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +6 -7
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +6 -3
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +11 -10
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +62 -87
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -49
- package/v2Components/HtmlEditor/constants.js +20 -29
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +85 -95
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +101 -99
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +25 -23
- package/v2Components/HtmlEditor/utils/validationAdapter.js +41 -34
- package/v2Components/MobilePushPreviewV2/index.js +7 -32
- package/v2Components/TemplatePreview/_templatePreview.scss +24 -44
- package/v2Components/TemplatePreview/index.js +32 -47
- package/v2Components/TemplatePreview/messages.js +0 -4
- package/v2Components/TestAndPreviewSlidebox/index.js +25 -31
- package/v2Containers/App/constants.js +5 -0
- package/v2Containers/BeeEditor/index.js +80 -82
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +4 -3
- package/v2Containers/CreativesContainer/SlideBoxContent.js +118 -148
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -9
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -2
- package/v2Containers/CreativesContainer/constants.js +2 -1
- package/v2Containers/CreativesContainer/index.js +41 -173
- package/v2Containers/CreativesContainer/messages.js +4 -4
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +210 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +354 -38
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -36
- package/v2Containers/Email/actions.js +0 -7
- package/v2Containers/Email/constants.js +1 -5
- package/v2Containers/Email/index.js +0 -13
- package/v2Containers/Email/messages.js +0 -32
- package/v2Containers/Email/reducer.js +1 -12
- package/v2Containers/Email/sagas.js +6 -41
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -2
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +7 -193
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
- package/v2Containers/EmailWrapper/constants.js +0 -2
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +67 -436
- package/v2Containers/EmailWrapper/index.js +23 -99
- package/v2Containers/EmailWrapper/messages.js +1 -61
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +1 -26
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -111
- package/v2Containers/InApp/actions.js +0 -7
- package/v2Containers/InApp/constants.js +4 -20
- package/v2Containers/InApp/index.js +357 -800
- package/v2Containers/InApp/index.scss +3 -4
- package/v2Containers/InApp/messages.js +3 -7
- package/v2Containers/InApp/reducer.js +3 -21
- package/v2Containers/InApp/sagas.js +9 -29
- package/v2Containers/InApp/selectors.js +5 -25
- package/v2Containers/InApp/tests/index.test.js +50 -154
- package/v2Containers/InApp/tests/reducer.test.js +0 -34
- package/v2Containers/InApp/tests/sagas.test.js +9 -61
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +12 -12
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +8 -8
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +100 -77
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +72 -63
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +184 -150
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +16 -12
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +32 -28
- package/v2Containers/TagList/index.js +1 -67
- package/v2Containers/Templates/ChannelTypeIllustration.js +13 -1
- package/v2Containers/Templates/_templates.scss +202 -56
- package/v2Containers/Templates/actions.js +2 -1
- package/v2Containers/Templates/constants.js +1 -0
- package/v2Containers/Templates/index.js +278 -128
- package/v2Containers/Templates/messages.js +24 -4
- package/v2Containers/Templates/reducer.js +2 -0
- package/v2Containers/Templates/tests/index.test.js +10 -0
- package/v2Containers/TemplatesV2/index.js +8 -1
- package/v2Containers/TemplatesV2/messages.js +4 -0
- package/v2Containers/WebPush/Create/components/BrandIconSection.js +108 -0
- package/v2Containers/WebPush/Create/components/ButtonForm.js +172 -0
- package/v2Containers/WebPush/Create/components/ButtonItem.js +101 -0
- package/v2Containers/WebPush/Create/components/ButtonList.js +145 -0
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.js +164 -0
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +463 -0
- package/v2Containers/WebPush/Create/components/FormActions.js +54 -0
- package/v2Containers/WebPush/Create/components/FormActions.test.js +163 -0
- package/v2Containers/WebPush/Create/components/MediaSection.js +142 -0
- package/v2Containers/WebPush/Create/components/MediaSection.test.js +341 -0
- package/v2Containers/WebPush/Create/components/MessageSection.js +103 -0
- package/v2Containers/WebPush/Create/components/MessageSection.test.js +268 -0
- package/v2Containers/WebPush/Create/components/NotificationTitleSection.js +87 -0
- package/v2Containers/WebPush/Create/components/NotificationTitleSection.test.js +210 -0
- package/v2Containers/WebPush/Create/components/TemplateNameSection.js +54 -0
- package/v2Containers/WebPush/Create/components/TemplateNameSection.test.js +143 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +86 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/FormActions.test.js.snap +16 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/MediaSection.test.js.snap +41 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +54 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/NotificationTitleSection.test.js.snap +37 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/TemplateNameSection.test.js.snap +21 -0
- package/v2Containers/WebPush/Create/components/_buttons.scss +246 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +554 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +607 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +633 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +666 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +74 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +78 -0
- package/v2Containers/WebPush/Create/hooks/useButtonManagement.js +138 -0
- package/v2Containers/WebPush/Create/hooks/useButtonManagement.test.js +406 -0
- package/v2Containers/WebPush/Create/hooks/useCharacterCount.js +30 -0
- package/v2Containers/WebPush/Create/hooks/useCharacterCount.test.js +151 -0
- package/v2Containers/WebPush/Create/hooks/useImageUpload.js +104 -0
- package/v2Containers/WebPush/Create/hooks/useImageUpload.test.js +538 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +122 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +633 -0
- package/v2Containers/WebPush/Create/index.js +1056 -0
- package/v2Containers/WebPush/Create/index.scss +134 -0
- package/v2Containers/WebPush/Create/messages.js +203 -0
- package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +228 -0
- package/v2Containers/WebPush/Create/preview/NotificationContainer.js +294 -0
- package/v2Containers/WebPush/Create/preview/PreviewContent.js +90 -0
- package/v2Containers/WebPush/Create/preview/PreviewControls.js +305 -0
- package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +23 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +150 -0
- package/v2Containers/WebPush/Create/preview/assets/Light.svg +53 -0
- package/v2Containers/WebPush/Create/preview/assets/Top.svg +5 -0
- package/v2Containers/WebPush/Create/preview/assets/android-arrow-down.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/android-arrow-up.svg +9 -0
- 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 +106 -0
- package/v2Containers/WebPush/Create/preview/assets/iOS.svg +26 -0
- package/v2Containers/WebPush/Create/preview/assets/macos-arrow-down-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/macos-triple-dot-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +18 -0
- package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +29 -0
- package/v2Containers/WebPush/Create/preview/assets/windows-close-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/windows-triple-dot-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +47 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +141 -0
- package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +45 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +68 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +61 -0
- package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +99 -0
- package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +733 -0
- package/v2Containers/WebPush/Create/preview/components/tests/WindowsChromeExpanded.test.js +571 -0
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +81 -0
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/WindowsChromeExpanded.test.js.snap +81 -0
- package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +50 -0
- package/v2Containers/WebPush/Create/preview/constants.js +637 -0
- package/v2Containers/WebPush/Create/preview/notification-container.scss +79 -0
- package/v2Containers/WebPush/Create/preview/preview.scss +351 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +370 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +47 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_base.scss +207 -0
- package/v2Containers/WebPush/Create/preview/styles/_ios.scss +153 -0
- package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +107 -0
- package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +101 -0
- package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +229 -0
- package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +909 -0
- package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +1081 -0
- package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +723 -0
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +943 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +131 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +112 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +144 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +129 -0
- package/v2Containers/WebPush/Create/utils/payloadBuilder.js +94 -0
- package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +390 -0
- package/v2Containers/WebPush/Create/utils/previewUtils.js +89 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.js +115 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.test.js +449 -0
- package/v2Containers/WebPush/Create/utils/validation.js +75 -0
- package/v2Containers/WebPush/Create/utils/validation.test.js +283 -0
- package/v2Containers/WebPush/actions.js +60 -0
- package/v2Containers/WebPush/constants.js +128 -0
- package/v2Containers/WebPush/index.js +2 -0
- package/v2Containers/WebPush/reducer.js +104 -0
- package/v2Containers/WebPush/sagas.js +119 -0
- package/v2Containers/WebPush/selectors.js +65 -0
- package/v2Containers/WebPush/tests/reducer.test.js +863 -0
- package/v2Containers/WebPush/tests/sagas.test.js +566 -0
- package/v2Containers/WebPush/tests/selectors.test.js +843 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +528 -431
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -254
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -362
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
- package/v2Containers/BeePopupEditor/constants.js +0 -10
- package/v2Containers/BeePopupEditor/index.js +0 -193
- package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1045
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
- package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
- package/v2Containers/InApp/tests/selectors.test.js +0 -612
- package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -162
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -9
- package/v2Containers/InAppWrapper/constants.js +0 -16
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
- package/v2Containers/InAppWrapper/index.js +0 -148
- package/v2Containers/InAppWrapper/messages.js +0 -49
- package/v2Containers/InappAdvance/index.js +0 -1099
- package/v2Containers/InappAdvance/index.scss +0 -10
- package/v2Containers/InappAdvance/tests/index.test.js +0 -448
|
@@ -0,0 +1,365 @@
|
|
|
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 } 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, UPLOAD_STATUS } from './constants';
|
|
19
|
+
import { fetchImageFromUrl, uploadImageFromUrlHelper } from '../../utils/imageUrlUpload';
|
|
20
|
+
import './index.scss';
|
|
21
|
+
|
|
22
|
+
function CapImageUrlUpload(props) {
|
|
23
|
+
const {
|
|
24
|
+
intl,
|
|
25
|
+
uploadAsset,
|
|
26
|
+
imgSize = DEFAULT_MAX_SIZE,
|
|
27
|
+
allowedContentTypes = DEFAULT_ALLOWED_CONTENT_TYPES,
|
|
28
|
+
recommendedDimensions = [],
|
|
29
|
+
sizeLabel = '',
|
|
30
|
+
formatLabel = '',
|
|
31
|
+
imageUrl = '',
|
|
32
|
+
imageSrc = '', // Secure file path from parent after upload completes
|
|
33
|
+
onUrlChange,
|
|
34
|
+
onValidationStateChange, // Callback to notify parent of validation state
|
|
35
|
+
onUploadStateChange, // Callback to notify parent of upload state
|
|
36
|
+
isExternalUploading = false, // Upload state from parent (e.g., redux)
|
|
37
|
+
className = '',
|
|
38
|
+
placeholder,
|
|
39
|
+
disabled = false,
|
|
40
|
+
fileNamePrefix = 'image',
|
|
41
|
+
} = props;
|
|
42
|
+
|
|
43
|
+
const { formatMessage } = intl ?? {};
|
|
44
|
+
const { CapHeadingSpan } = CapHeading;
|
|
45
|
+
|
|
46
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
47
|
+
const [isInternalUploading, setIsInternalUploading] = useState(false);
|
|
48
|
+
const [error, setError] = useState('');
|
|
49
|
+
// State machine for upload lifecycle
|
|
50
|
+
const [uploadStatus, setUploadStatus] = useState(UPLOAD_STATUS.IDLE);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate image URL
|
|
54
|
+
* Checks URL format, fetches as Blob, validates Content-Type, file size, and image validity
|
|
55
|
+
*/
|
|
56
|
+
const validateImageUrl = useCallback(async (url) => {
|
|
57
|
+
const trimmedUrl = url?.trim() || '';
|
|
58
|
+
|
|
59
|
+
if (!trimmedUrl) {
|
|
60
|
+
return { isValid: false, error: '' };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setIsValidating(true);
|
|
64
|
+
setError('');
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Validate URL format
|
|
68
|
+
let urlObj;
|
|
69
|
+
try {
|
|
70
|
+
urlObj = new URL(trimmedUrl);
|
|
71
|
+
if (!['http:', 'https:'].includes(urlObj.protocol)) {
|
|
72
|
+
setIsValidating(false);
|
|
73
|
+
return {
|
|
74
|
+
isValid: false,
|
|
75
|
+
error: formatMessage(messages.imageUrlInvalid),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
} catch (urlError) {
|
|
79
|
+
setIsValidating(false);
|
|
80
|
+
return {
|
|
81
|
+
isValid: false,
|
|
82
|
+
error: formatMessage(messages.imageUrlInvalid),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Fetch image as Blob to check file size
|
|
87
|
+
let response;
|
|
88
|
+
try {
|
|
89
|
+
response = await fetchImageFromUrl(trimmedUrl);
|
|
90
|
+
} catch (fetchError) {
|
|
91
|
+
setIsValidating(false);
|
|
92
|
+
return {
|
|
93
|
+
isValid: false,
|
|
94
|
+
error: formatMessage(messages.imageLoadError),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Validate Content-Type
|
|
99
|
+
const contentType = response.headers?.get('Content-Type') || '';
|
|
100
|
+
const normalizedContentType = contentType.split(';')[0].toLowerCase().trim();
|
|
101
|
+
|
|
102
|
+
if (!allowedContentTypes.includes(normalizedContentType)) {
|
|
103
|
+
setIsValidating(false);
|
|
104
|
+
return {
|
|
105
|
+
isValid: false,
|
|
106
|
+
error: formatMessage(messages.imageTypeInvalid),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Validate file size
|
|
111
|
+
const blob = await response.blob();
|
|
112
|
+
if (blob.size > imgSize) {
|
|
113
|
+
setIsValidating(false);
|
|
114
|
+
return {
|
|
115
|
+
isValid: false,
|
|
116
|
+
error: formatMessage(messages.imageSizeInvalid),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Verify image validity by loading in Image object
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
const img = new Image();
|
|
123
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
124
|
+
|
|
125
|
+
img.onload = () => {
|
|
126
|
+
URL.revokeObjectURL(objectUrl);
|
|
127
|
+
setIsValidating(false);
|
|
128
|
+
resolve({
|
|
129
|
+
isValid: true,
|
|
130
|
+
error: '',
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
img.onerror = () => {
|
|
135
|
+
URL.revokeObjectURL(objectUrl);
|
|
136
|
+
setIsValidating(false);
|
|
137
|
+
resolve({
|
|
138
|
+
isValid: false,
|
|
139
|
+
error: formatMessage(messages.imageLoadError),
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
img.src = objectUrl;
|
|
144
|
+
});
|
|
145
|
+
} catch (error) {
|
|
146
|
+
setIsValidating(false);
|
|
147
|
+
return {
|
|
148
|
+
isValid: false,
|
|
149
|
+
error: formatMessage(messages.imageLoadError),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}, [formatMessage, allowedContentTypes, imgSize]);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Upload image from URL to media gallery
|
|
156
|
+
* Fetches image, converts to File, uploads via uploadAsset
|
|
157
|
+
*/
|
|
158
|
+
const uploadImageFromUrl = useCallback(async (url) => {
|
|
159
|
+
setIsInternalUploading(true);
|
|
160
|
+
setUploadStatus(UPLOAD_STATUS.UPLOADING);
|
|
161
|
+
|
|
162
|
+
// Notify parent that upload is starting
|
|
163
|
+
if (onUploadStateChange) {
|
|
164
|
+
onUploadStateChange(true);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = await uploadImageFromUrlHelper(
|
|
168
|
+
url,
|
|
169
|
+
formatMessage,
|
|
170
|
+
messages,
|
|
171
|
+
uploadAsset,
|
|
172
|
+
fileNamePrefix,
|
|
173
|
+
imgSize,
|
|
174
|
+
allowedContentTypes,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
setIsInternalUploading(false);
|
|
178
|
+
|
|
179
|
+
// Check if imageSrc is already available (handles React batching/timing issues)
|
|
180
|
+
if (result.success && imageSrc && imageSrc !== '') {
|
|
181
|
+
// Upload already complete - imageSrc was set before we entered waiting state
|
|
182
|
+
setUploadStatus(UPLOAD_STATUS.IDLE);
|
|
183
|
+
if (onUploadStateChange) {
|
|
184
|
+
onUploadStateChange(false);
|
|
185
|
+
}
|
|
186
|
+
} else if (result.success) {
|
|
187
|
+
// Transition to waiting state - we've triggered the upload, now wait for imageSrc
|
|
188
|
+
setUploadStatus(UPLOAD_STATUS.WAITING);
|
|
189
|
+
} else {
|
|
190
|
+
// Upload failed
|
|
191
|
+
setUploadStatus(UPLOAD_STATUS.IDLE);
|
|
192
|
+
if (onUploadStateChange) {
|
|
193
|
+
onUploadStateChange(false);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
}, [formatMessage, uploadAsset, fileNamePrefix, imgSize, allowedContentTypes, onUploadStateChange, imageSrc]);
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Handle image URL change
|
|
202
|
+
* Validates URL and triggers upload on success
|
|
203
|
+
*/
|
|
204
|
+
const handleImageUrlChange = useCallback(async (e) => {
|
|
205
|
+
const url = e.target.value;
|
|
206
|
+
const trimmedUrl = url?.trim() || '';
|
|
207
|
+
|
|
208
|
+
// Reset upload status when URL changes
|
|
209
|
+
setUploadStatus(UPLOAD_STATUS.IDLE);
|
|
210
|
+
|
|
211
|
+
// Call parent's onUrlChange if provided
|
|
212
|
+
if (onUrlChange) {
|
|
213
|
+
onUrlChange(url);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (trimmedUrl !== '') {
|
|
217
|
+
// Notify parent that validation is starting
|
|
218
|
+
if (onValidationStateChange) {
|
|
219
|
+
onValidationStateChange(true);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const validation = await validateImageUrl(trimmedUrl);
|
|
223
|
+
|
|
224
|
+
// Notify parent that validation is complete
|
|
225
|
+
if (onValidationStateChange) {
|
|
226
|
+
onValidationStateChange(false);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!validation.isValid) {
|
|
230
|
+
setError(validation.error);
|
|
231
|
+
if (onUploadStateChange) {
|
|
232
|
+
onUploadStateChange(false);
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
setError('');
|
|
236
|
+
// Upload image from URL when validation succeeds
|
|
237
|
+
const uploadResult = await uploadImageFromUrl(trimmedUrl);
|
|
238
|
+
if (!uploadResult.success) {
|
|
239
|
+
setError(uploadResult.error);
|
|
240
|
+
if (onUploadStateChange) {
|
|
241
|
+
onUploadStateChange(false);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
setError('');
|
|
247
|
+
if (onValidationStateChange) {
|
|
248
|
+
onValidationStateChange(false);
|
|
249
|
+
}
|
|
250
|
+
if (onUploadStateChange) {
|
|
251
|
+
onUploadStateChange(false);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}, [validateImageUrl, uploadImageFromUrl, onUrlChange, onValidationStateChange, onUploadStateChange]);
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Monitor upload status and imageSrc to detect when upload is complete
|
|
258
|
+
* Handles React batching and timing issues by checking state transitions
|
|
259
|
+
*/
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
if ((uploadStatus === UPLOAD_STATUS.UPLOADING || uploadStatus === UPLOAD_STATUS.WAITING) && imageSrc && imageSrc !== '') {
|
|
262
|
+
// Upload is complete - we have the secure file path
|
|
263
|
+
setUploadStatus(UPLOAD_STATUS.IDLE);
|
|
264
|
+
|
|
265
|
+
// Notify parent that upload is complete
|
|
266
|
+
if (onUploadStateChange) {
|
|
267
|
+
onUploadStateChange(false);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}, [uploadStatus, imageSrc, onUploadStateChange]);
|
|
271
|
+
|
|
272
|
+
// Determine if we should show "uploading" state
|
|
273
|
+
// Show uploading when:
|
|
274
|
+
// 1. Internal upload is in progress, OR
|
|
275
|
+
// 2. External upload is in progress (from parent's redux), OR
|
|
276
|
+
// 3. State machine indicates we're uploading or waiting for completion
|
|
277
|
+
const isActuallyUploading = isInternalUploading || isExternalUploading || uploadStatus !== UPLOAD_STATUS.IDLE;
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div className={`cap-image-url-upload ${className}`}>
|
|
281
|
+
<CapInput
|
|
282
|
+
className="image-url-input"
|
|
283
|
+
placeholder={placeholder || formatMessage(messages.imageUrlPlaceholder)}
|
|
284
|
+
value={imageUrl}
|
|
285
|
+
onChange={handleImageUrlChange}
|
|
286
|
+
size="default"
|
|
287
|
+
errorMessage={
|
|
288
|
+
error && (
|
|
289
|
+
<CapError className="image-url-error">
|
|
290
|
+
{error}
|
|
291
|
+
</CapError>
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
disabled={disabled || isValidating || isActuallyUploading}
|
|
295
|
+
/>
|
|
296
|
+
|
|
297
|
+
{isValidating && (
|
|
298
|
+
<CapLabel type="label2" className="validation-label">
|
|
299
|
+
<FormattedMessage {...messages.validatingUrl} />
|
|
300
|
+
</CapLabel>
|
|
301
|
+
)}
|
|
302
|
+
|
|
303
|
+
{isActuallyUploading && !isValidating && (
|
|
304
|
+
<CapLabel type="label2" className="uploading-label">
|
|
305
|
+
<FormattedMessage {...messages.uploadingImage} />
|
|
306
|
+
</CapLabel>
|
|
307
|
+
)}
|
|
308
|
+
|
|
309
|
+
<div className="webpush-image-specs">
|
|
310
|
+
{recommendedDimensions?.length > 0 && (
|
|
311
|
+
<CapHeadingSpan type="label2" className="image-dimension">
|
|
312
|
+
<FormattedMessage
|
|
313
|
+
{...messages.recommendedDimensions}
|
|
314
|
+
values={{
|
|
315
|
+
dimensions: recommendedDimensions
|
|
316
|
+
.map((dim) => `${dim.width} x ${dim.height}px`)
|
|
317
|
+
.join(', '),
|
|
318
|
+
}}
|
|
319
|
+
/>
|
|
320
|
+
</CapHeadingSpan>
|
|
321
|
+
)}
|
|
322
|
+
|
|
323
|
+
{sizeLabel && (
|
|
324
|
+
<CapHeadingSpan type="label2" className="image-size">
|
|
325
|
+
{sizeLabel}
|
|
326
|
+
</CapHeadingSpan>
|
|
327
|
+
)}
|
|
328
|
+
|
|
329
|
+
{formatLabel && (
|
|
330
|
+
<CapHeadingSpan type="label2" className="image-format">
|
|
331
|
+
{formatLabel}
|
|
332
|
+
</CapHeadingSpan>
|
|
333
|
+
)}
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
CapImageUrlUpload.propTypes = {
|
|
340
|
+
intl: intlShape.isRequired,
|
|
341
|
+
uploadAsset: PropTypes.func.isRequired,
|
|
342
|
+
imgSize: PropTypes.number,
|
|
343
|
+
allowedContentTypes: PropTypes.arrayOf(PropTypes.string),
|
|
344
|
+
recommendedDimensions: PropTypes.arrayOf(
|
|
345
|
+
PropTypes.shape({
|
|
346
|
+
width: PropTypes.number.isRequired,
|
|
347
|
+
height: PropTypes.number.isRequired,
|
|
348
|
+
})
|
|
349
|
+
),
|
|
350
|
+
sizeLabel: PropTypes.string,
|
|
351
|
+
formatLabel: PropTypes.string,
|
|
352
|
+
imageUrl: PropTypes.string,
|
|
353
|
+
imageSrc: PropTypes.string, // Secure file path from parent after upload completes
|
|
354
|
+
onUrlChange: PropTypes.func,
|
|
355
|
+
onValidationStateChange: PropTypes.func, // Callback(isValidating: bool)
|
|
356
|
+
onUploadStateChange: PropTypes.func, // Callback(isUploading: bool)
|
|
357
|
+
isExternalUploading: PropTypes.bool, // Upload state from parent (e.g., redux)
|
|
358
|
+
className: PropTypes.string,
|
|
359
|
+
placeholder: PropTypes.string,
|
|
360
|
+
disabled: PropTypes.bool,
|
|
361
|
+
fileNamePrefix: PropTypes.string,
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
export default injectIntl(CapImageUrlUpload);
|
|
365
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
|
|
@@ -227,10 +227,6 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
227
227
|
|
|
228
228
|
togglePopoverVisibility = (visible) => {
|
|
229
229
|
this.setState({visible});
|
|
230
|
-
// Call onVisibleChange callback if provided (for triggering API calls when popover opens)
|
|
231
|
-
if (this.props.onVisibleChange) {
|
|
232
|
-
this.props.onVisibleChange(visible);
|
|
233
|
-
}
|
|
234
230
|
};
|
|
235
231
|
|
|
236
232
|
renderDynamicTagFlow = () => {
|
|
@@ -472,7 +468,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
472
468
|
onVisibleChange={this.togglePopoverVisibility}
|
|
473
469
|
content={contentSection}
|
|
474
470
|
trigger="click"
|
|
475
|
-
placement={
|
|
471
|
+
placement={channel === EMAIL.toUpperCase() ? "leftTop" : "rightTop"}
|
|
476
472
|
>
|
|
477
473
|
<CapTooltip
|
|
478
474
|
title={
|
|
@@ -539,7 +535,6 @@ CapTagList.propTypes = {
|
|
|
539
535
|
channel: PropTypes.string,
|
|
540
536
|
disabled: PropTypes.bool,
|
|
541
537
|
fetchingSchemaError: PropTypes.bool,
|
|
542
|
-
popoverPlacement: PropTypes.string,
|
|
543
538
|
};
|
|
544
539
|
|
|
545
540
|
CapTagList.defaultValue = {
|
|
@@ -48,14 +48,13 @@ export const CapTagListWithInput = (props) => {
|
|
|
48
48
|
showTagList = true,
|
|
49
49
|
showInput = true,
|
|
50
50
|
inputProps = {},
|
|
51
|
-
popoverPlacement,
|
|
52
51
|
} = props;
|
|
53
52
|
|
|
54
53
|
const { formatMessage } = intl;
|
|
55
54
|
|
|
56
55
|
return (
|
|
57
56
|
<CapColumn style={containerStyle}>
|
|
58
|
-
<CapRow
|
|
57
|
+
<CapRow style={{display: 'flex', flexDirection: 'row'}}>
|
|
59
58
|
{showHeading && headingText && (
|
|
60
59
|
<CapHeading type={headingType} style={headingStyle}>
|
|
61
60
|
{headingText}
|
|
@@ -77,7 +76,6 @@ export const CapTagListWithInput = (props) => {
|
|
|
77
76
|
selectedOfferDetails={selectedOfferDetails}
|
|
78
77
|
eventContextTags={eventContextTags}
|
|
79
78
|
style={tagListStyle}
|
|
80
|
-
popoverPlacement={popoverPlacement}
|
|
81
79
|
/>
|
|
82
80
|
)}
|
|
83
81
|
</CapRow>
|
|
@@ -138,7 +136,6 @@ CapTagListWithInput.propTypes = {
|
|
|
138
136
|
showHeading: PropTypes.bool,
|
|
139
137
|
showTagList: PropTypes.bool,
|
|
140
138
|
showInput: PropTypes.bool,
|
|
141
|
-
popoverPlacement: PropTypes.string,
|
|
142
139
|
};
|
|
143
140
|
|
|
144
141
|
CapTagListWithInput.defaultProps = {
|
|
@@ -167,7 +164,6 @@ CapTagListWithInput.defaultProps = {
|
|
|
167
164
|
showTagList: true,
|
|
168
165
|
showInput: true,
|
|
169
166
|
inputProps: {},
|
|
170
|
-
popoverPlacement: undefined,
|
|
171
167
|
};
|
|
172
168
|
|
|
173
169
|
export default injectIntl(CapTagListWithInput);
|
|
@@ -4,11 +4,6 @@ import '@testing-library/jest-dom';
|
|
|
4
4
|
import { render, screen, fireEvent } from '../../../utils/test-utils';
|
|
5
5
|
import { CapWhatsappCTA } from '../index';
|
|
6
6
|
|
|
7
|
-
// Mock the missing reducer
|
|
8
|
-
jest.mock('@capillarytech/cap-ui-library/CapCollapsibleLeftNavigation/reducer', () => {
|
|
9
|
-
return (state = {}) => state;
|
|
10
|
-
}, { virtual: true });
|
|
11
|
-
|
|
12
7
|
const updateHandler = jest.fn();
|
|
13
8
|
const deleteHandler = jest.fn();
|
|
14
9
|
const initializeComponent = (
|