@capillarytech/creatives-library 8.0.263 → 8.0.265

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 (280) 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/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +35 -17
  9. package/tests/integration/TemplateCreation/api-response.js +1 -31
  10. package/tests/integration/TemplateCreation/msw-handler.js +0 -2
  11. package/utils/common.js +0 -11
  12. package/utils/commonUtils.js +5 -28
  13. package/utils/tests/commonUtil.test.js +0 -224
  14. package/utils/tests/transformerUtils.test.js +0 -297
  15. package/utils/transformTemplateConfig.js +10 -0
  16. package/utils/transformerUtils.js +0 -40
  17. package/v2Components/CapDeviceContent/index.js +56 -61
  18. package/v2Components/CapImageUpload/constants.js +0 -2
  19. package/v2Components/CapImageUpload/index.js +16 -65
  20. package/v2Components/CapImageUpload/index.scss +1 -4
  21. package/v2Components/CapImageUpload/messages.js +1 -5
  22. package/v2Components/CapTagList/index.js +1 -6
  23. package/v2Components/CapTagListWithInput/index.js +1 -5
  24. package/v2Components/CapTagListWithInput/messages.js +1 -1
  25. package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
  26. package/v2Components/ErrorInfoNote/index.js +72 -402
  27. package/v2Components/ErrorInfoNote/messages.js +6 -32
  28. package/v2Components/ErrorInfoNote/style.scss +6 -278
  29. package/v2Components/FormBuilder/tests/index.test.js +4 -13
  30. package/v2Components/HtmlEditor/HTMLEditor.js +99 -418
  31. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1882
  32. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +16 -27
  33. package/v2Components/HtmlEditor/_htmlEditor.scss +45 -108
  34. package/v2Components/HtmlEditor/_index.lazy.scss +1 -0
  35. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +102 -23
  36. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +140 -148
  37. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
  38. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  39. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +1 -9
  40. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +6 -31
  41. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -22
  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 +10 -7
  48. package/v2Components/HtmlEditor/components/PreviewPane/index.js +43 -22
  49. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  50. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +152 -0
  51. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +0 -18
  52. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -36
  53. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +34 -46
  54. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +46 -52
  55. package/v2Components/HtmlEditor/constants.js +20 -45
  56. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
  57. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +16 -351
  58. package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
  59. package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
  60. package/v2Components/HtmlEditor/hooks/useValidation.js +56 -213
  61. package/v2Components/HtmlEditor/index.js +1 -1
  62. package/v2Components/HtmlEditor/messages.js +94 -102
  63. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +45 -214
  64. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +0 -134
  65. package/v2Components/HtmlEditor/utils/contentSanitizer.js +41 -40
  66. package/v2Components/HtmlEditor/utils/htmlValidator.js +72 -71
  67. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +124 -158
  68. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +25 -23
  69. package/v2Components/HtmlEditor/utils/validationAdapter.js +41 -66
  70. package/v2Components/MobilePushPreviewV2/index.js +7 -33
  71. package/v2Components/TemplatePreview/_templatePreview.scss +24 -55
  72. package/v2Components/TemplatePreview/index.js +32 -47
  73. package/v2Components/TemplatePreview/messages.js +0 -4
  74. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +0 -1
  75. package/v2Containers/App/constants.js +0 -5
  76. package/v2Containers/BeeEditor/index.js +90 -172
  77. package/v2Containers/CreativesContainer/SlideBoxContent.js +53 -184
  78. package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -163
  79. package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -3
  80. package/v2Containers/CreativesContainer/constants.js +0 -4
  81. package/v2Containers/CreativesContainer/index.js +46 -408
  82. package/v2Containers/CreativesContainer/messages.js +0 -12
  83. package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +0 -210
  84. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +2 -11
  85. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +50 -342
  86. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -103
  87. package/v2Containers/Email/actions.js +0 -7
  88. package/v2Containers/Email/constants.js +1 -5
  89. package/v2Containers/Email/index.js +36 -237
  90. package/v2Containers/Email/messages.js +0 -32
  91. package/v2Containers/Email/reducer.js +1 -12
  92. package/v2Containers/Email/sagas.js +7 -61
  93. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -2
  94. package/v2Containers/Email/tests/reducer.test.js +0 -46
  95. package/v2Containers/Email/tests/sagas.test.js +29 -320
  96. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +21 -211
  97. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
  98. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
  99. package/v2Containers/EmailWrapper/constants.js +0 -2
  100. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +77 -629
  101. package/v2Containers/EmailWrapper/index.js +23 -103
  102. package/v2Containers/EmailWrapper/messages.js +1 -65
  103. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +214 -0
  104. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -594
  105. package/v2Containers/InApp/actions.js +0 -7
  106. package/v2Containers/InApp/constants.js +4 -20
  107. package/v2Containers/InApp/index.js +359 -802
  108. package/v2Containers/InApp/index.scss +3 -4
  109. package/v2Containers/InApp/messages.js +3 -7
  110. package/v2Containers/InApp/reducer.js +3 -21
  111. package/v2Containers/InApp/sagas.js +9 -29
  112. package/v2Containers/InApp/selectors.js +5 -25
  113. package/v2Containers/InApp/tests/index.test.js +50 -154
  114. package/v2Containers/InApp/tests/reducer.test.js +0 -34
  115. package/v2Containers/InApp/tests/sagas.test.js +9 -61
  116. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +0 -3
  117. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +0 -2
  118. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +0 -2
  119. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +0 -9
  120. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -12
  121. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +0 -4
  122. package/v2Containers/TagList/index.js +19 -62
  123. package/v2Containers/Templates/ChannelTypeIllustration.js +1 -13
  124. package/v2Containers/Templates/_templates.scss +1 -265
  125. package/v2Containers/Templates/actions.js +1 -2
  126. package/v2Containers/Templates/constants.js +0 -1
  127. package/v2Containers/Templates/index.js +38 -363
  128. package/v2Containers/Templates/messages.js +0 -28
  129. package/v2Containers/Templates/reducer.js +0 -2
  130. package/v2Containers/Templates/tests/index.test.js +0 -10
  131. package/v2Containers/TemplatesV2/TemplatesV2.style.js +2 -4
  132. package/v2Containers/TemplatesV2/index.js +7 -15
  133. package/v2Containers/TemplatesV2/messages.js +0 -4
  134. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +0 -34
  135. package/utils/imageUrlUpload.js +0 -141
  136. package/v2Components/CapImageUrlUpload/constants.js +0 -26
  137. package/v2Components/CapImageUrlUpload/index.js +0 -365
  138. package/v2Components/CapImageUrlUpload/index.scss +0 -35
  139. package/v2Components/CapImageUrlUpload/messages.js +0 -47
  140. package/v2Components/ErrorInfoNote/constants.js +0 -1
  141. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -870
  142. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +0 -6
  143. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -281
  144. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -295
  145. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
  146. package/v2Components/HtmlEditor/utils/validationConstants.js +0 -38
  147. package/v2Components/MobilePushPreviewV2/constants.js +0 -6
  148. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +0 -14
  149. package/v2Containers/BeePopupEditor/constants.js +0 -10
  150. package/v2Containers/BeePopupEditor/index.js +0 -194
  151. package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
  152. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1246
  153. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +0 -2472
  154. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +0 -520
  155. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +0 -956
  156. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
  157. package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
  158. package/v2Containers/InApp/tests/selectors.test.js +0 -612
  159. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -151
  160. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
  161. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -23
  162. package/v2Containers/InAppWrapper/constants.js +0 -16
  163. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
  164. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
  165. package/v2Containers/InAppWrapper/index.js +0 -148
  166. package/v2Containers/InAppWrapper/messages.js +0 -49
  167. package/v2Containers/InappAdvance/index.js +0 -1099
  168. package/v2Containers/InappAdvance/index.scss +0 -10
  169. package/v2Containers/InappAdvance/tests/index.test.js +0 -448
  170. package/v2Containers/WebPush/Create/components/BrandIconSection.js +0 -108
  171. package/v2Containers/WebPush/Create/components/ButtonForm.js +0 -172
  172. package/v2Containers/WebPush/Create/components/ButtonItem.js +0 -101
  173. package/v2Containers/WebPush/Create/components/ButtonList.js +0 -145
  174. package/v2Containers/WebPush/Create/components/ButtonsLinksSection.js +0 -164
  175. package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +0 -463
  176. package/v2Containers/WebPush/Create/components/FormActions.js +0 -54
  177. package/v2Containers/WebPush/Create/components/FormActions.test.js +0 -163
  178. package/v2Containers/WebPush/Create/components/MediaSection.js +0 -142
  179. package/v2Containers/WebPush/Create/components/MediaSection.test.js +0 -341
  180. package/v2Containers/WebPush/Create/components/MessageSection.js +0 -103
  181. package/v2Containers/WebPush/Create/components/MessageSection.test.js +0 -268
  182. package/v2Containers/WebPush/Create/components/NotificationTitleSection.js +0 -87
  183. package/v2Containers/WebPush/Create/components/NotificationTitleSection.test.js +0 -210
  184. package/v2Containers/WebPush/Create/components/TemplateNameSection.js +0 -54
  185. package/v2Containers/WebPush/Create/components/TemplateNameSection.test.js +0 -143
  186. package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +0 -86
  187. package/v2Containers/WebPush/Create/components/__snapshots__/FormActions.test.js.snap +0 -16
  188. package/v2Containers/WebPush/Create/components/__snapshots__/MediaSection.test.js.snap +0 -41
  189. package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +0 -54
  190. package/v2Containers/WebPush/Create/components/__snapshots__/NotificationTitleSection.test.js.snap +0 -37
  191. package/v2Containers/WebPush/Create/components/__snapshots__/TemplateNameSection.test.js.snap +0 -21
  192. package/v2Containers/WebPush/Create/components/_buttons.scss +0 -246
  193. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +0 -554
  194. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +0 -607
  195. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +0 -633
  196. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +0 -666
  197. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +0 -74
  198. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +0 -78
  199. package/v2Containers/WebPush/Create/hooks/useButtonManagement.js +0 -138
  200. package/v2Containers/WebPush/Create/hooks/useButtonManagement.test.js +0 -406
  201. package/v2Containers/WebPush/Create/hooks/useCharacterCount.js +0 -30
  202. package/v2Containers/WebPush/Create/hooks/useCharacterCount.test.js +0 -151
  203. package/v2Containers/WebPush/Create/hooks/useImageUpload.js +0 -104
  204. package/v2Containers/WebPush/Create/hooks/useImageUpload.test.js +0 -538
  205. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -122
  206. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -633
  207. package/v2Containers/WebPush/Create/index.js +0 -1148
  208. package/v2Containers/WebPush/Create/index.scss +0 -134
  209. package/v2Containers/WebPush/Create/messages.js +0 -211
  210. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +0 -228
  211. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +0 -294
  212. package/v2Containers/WebPush/Create/preview/PreviewContent.js +0 -90
  213. package/v2Containers/WebPush/Create/preview/PreviewControls.js +0 -305
  214. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +0 -25
  215. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +0 -155
  216. package/v2Containers/WebPush/Create/preview/assets/Light.svg +0 -53
  217. package/v2Containers/WebPush/Create/preview/assets/Top.svg +0 -5
  218. package/v2Containers/WebPush/Create/preview/assets/android-arrow-down.svg +0 -9
  219. package/v2Containers/WebPush/Create/preview/assets/android-arrow-up.svg +0 -9
  220. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  221. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  222. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +0 -106
  223. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +0 -26
  224. package/v2Containers/WebPush/Create/preview/assets/macos-arrow-down-icon.svg +0 -9
  225. package/v2Containers/WebPush/Create/preview/assets/macos-triple-dot-icon.svg +0 -9
  226. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +0 -18
  227. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +0 -29
  228. package/v2Containers/WebPush/Create/preview/assets/windows-close-icon.svg +0 -9
  229. package/v2Containers/WebPush/Create/preview/assets/windows-triple-dot-icon.svg +0 -9
  230. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +0 -51
  231. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +0 -145
  232. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +0 -45
  233. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +0 -68
  234. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +0 -61
  235. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +0 -99
  236. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +0 -733
  237. package/v2Containers/WebPush/Create/preview/components/tests/WindowsChromeExpanded.test.js +0 -571
  238. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +0 -85
  239. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/WindowsChromeExpanded.test.js.snap +0 -81
  240. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +0 -50
  241. package/v2Containers/WebPush/Create/preview/constants.js +0 -637
  242. package/v2Containers/WebPush/Create/preview/notification-container.scss +0 -79
  243. package/v2Containers/WebPush/Create/preview/preview.scss +0 -358
  244. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +0 -370
  245. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +0 -12
  246. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +0 -12
  247. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +0 -12
  248. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +0 -47
  249. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +0 -11
  250. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +0 -11
  251. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +0 -11
  252. package/v2Containers/WebPush/Create/preview/styles/_base.scss +0 -207
  253. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +0 -153
  254. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +0 -107
  255. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +0 -101
  256. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +0 -229
  257. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +0 -909
  258. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +0 -1081
  259. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +0 -723
  260. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +0 -1327
  261. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +0 -131
  262. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +0 -112
  263. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +0 -144
  264. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +0 -129
  265. package/v2Containers/WebPush/Create/utils/payloadBuilder.js +0 -96
  266. package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +0 -396
  267. package/v2Containers/WebPush/Create/utils/previewUtils.js +0 -89
  268. package/v2Containers/WebPush/Create/utils/urlValidation.js +0 -115
  269. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +0 -449
  270. package/v2Containers/WebPush/Create/utils/validation.js +0 -75
  271. package/v2Containers/WebPush/Create/utils/validation.test.js +0 -283
  272. package/v2Containers/WebPush/actions.js +0 -60
  273. package/v2Containers/WebPush/constants.js +0 -132
  274. package/v2Containers/WebPush/index.js +0 -2
  275. package/v2Containers/WebPush/reducer.js +0 -104
  276. package/v2Containers/WebPush/sagas.js +0 -119
  277. package/v2Containers/WebPush/selectors.js +0 -65
  278. package/v2Containers/WebPush/tests/reducer.test.js +0 -863
  279. package/v2Containers/WebPush/tests/sagas.test.js +0 -566
  280. 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
-