@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.
Files changed (255) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/config/app.js +1 -1
  4. package/constants/unified.js +2 -2
  5. package/initialReducer.js +0 -2
  6. package/package.json +1 -1
  7. package/services/api.js +5 -10
  8. package/services/tests/api.test.js +0 -18
  9. package/translations/en.json +4 -3
  10. package/utils/common.js +6 -5
  11. package/utils/commonUtils.js +1 -14
  12. package/utils/imageUrlUpload.js +141 -0
  13. package/utils/tests/commonUtil.test.js +0 -224
  14. package/utils/transformTemplateConfig.js +10 -0
  15. package/v2Components/CapDeviceContent/index.js +56 -61
  16. package/v2Components/CapImageUpload/constants.js +2 -0
  17. package/v2Components/CapImageUpload/index.js +65 -16
  18. package/v2Components/CapImageUpload/index.scss +4 -1
  19. package/v2Components/CapImageUpload/messages.js +5 -1
  20. package/v2Components/CapImageUrlUpload/constants.js +26 -0
  21. package/v2Components/CapImageUrlUpload/index.js +365 -0
  22. package/v2Components/CapImageUrlUpload/index.scss +35 -0
  23. package/v2Components/CapImageUrlUpload/messages.js +47 -0
  24. package/v2Components/CapTagList/index.js +1 -6
  25. package/v2Components/CapTagListWithInput/index.js +1 -5
  26. package/v2Components/CapTagListWithInput/messages.js +1 -1
  27. package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
  28. package/v2Components/ErrorInfoNote/index.js +72 -412
  29. package/v2Components/ErrorInfoNote/messages.js +0 -22
  30. package/v2Components/ErrorInfoNote/style.scss +2 -279
  31. package/v2Components/HtmlEditor/HTMLEditor.js +89 -210
  32. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1132
  33. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +12 -17
  34. package/v2Components/HtmlEditor/_htmlEditor.scss +23 -8
  35. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  36. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +101 -13
  37. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +139 -148
  38. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
  39. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  40. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  41. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -1
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +7 -4
  43. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +45 -35
  44. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +3 -1
  45. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  46. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +6 -7
  47. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +6 -3
  48. package/v2Components/HtmlEditor/components/PreviewPane/index.js +11 -10
  49. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  50. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +62 -87
  51. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -49
  52. package/v2Components/HtmlEditor/constants.js +20 -29
  53. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
  54. package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
  55. package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
  56. package/v2Components/HtmlEditor/index.js +1 -1
  57. package/v2Components/HtmlEditor/messages.js +85 -95
  58. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +101 -99
  59. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +25 -23
  60. package/v2Components/HtmlEditor/utils/validationAdapter.js +41 -34
  61. package/v2Components/MobilePushPreviewV2/index.js +7 -32
  62. package/v2Components/TemplatePreview/_templatePreview.scss +24 -44
  63. package/v2Components/TemplatePreview/index.js +32 -47
  64. package/v2Components/TemplatePreview/messages.js +0 -4
  65. package/v2Components/TestAndPreviewSlidebox/index.js +25 -31
  66. package/v2Containers/App/constants.js +5 -0
  67. package/v2Containers/BeeEditor/index.js +80 -82
  68. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +4 -3
  69. package/v2Containers/CreativesContainer/SlideBoxContent.js +118 -148
  70. package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -9
  71. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -2
  72. package/v2Containers/CreativesContainer/constants.js +2 -1
  73. package/v2Containers/CreativesContainer/index.js +41 -173
  74. package/v2Containers/CreativesContainer/messages.js +4 -4
  75. package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +210 -0
  76. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +354 -38
  77. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -36
  78. package/v2Containers/Email/actions.js +0 -7
  79. package/v2Containers/Email/constants.js +1 -5
  80. package/v2Containers/Email/index.js +0 -13
  81. package/v2Containers/Email/messages.js +0 -32
  82. package/v2Containers/Email/reducer.js +1 -12
  83. package/v2Containers/Email/sagas.js +6 -41
  84. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -2
  85. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +7 -193
  86. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
  87. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
  88. package/v2Containers/EmailWrapper/constants.js +0 -2
  89. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +67 -436
  90. package/v2Containers/EmailWrapper/index.js +23 -99
  91. package/v2Containers/EmailWrapper/messages.js +1 -61
  92. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +1 -26
  93. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -111
  94. package/v2Containers/InApp/actions.js +0 -7
  95. package/v2Containers/InApp/constants.js +4 -20
  96. package/v2Containers/InApp/index.js +357 -800
  97. package/v2Containers/InApp/index.scss +3 -4
  98. package/v2Containers/InApp/messages.js +3 -7
  99. package/v2Containers/InApp/reducer.js +3 -21
  100. package/v2Containers/InApp/sagas.js +9 -29
  101. package/v2Containers/InApp/selectors.js +5 -25
  102. package/v2Containers/InApp/tests/index.test.js +50 -154
  103. package/v2Containers/InApp/tests/reducer.test.js +0 -34
  104. package/v2Containers/InApp/tests/sagas.test.js +9 -61
  105. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +12 -12
  106. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +8 -8
  107. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +100 -77
  108. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +72 -63
  109. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +184 -150
  110. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +16 -12
  111. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +32 -28
  112. package/v2Containers/TagList/index.js +1 -67
  113. package/v2Containers/Templates/ChannelTypeIllustration.js +13 -1
  114. package/v2Containers/Templates/_templates.scss +202 -56
  115. package/v2Containers/Templates/actions.js +2 -1
  116. package/v2Containers/Templates/constants.js +1 -0
  117. package/v2Containers/Templates/index.js +278 -128
  118. package/v2Containers/Templates/messages.js +24 -4
  119. package/v2Containers/Templates/reducer.js +2 -0
  120. package/v2Containers/Templates/tests/index.test.js +10 -0
  121. package/v2Containers/TemplatesV2/index.js +8 -1
  122. package/v2Containers/TemplatesV2/messages.js +4 -0
  123. package/v2Containers/WebPush/Create/components/BrandIconSection.js +108 -0
  124. package/v2Containers/WebPush/Create/components/ButtonForm.js +172 -0
  125. package/v2Containers/WebPush/Create/components/ButtonItem.js +101 -0
  126. package/v2Containers/WebPush/Create/components/ButtonList.js +145 -0
  127. package/v2Containers/WebPush/Create/components/ButtonsLinksSection.js +164 -0
  128. package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +463 -0
  129. package/v2Containers/WebPush/Create/components/FormActions.js +54 -0
  130. package/v2Containers/WebPush/Create/components/FormActions.test.js +163 -0
  131. package/v2Containers/WebPush/Create/components/MediaSection.js +142 -0
  132. package/v2Containers/WebPush/Create/components/MediaSection.test.js +341 -0
  133. package/v2Containers/WebPush/Create/components/MessageSection.js +103 -0
  134. package/v2Containers/WebPush/Create/components/MessageSection.test.js +268 -0
  135. package/v2Containers/WebPush/Create/components/NotificationTitleSection.js +87 -0
  136. package/v2Containers/WebPush/Create/components/NotificationTitleSection.test.js +210 -0
  137. package/v2Containers/WebPush/Create/components/TemplateNameSection.js +54 -0
  138. package/v2Containers/WebPush/Create/components/TemplateNameSection.test.js +143 -0
  139. package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +86 -0
  140. package/v2Containers/WebPush/Create/components/__snapshots__/FormActions.test.js.snap +16 -0
  141. package/v2Containers/WebPush/Create/components/__snapshots__/MediaSection.test.js.snap +41 -0
  142. package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +54 -0
  143. package/v2Containers/WebPush/Create/components/__snapshots__/NotificationTitleSection.test.js.snap +37 -0
  144. package/v2Containers/WebPush/Create/components/__snapshots__/TemplateNameSection.test.js.snap +21 -0
  145. package/v2Containers/WebPush/Create/components/_buttons.scss +246 -0
  146. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +554 -0
  147. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +607 -0
  148. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +633 -0
  149. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +666 -0
  150. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +74 -0
  151. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +78 -0
  152. package/v2Containers/WebPush/Create/hooks/useButtonManagement.js +138 -0
  153. package/v2Containers/WebPush/Create/hooks/useButtonManagement.test.js +406 -0
  154. package/v2Containers/WebPush/Create/hooks/useCharacterCount.js +30 -0
  155. package/v2Containers/WebPush/Create/hooks/useCharacterCount.test.js +151 -0
  156. package/v2Containers/WebPush/Create/hooks/useImageUpload.js +104 -0
  157. package/v2Containers/WebPush/Create/hooks/useImageUpload.test.js +538 -0
  158. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +122 -0
  159. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +633 -0
  160. package/v2Containers/WebPush/Create/index.js +1056 -0
  161. package/v2Containers/WebPush/Create/index.scss +134 -0
  162. package/v2Containers/WebPush/Create/messages.js +203 -0
  163. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +228 -0
  164. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +294 -0
  165. package/v2Containers/WebPush/Create/preview/PreviewContent.js +90 -0
  166. package/v2Containers/WebPush/Create/preview/PreviewControls.js +305 -0
  167. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +23 -0
  168. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +150 -0
  169. package/v2Containers/WebPush/Create/preview/assets/Light.svg +53 -0
  170. package/v2Containers/WebPush/Create/preview/assets/Top.svg +5 -0
  171. package/v2Containers/WebPush/Create/preview/assets/android-arrow-down.svg +9 -0
  172. package/v2Containers/WebPush/Create/preview/assets/android-arrow-up.svg +9 -0
  173. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  174. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  175. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +106 -0
  176. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +26 -0
  177. package/v2Containers/WebPush/Create/preview/assets/macos-arrow-down-icon.svg +9 -0
  178. package/v2Containers/WebPush/Create/preview/assets/macos-triple-dot-icon.svg +9 -0
  179. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +18 -0
  180. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +29 -0
  181. package/v2Containers/WebPush/Create/preview/assets/windows-close-icon.svg +9 -0
  182. package/v2Containers/WebPush/Create/preview/assets/windows-triple-dot-icon.svg +9 -0
  183. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +47 -0
  184. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +141 -0
  185. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +45 -0
  186. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +68 -0
  187. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +61 -0
  188. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +99 -0
  189. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +733 -0
  190. package/v2Containers/WebPush/Create/preview/components/tests/WindowsChromeExpanded.test.js +571 -0
  191. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +81 -0
  192. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/WindowsChromeExpanded.test.js.snap +81 -0
  193. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +50 -0
  194. package/v2Containers/WebPush/Create/preview/constants.js +637 -0
  195. package/v2Containers/WebPush/Create/preview/notification-container.scss +79 -0
  196. package/v2Containers/WebPush/Create/preview/preview.scss +351 -0
  197. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +370 -0
  198. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +12 -0
  199. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +12 -0
  200. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +12 -0
  201. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +47 -0
  202. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +11 -0
  203. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +11 -0
  204. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +11 -0
  205. package/v2Containers/WebPush/Create/preview/styles/_base.scss +207 -0
  206. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +153 -0
  207. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +107 -0
  208. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +101 -0
  209. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +229 -0
  210. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +909 -0
  211. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +1081 -0
  212. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +723 -0
  213. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +943 -0
  214. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +131 -0
  215. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +112 -0
  216. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +144 -0
  217. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +129 -0
  218. package/v2Containers/WebPush/Create/utils/payloadBuilder.js +94 -0
  219. package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +390 -0
  220. package/v2Containers/WebPush/Create/utils/previewUtils.js +89 -0
  221. package/v2Containers/WebPush/Create/utils/urlValidation.js +115 -0
  222. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +449 -0
  223. package/v2Containers/WebPush/Create/utils/validation.js +75 -0
  224. package/v2Containers/WebPush/Create/utils/validation.test.js +283 -0
  225. package/v2Containers/WebPush/actions.js +60 -0
  226. package/v2Containers/WebPush/constants.js +128 -0
  227. package/v2Containers/WebPush/index.js +2 -0
  228. package/v2Containers/WebPush/reducer.js +104 -0
  229. package/v2Containers/WebPush/sagas.js +119 -0
  230. package/v2Containers/WebPush/selectors.js +65 -0
  231. package/v2Containers/WebPush/tests/reducer.test.js +863 -0
  232. package/v2Containers/WebPush/tests/sagas.test.js +566 -0
  233. package/v2Containers/WebPush/tests/selectors.test.js +843 -0
  234. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +528 -431
  235. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -254
  236. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -362
  237. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
  238. package/v2Containers/BeePopupEditor/constants.js +0 -10
  239. package/v2Containers/BeePopupEditor/index.js +0 -193
  240. package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
  241. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1045
  242. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
  243. package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
  244. package/v2Containers/InApp/tests/selectors.test.js +0 -612
  245. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -162
  246. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
  247. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -9
  248. package/v2Containers/InAppWrapper/constants.js +0 -16
  249. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
  250. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
  251. package/v2Containers/InAppWrapper/index.js +0 -148
  252. package/v2Containers/InAppWrapper/messages.js +0 -49
  253. package/v2Containers/InappAdvance/index.js +0 -1099
  254. package/v2Containers/InappAdvance/index.scss +0 -10
  255. 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={this.props.popoverPlacement || (channel === EMAIL.toUpperCase() ? "leftTop" : "rightTop")}
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 align="middle" type="flex">
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);
@@ -5,6 +5,6 @@ const prefix = 'creatives.componentsV2.CapTagListWithInput';
5
5
  export default defineMessages({
6
6
  addLabels: {
7
7
  id: `${prefix}.addLabels`,
8
- defaultMessage: 'Add label',
8
+ defaultMessage: 'Add labels',
9
9
  },
10
10
  });
@@ -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 = (