@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.
Files changed (216) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +2 -1
  4. package/initialReducer.js +2 -0
  5. package/package.json +1 -1
  6. package/sagas/__tests__/assetPolling.test.js +74 -3
  7. package/sagas/assetPolling.js +8 -1
  8. package/services/api.js +10 -5
  9. package/services/tests/api.test.js +18 -0
  10. package/translations/en.json +0 -1
  11. package/utils/common.js +5 -0
  12. package/utils/commonUtils.js +14 -1
  13. package/utils/tests/commonUtil.test.js +224 -0
  14. package/utils/transformTemplateConfig.js +0 -10
  15. package/utils/transformerUtils.js +0 -42
  16. package/v2Components/CapDeviceContent/index.js +61 -56
  17. package/v2Components/CapImageUpload/constants.js +0 -2
  18. package/v2Components/CapImageUpload/index.js +14 -54
  19. package/v2Components/CapImageUpload/index.scss +1 -4
  20. package/v2Components/CapImageUpload/messages.js +0 -4
  21. package/v2Components/CapTagList/index.js +6 -1
  22. package/v2Components/CapTagListWithInput/index.js +5 -1
  23. package/v2Components/CapTagListWithInput/messages.js +1 -1
  24. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  25. package/v2Components/ErrorInfoNote/index.js +412 -72
  26. package/v2Components/ErrorInfoNote/messages.js +22 -0
  27. package/v2Components/ErrorInfoNote/style.scss +279 -2
  28. package/v2Components/HtmlEditor/HTMLEditor.js +220 -91
  29. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1132 -133
  30. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +17 -12
  31. package/v2Components/HtmlEditor/_htmlEditor.scss +107 -45
  32. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  33. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +13 -101
  34. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -139
  35. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  36. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  37. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
  38. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  39. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  40. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  41. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  43. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  44. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  45. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
  46. package/v2Components/HtmlEditor/components/PreviewPane/index.js +10 -11
  47. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  48. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +70 -72
  49. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
  50. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
  51. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +362 -0
  52. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  53. package/v2Components/HtmlEditor/constants.js +29 -20
  54. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  55. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  56. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  57. package/v2Components/HtmlEditor/index.js +1 -1
  58. package/v2Components/HtmlEditor/messages.js +95 -85
  59. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +99 -101
  60. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  61. package/v2Components/HtmlEditor/utils/validationAdapter.js +34 -41
  62. package/v2Components/MobilePushPreviewV2/index.js +32 -7
  63. package/v2Components/TemplatePreview/_templatePreview.scss +44 -24
  64. package/v2Components/TemplatePreview/index.js +47 -32
  65. package/v2Components/TemplatePreview/messages.js +4 -0
  66. package/v2Components/TestAndPreviewSlidebox/index.js +31 -25
  67. package/v2Containers/App/constants.js +0 -5
  68. package/v2Containers/BeeEditor/index.js +82 -80
  69. package/v2Containers/BeePopupEditor/constants.js +10 -0
  70. package/v2Containers/BeePopupEditor/index.js +193 -0
  71. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  72. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +0 -1
  73. package/v2Containers/CreativesContainer/SlideBoxContent.js +148 -120
  74. package/v2Containers/CreativesContainer/SlideBoxFooter.js +9 -3
  75. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -2
  76. package/v2Containers/CreativesContainer/constants.js +1 -2
  77. package/v2Containers/CreativesContainer/index.js +173 -193
  78. package/v2Containers/CreativesContainer/messages.js +4 -4
  79. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  80. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +36 -0
  81. package/v2Containers/Email/actions.js +7 -0
  82. package/v2Containers/Email/constants.js +5 -1
  83. package/v2Containers/Email/index.js +13 -0
  84. package/v2Containers/Email/messages.js +32 -0
  85. package/v2Containers/Email/reducer.js +12 -1
  86. package/v2Containers/Email/sagas.js +41 -6
  87. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  88. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1046 -0
  89. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +193 -7
  90. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  91. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  92. package/v2Containers/EmailWrapper/constants.js +2 -0
  93. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +436 -67
  94. package/v2Containers/EmailWrapper/index.js +99 -23
  95. package/v2Containers/EmailWrapper/messages.js +61 -1
  96. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +111 -77
  97. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  98. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  99. package/v2Containers/InApp/actions.js +7 -0
  100. package/v2Containers/InApp/constants.js +20 -4
  101. package/v2Containers/InApp/index.js +801 -357
  102. package/v2Containers/InApp/index.scss +4 -3
  103. package/v2Containers/InApp/messages.js +7 -3
  104. package/v2Containers/InApp/reducer.js +21 -3
  105. package/v2Containers/InApp/sagas.js +29 -9
  106. package/v2Containers/InApp/selectors.js +25 -5
  107. package/v2Containers/InApp/tests/index.test.js +154 -50
  108. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  109. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  110. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  111. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +162 -0
  112. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  113. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +9 -0
  114. package/v2Containers/InAppWrapper/constants.js +16 -0
  115. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  116. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  117. package/v2Containers/InAppWrapper/index.js +148 -0
  118. package/v2Containers/InAppWrapper/messages.js +49 -0
  119. package/v2Containers/InappAdvance/index.js +1099 -0
  120. package/v2Containers/InappAdvance/index.scss +10 -0
  121. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  122. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -3
  123. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -2
  124. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -25
  125. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -18
  126. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -46
  127. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +0 -4
  128. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -8
  129. package/v2Containers/TagList/index.js +67 -1
  130. package/v2Containers/Templates/ChannelTypeIllustration.js +1 -13
  131. package/v2Containers/Templates/_templates.scss +56 -200
  132. package/v2Containers/Templates/actions.js +1 -2
  133. package/v2Containers/Templates/constants.js +0 -1
  134. package/v2Containers/Templates/index.js +124 -277
  135. package/v2Containers/Templates/messages.js +4 -24
  136. package/v2Containers/Templates/reducer.js +0 -2
  137. package/v2Containers/Templates/tests/index.test.js +0 -10
  138. package/v2Containers/TemplatesV2/index.js +2 -3
  139. package/v2Containers/TemplatesV2/messages.js +0 -4
  140. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +35 -132
  141. package/v2Components/CapImageUrlUpload/constants.js +0 -19
  142. package/v2Components/CapImageUrlUpload/index.js +0 -455
  143. package/v2Components/CapImageUrlUpload/index.scss +0 -35
  144. package/v2Components/CapImageUrlUpload/messages.js +0 -47
  145. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
  146. package/v2Containers/WebPush/Create/components/ButtonForm.js +0 -175
  147. package/v2Containers/WebPush/Create/components/ButtonItem.js +0 -101
  148. package/v2Containers/WebPush/Create/components/ButtonList.js +0 -144
  149. package/v2Containers/WebPush/Create/components/_buttons.scss +0 -246
  150. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +0 -554
  151. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +0 -607
  152. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +0 -633
  153. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +0 -666
  154. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +0 -74
  155. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +0 -80
  156. package/v2Containers/WebPush/Create/index.js +0 -1755
  157. package/v2Containers/WebPush/Create/index.scss +0 -123
  158. package/v2Containers/WebPush/Create/messages.js +0 -199
  159. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +0 -241
  160. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +0 -290
  161. package/v2Containers/WebPush/Create/preview/PreviewContent.js +0 -81
  162. package/v2Containers/WebPush/Create/preview/PreviewControls.js +0 -240
  163. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +0 -23
  164. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +0 -144
  165. package/v2Containers/WebPush/Create/preview/assets/Light.svg +0 -53
  166. package/v2Containers/WebPush/Create/preview/assets/Top.svg +0 -5
  167. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  168. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  169. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +0 -106
  170. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +0 -26
  171. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +0 -18
  172. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +0 -29
  173. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +0 -44
  174. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +0 -110
  175. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +0 -45
  176. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +0 -72
  177. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +0 -55
  178. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +0 -70
  179. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +0 -512
  180. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +0 -77
  181. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +0 -527
  182. package/v2Containers/WebPush/Create/preview/constants.js +0 -162
  183. package/v2Containers/WebPush/Create/preview/notification-container.scss +0 -104
  184. package/v2Containers/WebPush/Create/preview/preview.scss +0 -409
  185. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +0 -300
  186. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +0 -12
  187. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +0 -12
  188. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +0 -12
  189. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +0 -303
  190. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +0 -11
  191. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +0 -11
  192. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +0 -11
  193. package/v2Containers/WebPush/Create/preview/styles/_base.scss +0 -188
  194. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +0 -106
  195. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +0 -107
  196. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +0 -75
  197. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +0 -174
  198. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +0 -909
  199. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +0 -1077
  200. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +0 -723
  201. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +0 -943
  202. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +0 -128
  203. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +0 -121
  204. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +0 -144
  205. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +0 -127
  206. package/v2Containers/WebPush/Create/utils/urlValidation.js +0 -116
  207. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +0 -449
  208. package/v2Containers/WebPush/actions.js +0 -60
  209. package/v2Containers/WebPush/constants.js +0 -108
  210. package/v2Containers/WebPush/index.js +0 -2
  211. package/v2Containers/WebPush/reducer.js +0 -104
  212. package/v2Containers/WebPush/sagas.js +0 -119
  213. package/v2Containers/WebPush/selectors.js +0 -65
  214. package/v2Containers/WebPush/tests/reducer.test.js +0 -863
  215. package/v2Containers/WebPush/tests/sagas.test.js +0 -566
  216. 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
-