@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,1056 @@
1
+ import React, {
2
+ useState,
3
+ useEffect,
4
+ useRef,
5
+ useCallback,
6
+ useMemo,
7
+ memo,
8
+ } from 'react';
9
+ import PropTypes from 'prop-types';
10
+ import { injectIntl, intlShape } from 'react-intl';
11
+ import { createStructuredSelector, createSelector } from 'reselect';
12
+ import { bindActionCreators } from 'redux';
13
+ import {
14
+ injectReducer,
15
+ injectSaga,
16
+ } from '@capillarytech/vulcan-react-sdk/utils';
17
+ import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
18
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
19
+ import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
20
+ import TagList from '../../TagList';
21
+ import WebPushPreview from './preview/WebPushPreview';
22
+ import TemplateNameSection from './components/TemplateNameSection';
23
+ import NotificationTitleSection from './components/NotificationTitleSection';
24
+ import MessageSection from './components/MessageSection';
25
+ import MediaSection from './components/MediaSection';
26
+ import BrandIconSection from './components/BrandIconSection';
27
+ import ButtonsLinksSection from './components/ButtonsLinksSection';
28
+ import FormActions from './components/FormActions';
29
+ import { useCharacterCount } from './hooks/useCharacterCount';
30
+ import { useButtonManagement } from './hooks/useButtonManagement';
31
+ import { useImageUpload } from './hooks/useImageUpload';
32
+ import { useTagManagement } from './hooks/useTagManagement';
33
+ import isEmpty from 'lodash/isEmpty';
34
+ import get from 'lodash/get';
35
+ import {
36
+ WEBPUSH_MEDIA_TYPES,
37
+ BRAND_ICON_OPTIONS,
38
+ IMAGE_UPLOAD_METHODS,
39
+ UPLOAD_FIELD_TYPES,
40
+ WEBPUSH_BUTTON_TYPES,
41
+ ON_CLICK_BEHAVIOUR_OPTIONS,
42
+ ACTION_TYPES,
43
+ NOTIFICATION_TITLE_MAX_LENGTH,
44
+ MESSAGE_MAX_LENGTH,
45
+ } from '../constants';
46
+ import * as actions from '../actions';
47
+ import {
48
+ makeSelectWebPush,
49
+ makeSelectCreateError,
50
+ makeSelectCreateTemplateInProgress,
51
+ makeSelectEditTemplateInProgress,
52
+ makeSelectEditError,
53
+ } from '../selectors';
54
+ import webPushReducer from '../reducer';
55
+ import webPushSagas from '../sagas';
56
+ import withCreatives from '../../../hoc/withCreatives';
57
+ import messages from './messages';
58
+ import { createWebPushPayload } from './utils/payloadBuilder';
59
+ import {
60
+ validateTemplateName as validateTemplateNameUtil,
61
+ validateTitle as validateTitleUtil,
62
+ validateUrl as validateUrlUtil,
63
+ validateMessageContent as validateMessageContentUtil,
64
+ } from './utils/validation';
65
+ import {
66
+ makeSelectMetaEntities,
67
+ setInjectedTags,
68
+ } from '../../Cap/selectors';
69
+ import './index.scss';
70
+
71
+ // Memoized TagList wrapper components for better performance
72
+ const MemoizedTagList = memo(({
73
+ moduleFilterEnabled,
74
+ label,
75
+ onContextChange,
76
+ location,
77
+ tags,
78
+ injectedTags,
79
+ selectedOfferDetails,
80
+ eventContextTags,
81
+ forwardedTags,
82
+ onTagSelect,
83
+ }) => (
84
+ <TagList
85
+ moduleFilterEnabled={moduleFilterEnabled}
86
+ label={label}
87
+ onContextChange={onContextChange}
88
+ location={location}
89
+ tags={tags}
90
+ injectedTags={injectedTags}
91
+ selectedOfferDetails={selectedOfferDetails}
92
+ eventContextTags={eventContextTags}
93
+ forwardedTags={forwardedTags}
94
+ onTagSelect={onTagSelect}
95
+ />
96
+ ), (prevProps, nextProps) => {
97
+ // Custom comparison function for better memoization
98
+ return (
99
+ prevProps.moduleFilterEnabled === nextProps.moduleFilterEnabled
100
+ && prevProps.label === nextProps.label
101
+ && prevProps.onContextChange === nextProps.onContextChange
102
+ && prevProps.location === nextProps.location
103
+ && prevProps.tags === nextProps.tags
104
+ && prevProps.injectedTags === nextProps.injectedTags
105
+ && prevProps.selectedOfferDetails === nextProps.selectedOfferDetails
106
+ && prevProps.eventContextTags === nextProps.eventContextTags
107
+ && prevProps.forwardedTags === nextProps.forwardedTags
108
+ && prevProps.onTagSelect === nextProps.onTagSelect
109
+ );
110
+ });
111
+
112
+ MemoizedTagList.displayName = 'MemoizedTagList';
113
+
114
+ const WebPushCreate = ({
115
+ isFullMode,
116
+ handleClose,
117
+ intl,
118
+ webPushActions,
119
+ createTemplateInProgress,
120
+ createTemplateError,
121
+ editTemplateInProgress,
122
+ editTemplateError,
123
+ accountData,
124
+ webPush,
125
+ onCreateComplete,
126
+ templateData,
127
+ creativesMode,
128
+ params,
129
+ globalActions,
130
+ location,
131
+ metaEntities,
132
+ injectedTags,
133
+ getDefaultTags,
134
+ supportedTags = [],
135
+ forwardedTags,
136
+ selectedOfferDetails = [],
137
+ eventContextTags = [],
138
+ }) => {
139
+ const { formatMessage } = intl;
140
+
141
+ // Form state - kept in main component for now
142
+ const [templateName, setTemplateName] = useState('');
143
+ const [notificationTitle, setNotificationTitle] = useState('');
144
+ const [message, setMessage] = useState('');
145
+ const [mediaType, setMediaType] = useState(WEBPUSH_MEDIA_TYPES.NONE);
146
+ const [templateNameError, setTemplateNameError] = useState(false);
147
+ const [titleError, setTitleError] = useState('');
148
+ const [messageError, setMessageError] = useState('');
149
+ const [fieldCompletion, setFieldCompletion] = useState({
150
+ templateName: !isFullMode,
151
+ notificationTitle: false,
152
+ message: false,
153
+ });
154
+ const [brandIconOption, setBrandIconOption] = useState(BRAND_ICON_OPTIONS.DONT_SHOW);
155
+ const [onClickBehaviour, setOnClickBehaviour] = useState(ON_CLICK_BEHAVIOUR_OPTIONS.OPEN_SITE);
156
+ const [redirectUrl, setRedirectUrl] = useState('');
157
+ const [redirectUrlError, setRedirectUrlError] = useState('');
158
+ const [activeUploadField, setActiveUploadField] = useState(null);
159
+ const [templateIdError, setTemplateIdError] = useState('');
160
+
161
+ // Refs
162
+ const titleCountRef = useRef(null);
163
+ const messageCountRef = useRef(null);
164
+ const saveInitiatedRef = useRef(false);
165
+ const messageTextAreaRef = useRef(null);
166
+
167
+ // Custom hooks
168
+ const { updateCharacterCount } = useCharacterCount(formatMessage, messages);
169
+
170
+ const buttonState = useButtonManagement([]);
171
+ const { buttons, setButtons } = buttonState;
172
+
173
+ const imageUpload = useImageUpload({
174
+ webPush,
175
+ webPushActions,
176
+ index: 0,
177
+ initialUploadMethod: IMAGE_UPLOAD_METHODS.UPLOAD_IMAGE,
178
+ });
179
+ const {
180
+ imageSrc,
181
+ imageUrl,
182
+ isValidating: isImageValidating,
183
+ isUploading: isImageUploading,
184
+ setImageSrc,
185
+ setImageUrl,
186
+ } = imageUpload;
187
+
188
+ const brandIconUpload = useImageUpload({
189
+ webPush,
190
+ webPushActions,
191
+ index: 1,
192
+ initialUploadMethod: IMAGE_UPLOAD_METHODS.UPLOAD_IMAGE,
193
+ });
194
+ const {
195
+ imageSrc: brandIconSrc,
196
+ imageUrl: brandIconUrl,
197
+ isValidating: isBrandIconValidating,
198
+ isUploading: isBrandIconUploading,
199
+ setImageSrc: setBrandIconSrc,
200
+ setImageUrl: setBrandIconUrl,
201
+ } = brandIconUpload;
202
+
203
+ // Memoize supportedTags to prevent unnecessary re-renders in useTagManagement
204
+ const memoizedSupportedTags = useMemo(() => supportedTags, [supportedTags]);
205
+
206
+ const tagState = useTagManagement({
207
+ location,
208
+ globalActions,
209
+ metaEntities,
210
+ getDefaultTags,
211
+ supportedTags: memoizedSupportedTags,
212
+ injectedTags,
213
+ eventContextTags,
214
+ });
215
+ const { tags, handleOnTagsContextChange, validationConfig } = tagState;
216
+
217
+ // Edit mode detection: check creativesMode or presence of template ID
218
+ const isEditMode = useMemo(
219
+ () =>
220
+ creativesMode === 'editTemplate'
221
+ || creativesMode === 'edit'
222
+ || !!(templateData?._id)
223
+ || !!(params?.id),
224
+ [creativesMode, templateData?._id, params?.id],
225
+ );
226
+
227
+ const accountId = useMemo(() => {
228
+ const fallbackAccountId = get(templateData, 'definition.accountId');
229
+ return (
230
+ accountData?.accountId
231
+ || accountData?.id
232
+ || fallbackAccountId
233
+ || get(accountData, 'sourceAccountIdentifier')
234
+ );
235
+ }, [
236
+ accountData?.accountId,
237
+ accountData?.id,
238
+ accountData?.sourceAccountIdentifier,
239
+ templateData,
240
+ ]);
241
+
242
+ const websiteLink = useMemo(
243
+ () => accountData?.configs?.websiteLink || '',
244
+ [accountData?.configs?.websiteLink],
245
+ );
246
+
247
+ const previewUrl = useMemo(
248
+ () => (
249
+ onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL
250
+ ? redirectUrl
251
+ : websiteLink || redirectUrl
252
+ ),
253
+ [onClickBehaviour, redirectUrl, websiteLink],
254
+ );
255
+
256
+ const onClickBehaviourOptions = useMemo(
257
+ () => ([
258
+ { value: ON_CLICK_BEHAVIOUR_OPTIONS.OPEN_SITE, label: formatMessage(messages.openSite) },
259
+ { value: ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL, label: formatMessage(messages.redirectToSpecificUrl) },
260
+ ]),
261
+ [formatMessage],
262
+ );
263
+
264
+ const validateTemplateName = useCallback((value) => validateTemplateNameUtil(value), []);
265
+
266
+ const validateTitle = useCallback(
267
+ (value) => validateTitleUtil(value, formatMessage, messages),
268
+ [formatMessage],
269
+ );
270
+
271
+ const validateUrl = useCallback(
272
+ (value) => validateUrlUtil(value, formatMessage, messages),
273
+ [formatMessage],
274
+ );
275
+
276
+ const updateFieldCompletion = useCallback((field, hasValue) => {
277
+ setFieldCompletion((prev) => {
278
+ if (prev[field] === hasValue) {
279
+ return prev;
280
+ }
281
+ return {
282
+ ...prev,
283
+ [field]: hasValue,
284
+ };
285
+ });
286
+ }, []);
287
+
288
+
289
+
290
+ const validateMessageContent = useCallback(
291
+ (value) => validateMessageContentUtil(value, formatMessage, messages, validationConfig),
292
+ [formatMessage, validationConfig],
293
+ );
294
+
295
+ useEffect(() => {
296
+ if (!isEditMode) {
297
+ return;
298
+ }
299
+
300
+ if (isEmpty(templateData)) {
301
+ return;
302
+ }
303
+
304
+ const webpushContent = get(templateData, 'versions.base.content.webpush', {});
305
+
306
+ const extractedTemplateName = templateData?.name || '';
307
+ const extractedNotificationTitle = webpushContent?.title || '';
308
+ const extractedMessage = webpushContent?.message || '';
309
+
310
+ // Update state to populate form fields
311
+ setTemplateName(extractedTemplateName);
312
+ setNotificationTitle(extractedNotificationTitle);
313
+ setMessage(extractedMessage);
314
+
315
+ const nextMediaType = webpushContent?.mediaType || WEBPUSH_MEDIA_TYPES.NONE;
316
+ setMediaType(nextMediaType);
317
+
318
+ const existingImage = webpushContent?.image || '';
319
+ if (existingImage) {
320
+ setImageSrc(existingImage);
321
+ setImageUrl('');
322
+ } else {
323
+ setImageSrc('');
324
+ setImageUrl('');
325
+ }
326
+
327
+ const existingBrandIcon = webpushContent?.brandIcon || '';
328
+ if (existingBrandIcon) {
329
+ setBrandIconOption(BRAND_ICON_OPTIONS.UPLOAD_IMAGE);
330
+ setBrandIconSrc(existingBrandIcon);
331
+ setBrandIconUrl('');
332
+ } else {
333
+ setBrandIconOption(BRAND_ICON_OPTIONS.DONT_SHOW);
334
+ setBrandIconSrc('');
335
+ setBrandIconUrl('');
336
+ }
337
+
338
+ const webpushCtas = Array.isArray(webpushContent?.ctas) ? webpushContent.ctas : [];
339
+ setButtons(
340
+ webpushCtas.map((cta, index) => ({
341
+ text: cta?.actionText || '',
342
+ url: cta?.actionLink || '',
343
+ type: index === 0 ? WEBPUSH_BUTTON_TYPES.PRIMARY : WEBPUSH_BUTTON_TYPES.SECONDARY,
344
+ })),
345
+ );
346
+
347
+ const onClickAction = webpushContent?.onClickAction || {};
348
+ if (onClickAction?.type === ACTION_TYPES.URL) {
349
+ setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL);
350
+ setRedirectUrl(onClickAction?.url || '');
351
+ setRedirectUrlError('');
352
+ } else if (onClickAction?.type === ACTION_TYPES.OPEN_SITE) {
353
+ setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.OPEN_SITE);
354
+ setRedirectUrl('');
355
+ setRedirectUrlError('');
356
+ } else {
357
+ setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.OPEN_SITE);
358
+ setRedirectUrl('');
359
+ setRedirectUrlError('');
360
+ }
361
+
362
+ updateFieldCompletion('notificationTitle', extractedNotificationTitle.trim().length > 0);
363
+ updateFieldCompletion('message', extractedMessage.trim().length > 0);
364
+ if (isFullMode) {
365
+ updateFieldCompletion('templateName', extractedTemplateName.trim().length > 0);
366
+ setTemplateNameError('');
367
+ }
368
+ setTitleError('');
369
+ setMessageError('');
370
+
371
+ updateCharacterCount(
372
+ titleCountRef,
373
+ extractedNotificationTitle.length,
374
+ NOTIFICATION_TITLE_MAX_LENGTH,
375
+ );
376
+ updateCharacterCount(
377
+ messageCountRef,
378
+ extractedMessage.length,
379
+ MESSAGE_MAX_LENGTH,
380
+ );
381
+ }, [
382
+ isEditMode,
383
+ templateData,
384
+ isFullMode,
385
+ updateFieldCompletion,
386
+ updateCharacterCount,
387
+ ]);
388
+
389
+ const handleTemplateNameChange = useCallback((e) => {
390
+ const { value } = e.target;
391
+ setTemplateName(value);
392
+ if (isFullMode) {
393
+ const nextError = validateTemplateName(value);
394
+ setTemplateNameError((prev) => (prev === nextError ? prev : nextError));
395
+ }
396
+ updateFieldCompletion('templateName', value.trim().length > 0);
397
+ }, [isFullMode, updateFieldCompletion, validateTemplateName]);
398
+
399
+ const handleNotificationTitleChange = useCallback((e) => {
400
+ const { value } = e.target;
401
+ setNotificationTitle(value);
402
+ const nextError = validateTitle(value);
403
+ setTitleError((prev) => (prev === nextError ? prev : nextError));
404
+ updateFieldCompletion('notificationTitle', value.trim().length > 0);
405
+ updateCharacterCount(titleCountRef, value.length, NOTIFICATION_TITLE_MAX_LENGTH);
406
+ }, [updateCharacterCount, updateFieldCompletion, validateTitle]);
407
+
408
+ // Handler to capture the textarea ref for CapEmojiPicker
409
+ const handleMessageTextAreaRef = useCallback((node) => {
410
+ if (!node) {
411
+ messageTextAreaRef.current = null;
412
+ return;
413
+ }
414
+ // Handles different TextArea implementations (antd's resizable, etc.)
415
+ messageTextAreaRef.current = node?.resizableTextArea?.textArea || node?.textAreaRef || node;
416
+ }, []);
417
+
418
+ // Optimize message change handler - use functional updates where possible
419
+ // Handles both event (from TextArea) and direct value (from CapEmojiPicker.Wrapper)
420
+ const handleMessageChange = useCallback((eOrValue) => {
421
+ const value = typeof eOrValue === 'string' ? eOrValue : eOrValue.target.value;
422
+ // Cap length to maximum (maxLength not passed to TextArea to avoid embedded character count)
423
+ const cappedValue = value.length > MESSAGE_MAX_LENGTH
424
+ ? value.slice(0, MESSAGE_MAX_LENGTH)
425
+ : value;
426
+ if (typeof eOrValue !== 'string' && eOrValue.target && cappedValue !== value) {
427
+ eOrValue.target.value = cappedValue;
428
+ }
429
+ setMessage(cappedValue);
430
+ const nextError = validateMessageContent(cappedValue);
431
+ setMessageError((prev) => (prev === nextError ? prev : nextError));
432
+ updateFieldCompletion('message', cappedValue.trim().length > 0);
433
+ updateCharacterCount(messageCountRef, cappedValue.length, MESSAGE_MAX_LENGTH);
434
+ }, [updateCharacterCount, updateFieldCompletion, validateMessageContent]);
435
+
436
+ const handleMediaTypeChange = useCallback((value) => {
437
+ setMediaType(value);
438
+ if (value === WEBPUSH_MEDIA_TYPES.NONE) {
439
+ setImageSrc('');
440
+ setImageUrl('');
441
+ }
442
+ }, [setImageSrc, setImageUrl]);
443
+
444
+ // Brand icon option change handler (kept in main component)
445
+ const handleBrandIconChange = useCallback((e) => {
446
+ const option = e.target.value;
447
+ setBrandIconOption(option);
448
+ setBrandIconSrc('');
449
+ setBrandIconUrl('');
450
+ }, [setBrandIconSrc, setBrandIconUrl]);
451
+
452
+
453
+
454
+ const handleOnClickBehaviourChange = useCallback((e) => {
455
+ const value = e.target.value;
456
+ setOnClickBehaviour(value);
457
+ if (value !== ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL) {
458
+ setRedirectUrl('');
459
+ setRedirectUrlError('');
460
+ }
461
+ }, []);
462
+
463
+ const handleRedirectUrlChange = useCallback((e) => {
464
+ const { value } = e.target;
465
+ setRedirectUrl(value);
466
+ const nextError = validateUrl(value);
467
+ setRedirectUrlError((prev) => (prev === nextError ? prev : nextError));
468
+ }, [validateUrl]);
469
+
470
+ // Optimized tag insertion handlers - split into separate callbacks
471
+ const handleTagSelectTitle = useCallback((tagValue) => {
472
+ const tagText = `{{${tagValue}}}`;
473
+ setNotificationTitle((prevTitle) => {
474
+ const nextTitle = `${prevTitle}${tagText}`.slice(0, NOTIFICATION_TITLE_MAX_LENGTH);
475
+ // Use functional updates to avoid dependency on notificationTitle
476
+ const nextError = validateTitle(nextTitle);
477
+ setTitleError((prev) => (prev === nextError ? prev : nextError));
478
+ updateFieldCompletion('notificationTitle', nextTitle.trim().length > 0);
479
+ updateCharacterCount(titleCountRef, nextTitle.length, NOTIFICATION_TITLE_MAX_LENGTH);
480
+ return nextTitle;
481
+ });
482
+ }, [updateCharacterCount, updateFieldCompletion, validateTitle]);
483
+
484
+ const handleTagSelectMessage = useCallback((tagValue) => {
485
+ const tagText = `{{${tagValue}}}`;
486
+ setMessage((prevMessage) => {
487
+ const nextMessage = `${prevMessage}${tagText}`.slice(0, MESSAGE_MAX_LENGTH);
488
+ // Use functional updates to avoid dependency on message
489
+ const nextError = validateMessageContent(nextMessage);
490
+ setMessageError((prev) => (prev === nextError ? prev : nextError));
491
+ updateFieldCompletion('message', nextMessage.trim().length > 0);
492
+ updateCharacterCount(messageCountRef, nextMessage.length, MESSAGE_MAX_LENGTH);
493
+ return nextMessage;
494
+ });
495
+ }, [updateCharacterCount, updateFieldCompletion, validateMessageContent]);
496
+
497
+
498
+ useEffect(() => {
499
+ updateCharacterCount(
500
+ titleCountRef,
501
+ notificationTitle.length,
502
+ NOTIFICATION_TITLE_MAX_LENGTH,
503
+ );
504
+ updateCharacterCount(
505
+ messageCountRef,
506
+ message.length,
507
+ MESSAGE_MAX_LENGTH,
508
+ );
509
+ }, [updateCharacterCount, notificationTitle, message]);
510
+
511
+ useEffect(() => {
512
+ setFieldCompletion((prev) => {
513
+ const nextTemplateNameComplete = !isFullMode
514
+ || templateName.trim().length > 0;
515
+ if (prev.templateName === nextTemplateNameComplete) {
516
+ return prev;
517
+ }
518
+ return {
519
+ ...prev,
520
+ templateName: nextTemplateNameComplete,
521
+ };
522
+ });
523
+ }, [isFullMode, templateName]);
524
+
525
+ const isFormValid = () => {
526
+ const templateNameInvalid = isFullMode && validateTemplateName(templateName);
527
+ const titleValidation = validateTitle(notificationTitle);
528
+ const messageValidation = validateMessageContent(message);
529
+
530
+ setTemplateNameError((prev) => (prev === templateNameInvalid ? prev : templateNameInvalid));
531
+ setTitleError((prev) => (prev === titleValidation ? prev : titleValidation));
532
+ setMessageError((prev) => (prev === messageValidation ? prev : messageValidation));
533
+
534
+ return !(templateNameInvalid || titleValidation || messageValidation);
535
+ };
536
+
537
+ const handleSave = () => {
538
+ if (!isFormValid()) {
539
+ return;
540
+ }
541
+
542
+ // Set flag to indicate save/edit operation has been initiated
543
+ saveInitiatedRef.current = true;
544
+
545
+ const payload = createWebPushPayload({
546
+ templateName,
547
+ notificationTitle,
548
+ message,
549
+ mediaType,
550
+ accountId,
551
+ isFullMode,
552
+ imageSrc,
553
+ imageUrl,
554
+ imageUploadMethod: imageUpload.uploadMethod,
555
+ brandIconOption,
556
+ brandIconSrc,
557
+ brandIconUrl,
558
+ buttons,
559
+ onClickBehaviour,
560
+ redirectUrl,
561
+ });
562
+
563
+ if (isEditMode) {
564
+ // Get template ID from params or templateData
565
+ const templateId = params?.id || templateData?._id;
566
+ if (templateId) {
567
+ // Clear any previous template ID error
568
+ setTemplateIdError('');
569
+ webPushActions.editTemplate(
570
+ {
571
+ ...payload,
572
+ _id: templateId,
573
+ },
574
+ (resp, errorMessage) => {
575
+ if (!errorMessage) {
576
+ // Clear flag before calling callbacks to prevent double-invocation
577
+ // from the editResponse useEffect
578
+ saveInitiatedRef.current = false;
579
+ if (onCreateComplete) {
580
+ onCreateComplete(true);
581
+ }
582
+ if (handleClose) {
583
+ handleClose();
584
+ }
585
+ }
586
+ },
587
+ );
588
+ } else {
589
+ // Template ID is missing in edit mode - surface error
590
+ const errorMsg = formatMessage(messages.templateIdMissingError);
591
+ setTemplateIdError(errorMsg);
592
+ // Reset save flag since we're not proceeding with the save
593
+ saveInitiatedRef.current = false;
594
+ // Notify parent component of failure
595
+ if (onCreateComplete) {
596
+ onCreateComplete(false);
597
+ }
598
+ return;
599
+ }
600
+ } else {
601
+ webPushActions.createTemplate(payload);
602
+ }
603
+ };
604
+
605
+ // Clear response state on mount to prevent stale responses from triggering callbacks
606
+ useEffect(() => {
607
+ webPushActions.clearCreateResponse();
608
+ webPushActions.clearEditResponse();
609
+ saveInitiatedRef.current = false;
610
+ }, []);
611
+
612
+ // Handle create response
613
+ useEffect(() => {
614
+ const response = webPush?.response || {};
615
+ if (
616
+ response &&
617
+ Object.keys(response).length > 0 &&
618
+ !isEditMode &&
619
+ saveInitiatedRef.current
620
+ ) {
621
+ // Reset flag after handling response
622
+ saveInitiatedRef.current = false;
623
+ if (onCreateComplete) {
624
+ onCreateComplete(true);
625
+ }
626
+ if (handleClose) {
627
+ handleClose();
628
+ }
629
+ }
630
+ }, [webPush?.response, onCreateComplete, handleClose, isEditMode]);
631
+
632
+ // Handle edit response
633
+ useEffect(() => {
634
+ const editResponse = webPush?.editResponse || {};
635
+ if (
636
+ editResponse &&
637
+ Object.keys(editResponse).length > 0 &&
638
+ isEditMode &&
639
+ saveInitiatedRef.current
640
+ ) {
641
+ // Reset flag after handling response
642
+ saveInitiatedRef.current = false;
643
+ if (onCreateComplete) {
644
+ onCreateComplete(true);
645
+ }
646
+ if (handleClose) {
647
+ handleClose();
648
+ }
649
+ }
650
+ }, [webPush?.editResponse, onCreateComplete, handleClose, isEditMode]);
651
+
652
+ const isUploadingAsset = useMemo(
653
+ () => webPush?.assetUploading || false,
654
+ [webPush?.assetUploading],
655
+ );
656
+
657
+ // Track which field is currently controlling uploads/validation
658
+ useEffect(() => {
659
+ if (isImageValidating || isImageUploading) {
660
+ setActiveUploadField(UPLOAD_FIELD_TYPES.IMAGE);
661
+ return;
662
+ }
663
+ if (isBrandIconValidating || isBrandIconUploading) {
664
+ setActiveUploadField(UPLOAD_FIELD_TYPES.BRAND_ICON);
665
+ return;
666
+ }
667
+ if (!isUploadingAsset) {
668
+ setActiveUploadField(null);
669
+ }
670
+ }, [
671
+ isImageValidating,
672
+ isImageUploading,
673
+ isBrandIconValidating,
674
+ isBrandIconUploading,
675
+ isUploadingAsset,
676
+ ]);
677
+
678
+ const isImageFieldActive = useMemo(
679
+ () => (
680
+ isImageValidating
681
+ || isImageUploading
682
+ || (isUploadingAsset && activeUploadField === UPLOAD_FIELD_TYPES.IMAGE)
683
+ ),
684
+ [
685
+ isImageValidating,
686
+ isImageUploading,
687
+ isUploadingAsset,
688
+ activeUploadField,
689
+ ],
690
+ );
691
+
692
+ const isBrandIconFieldActive = useMemo(
693
+ () => (
694
+ isBrandIconValidating
695
+ || isBrandIconUploading
696
+ || (isUploadingAsset && activeUploadField === UPLOAD_FIELD_TYPES.BRAND_ICON)
697
+ ),
698
+ [
699
+ isBrandIconValidating,
700
+ isBrandIconUploading,
701
+ isUploadingAsset,
702
+ activeUploadField,
703
+ ],
704
+ );
705
+
706
+ const isAnyUploadActive = useMemo(
707
+ () => isImageFieldActive || isBrandIconFieldActive,
708
+ [isImageFieldActive, isBrandIconFieldActive],
709
+ );
710
+
711
+ const isMediaSectionLocked = useMemo(
712
+ () => isBrandIconFieldActive,
713
+ [isBrandIconFieldActive],
714
+ );
715
+
716
+ const isBrandIconSectionLocked = useMemo(
717
+ () => isImageFieldActive,
718
+ [isImageFieldActive],
719
+ );
720
+
721
+ // Optimize tagListCommonProps - split into stable and dynamic parts
722
+ const moduleFilterEnabled = useMemo(
723
+ () => location?.query?.type !== 'embedded',
724
+ [location?.query?.type],
725
+ );
726
+
727
+ const tagListLabel = useMemo(
728
+ () => formatMessage(messages.addLabels),
729
+ [formatMessage],
730
+ );
731
+
732
+ // Stable props that don't change often
733
+ const tagListStableProps = useMemo(
734
+ () => ({
735
+ moduleFilterEnabled,
736
+ label: tagListLabel,
737
+ onContextChange: handleOnTagsContextChange,
738
+ location,
739
+ }),
740
+ [moduleFilterEnabled, tagListLabel, handleOnTagsContextChange, location],
741
+ );
742
+
743
+ // Dynamic props that change with tags
744
+ const tagListDynamicProps = useMemo(
745
+ () => ({
746
+ tags,
747
+ injectedTags,
748
+ selectedOfferDetails,
749
+ eventContextTags,
750
+ forwardedTags,
751
+ }),
752
+ [tags, injectedTags, selectedOfferDetails, eventContextTags, forwardedTags],
753
+ );
754
+
755
+ // Memoized TagList components with optimized props
756
+ const titleTagList = useMemo(
757
+ () => (
758
+ <MemoizedTagList
759
+ {...tagListStableProps}
760
+ {...tagListDynamicProps}
761
+ onTagSelect={handleTagSelectTitle}
762
+ />
763
+ ),
764
+ [tagListStableProps, tagListDynamicProps, handleTagSelectTitle],
765
+ );
766
+
767
+ const messageTagList = useMemo(
768
+ () => (
769
+ <MemoizedTagList
770
+ {...tagListStableProps}
771
+ {...tagListDynamicProps}
772
+ onTagSelect={handleTagSelectMessage}
773
+ />
774
+ ),
775
+ [tagListStableProps, tagListDynamicProps, handleTagSelectMessage],
776
+ );
777
+
778
+ const isSaveDisabled = useMemo(
779
+ () => (
780
+ createTemplateInProgress
781
+ || editTemplateInProgress
782
+ || isUploadingAsset
783
+ || isImageValidating
784
+ || isImageUploading
785
+ || isBrandIconValidating
786
+ || isBrandIconUploading
787
+ || buttonState.isAddingButton
788
+ || (isFullMode && !fieldCompletion.templateName)
789
+ || !fieldCompletion.notificationTitle
790
+ || !fieldCompletion.message
791
+ || !accountId
792
+ || (
793
+ mediaType === WEBPUSH_MEDIA_TYPES.IMAGE
794
+ && (
795
+ (imageUpload.uploadMethod === IMAGE_UPLOAD_METHODS.UPLOAD_IMAGE && !imageSrc)
796
+ || (imageUpload.uploadMethod === IMAGE_UPLOAD_METHODS.ADD_IMAGE_URL && !imageUrl)
797
+ )
798
+ )
799
+ || (
800
+ brandIconOption !== BRAND_ICON_OPTIONS.DONT_SHOW
801
+ && (
802
+ (brandIconOption === BRAND_ICON_OPTIONS.UPLOAD_IMAGE && !brandIconSrc)
803
+ || (brandIconOption === BRAND_ICON_OPTIONS.ADD_IMAGE_URL && !brandIconUrl)
804
+ )
805
+ )
806
+ || (onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL && (!redirectUrl.trim() || redirectUrlError))
807
+ ),
808
+ [
809
+ createTemplateInProgress,
810
+ editTemplateInProgress,
811
+ isUploadingAsset,
812
+ isImageValidating,
813
+ isImageUploading,
814
+ isBrandIconValidating,
815
+ isBrandIconUploading,
816
+ buttonState.isAddingButton,
817
+ isFullMode,
818
+ fieldCompletion.templateName,
819
+ fieldCompletion.notificationTitle,
820
+ fieldCompletion.message,
821
+ accountId,
822
+ mediaType,
823
+ imageUpload.uploadMethod,
824
+ imageSrc,
825
+ imageUrl,
826
+ brandIconOption,
827
+ brandIconSrc,
828
+ brandIconUrl,
829
+ onClickBehaviour,
830
+ redirectUrl,
831
+ redirectUrlError,
832
+ ],
833
+ );
834
+
835
+ const errorText = useMemo(() => {
836
+ // Check for templateIdError first (validation error before API call)
837
+ if (templateIdError) {
838
+ return templateIdError;
839
+ }
840
+ const error = isEditMode ? editTemplateError : createTemplateError;
841
+ if (!error) {
842
+ return '';
843
+ }
844
+ if (typeof error === 'string') {
845
+ return error;
846
+ }
847
+ if (error?.message) {
848
+ return error.message;
849
+ }
850
+ if (typeof error?.toJS === 'function') {
851
+ const errorObject = error.toJS();
852
+ if (typeof errorObject === 'string') {
853
+ return errorObject;
854
+ }
855
+ if (errorObject?.message) {
856
+ return errorObject.message;
857
+ }
858
+ try {
859
+ return JSON.stringify(errorObject);
860
+ } catch (err) {
861
+ return '';
862
+ }
863
+ }
864
+ try {
865
+ return JSON.stringify(error);
866
+ } catch (err) {
867
+ return '';
868
+ }
869
+ }, [templateIdError, createTemplateError, editTemplateError, isEditMode]);
870
+
871
+ const accountErrorText = useMemo(
872
+ () => (!accountId ? formatMessage(messages.accountRequired) : ''),
873
+ [accountId, formatMessage],
874
+ );
875
+
876
+ return (
877
+ <CapRow className="webpush-container">
878
+ <CapColumn className="content-section" span={14}>
879
+ <TemplateNameSection
880
+ isFullMode={isFullMode}
881
+ value={templateName}
882
+ error={templateNameError}
883
+ onChange={handleTemplateNameChange}
884
+ formatMessage={formatMessage}
885
+ messages={messages}
886
+ />
887
+ <NotificationTitleSection
888
+ value={notificationTitle}
889
+ error={titleError}
890
+ onChange={handleNotificationTitleChange}
891
+ formatMessage={formatMessage}
892
+ messages={messages}
893
+ tagList={titleTagList}
894
+ titleCountRef={titleCountRef}
895
+ />
896
+ <MessageSection
897
+ value={message}
898
+ error={messageError}
899
+ onChange={handleMessageChange}
900
+ formatMessage={formatMessage}
901
+ messages={messages}
902
+ tagList={messageTagList}
903
+ messageCountRef={messageCountRef}
904
+ messageTextAreaRef={messageTextAreaRef}
905
+ handleMessageTextAreaRef={handleMessageTextAreaRef}
906
+ />
907
+ <MediaSection
908
+ mediaType={mediaType}
909
+ onMediaTypeChange={handleMediaTypeChange}
910
+ imageUpload={imageUpload}
911
+ isLocked={isMediaSectionLocked}
912
+ isAnyUploadActive={isAnyUploadActive}
913
+ formatMessage={formatMessage}
914
+ messages={messages}
915
+ webPush={webPush}
916
+ isFullMode={isFullMode}
917
+ />
918
+ <BrandIconSection
919
+ brandIconOption={brandIconOption}
920
+ onBrandIconChange={handleBrandIconChange}
921
+ brandIconUpload={brandIconUpload}
922
+ isLocked={isBrandIconSectionLocked}
923
+ isAnyUploadActive={isAnyUploadActive}
924
+ formatMessage={formatMessage}
925
+ messages={messages}
926
+ webPush={webPush}
927
+ isFullMode={isFullMode}
928
+ />
929
+ <ButtonsLinksSection
930
+ onClickBehaviour={onClickBehaviour}
931
+ onClickBehaviourOptions={onClickBehaviourOptions}
932
+ onClickBehaviourChange={handleOnClickBehaviourChange}
933
+ redirectUrl={redirectUrl}
934
+ redirectUrlError={redirectUrlError}
935
+ onRedirectUrlChange={handleRedirectUrlChange}
936
+ buttonState={buttonState}
937
+ formatMessage={formatMessage}
938
+ messages={messages}
939
+ />
940
+ <FormActions
941
+ onSave={handleSave}
942
+ isSaveDisabled={isSaveDisabled}
943
+ errorText={errorText}
944
+ accountErrorText={accountErrorText}
945
+ formatMessage={formatMessage}
946
+ messages={messages}
947
+ />
948
+ </CapColumn>
949
+ <CapColumn className="preview-section" span={10}>
950
+ <WebPushPreview
951
+ notificationTitle={notificationTitle}
952
+ notificationBody={message}
953
+ url={previewUrl}
954
+ imageSrc={imageSrc}
955
+ brandIconSrc={brandIconSrc}
956
+ buttons={buttons}
957
+ />
958
+ </CapColumn>
959
+ </CapRow>
960
+ );
961
+ };
962
+
963
+ WebPushCreate.propTypes = {
964
+ isFullMode: PropTypes.bool,
965
+ handleClose: PropTypes.func,
966
+ intl: intlShape.isRequired,
967
+ webPushActions: PropTypes.object,
968
+ createTemplateInProgress: PropTypes.bool,
969
+ createTemplateError: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
970
+ editTemplateInProgress: PropTypes.bool,
971
+ editTemplateError: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
972
+ accountData: PropTypes.object,
973
+ webPush: PropTypes.object,
974
+ onCreateComplete: PropTypes.func,
975
+ templateData: PropTypes.object,
976
+ creativesMode: PropTypes.string,
977
+ params: PropTypes.object,
978
+ globalActions: PropTypes.object,
979
+ location: PropTypes.object,
980
+ metaEntities: PropTypes.object,
981
+ injectedTags: PropTypes.object,
982
+ getDefaultTags: PropTypes.string,
983
+ supportedTags: PropTypes.array,
984
+ forwardedTags: PropTypes.object,
985
+ selectedOfferDetails: PropTypes.array,
986
+ eventContextTags: PropTypes.array,
987
+ };
988
+
989
+ WebPushCreate.defaultProps = {
990
+ isFullMode: true,
991
+ handleClose: () => { },
992
+ webPushActions: {},
993
+ createTemplateInProgress: false,
994
+ editTemplateInProgress: false,
995
+ createTemplateError: '',
996
+ accountData: {},
997
+ webPush: {},
998
+ onCreateComplete: () => { },
999
+ templateData: null,
1000
+ creativesMode: 'createTemplate',
1001
+ params: null,
1002
+ globalActions: {},
1003
+ location: null,
1004
+ metaEntities: null,
1005
+ injectedTags: {},
1006
+ getDefaultTags: '',
1007
+ supportedTags: [],
1008
+ forwardedTags: {},
1009
+ selectedOfferDetails: [],
1010
+ eventContextTags: [],
1011
+ };
1012
+
1013
+ const mapStateToProps = createStructuredSelector({
1014
+ webPush: makeSelectWebPush(),
1015
+ createTemplateInProgress: makeSelectCreateTemplateInProgress(),
1016
+ createTemplateError: makeSelectCreateError(),
1017
+ editTemplateInProgress: makeSelectEditTemplateInProgress(),
1018
+ editTemplateError: makeSelectEditError(),
1019
+ metaEntities: makeSelectMetaEntities(),
1020
+ injectedTags: setInjectedTags(),
1021
+ accountData: createSelector(
1022
+ (state) => state.get('templates'),
1023
+ (templatesState) => {
1024
+ if (!templatesState) {
1025
+ return {};
1026
+ }
1027
+ const templates = templatesState.toJS();
1028
+ return templates?.selectedWebPushAccount || {};
1029
+ },
1030
+ ),
1031
+ });
1032
+
1033
+ const mapDispatchToProps = (dispatch) => ({
1034
+ webPushActions: bindActionCreators(actions, dispatch),
1035
+ });
1036
+
1037
+ const withSaga = injectSaga({
1038
+ key: 'webPush',
1039
+ saga: webPushSagas,
1040
+ mode: DAEMON,
1041
+ });
1042
+
1043
+ const withReducer = injectReducer({
1044
+ key: 'webPush',
1045
+ reducer: webPushReducer,
1046
+ });
1047
+
1048
+ export default withCreatives({
1049
+ WrappedComponent: injectIntl(WebPushCreate),
1050
+ mapStateToProps,
1051
+ mapDispatchToProps,
1052
+ userAuth: true,
1053
+ sagas: [withSaga],
1054
+ reducers: [withReducer],
1055
+ });
1056
+