@capillarytech/creatives-library 8.0.250-alpha.2 → 8.0.251

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