@capillarytech/creatives-library 8.0.242-alpha.0 → 8.0.242-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +2 -1
  4. package/initialReducer.js +2 -0
  5. package/package.json +1 -1
  6. package/sagas/__tests__/assetPolling.test.js +74 -3
  7. package/sagas/assetPolling.js +8 -1
  8. package/services/api.js +10 -5
  9. package/services/tests/api.test.js +18 -0
  10. package/translations/en.json +0 -1
  11. package/utils/common.js +5 -0
  12. package/utils/commonUtils.js +14 -1
  13. package/utils/tests/commonUtil.test.js +224 -0
  14. package/utils/transformTemplateConfig.js +0 -10
  15. package/utils/transformerUtils.js +0 -42
  16. package/v2Components/CapDeviceContent/index.js +61 -56
  17. package/v2Components/CapImageUpload/constants.js +0 -2
  18. package/v2Components/CapImageUpload/index.js +14 -54
  19. package/v2Components/CapImageUpload/index.scss +1 -4
  20. package/v2Components/CapImageUpload/messages.js +0 -4
  21. package/v2Components/CapTagList/index.js +6 -1
  22. package/v2Components/CapTagListWithInput/index.js +5 -1
  23. package/v2Components/CapTagListWithInput/messages.js +1 -1
  24. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  25. package/v2Components/ErrorInfoNote/index.js +412 -72
  26. package/v2Components/ErrorInfoNote/messages.js +22 -0
  27. package/v2Components/ErrorInfoNote/style.scss +279 -2
  28. package/v2Components/HtmlEditor/HTMLEditor.js +217 -90
  29. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1132 -133
  30. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +17 -12
  31. package/v2Components/HtmlEditor/_htmlEditor.scss +15 -23
  32. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  33. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +13 -101
  34. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -139
  35. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  36. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  37. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +1 -0
  38. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  39. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +1 -0
  40. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  41. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  43. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  44. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  45. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
  46. package/v2Components/HtmlEditor/components/PreviewPane/index.js +10 -11
  47. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  48. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +87 -62
  49. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
  50. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
  51. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +362 -0
  52. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  53. package/v2Components/HtmlEditor/constants.js +29 -20
  54. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  55. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  56. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  57. package/v2Components/HtmlEditor/index.js +1 -1
  58. package/v2Components/HtmlEditor/messages.js +95 -85
  59. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +99 -101
  60. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  61. package/v2Components/HtmlEditor/utils/validationAdapter.js +34 -41
  62. package/v2Components/MobilePushPreviewV2/index.js +32 -7
  63. package/v2Components/TemplatePreview/_templatePreview.scss +44 -24
  64. package/v2Components/TemplatePreview/index.js +47 -32
  65. package/v2Components/TemplatePreview/messages.js +4 -0
  66. package/v2Components/TestAndPreviewSlidebox/index.js +31 -25
  67. package/v2Containers/App/constants.js +0 -5
  68. package/v2Containers/BeeEditor/index.js +82 -80
  69. package/v2Containers/BeePopupEditor/constants.js +10 -0
  70. package/v2Containers/BeePopupEditor/index.js +193 -0
  71. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  72. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +0 -1
  73. package/v2Containers/CreativesContainer/SlideBoxContent.js +148 -120
  74. package/v2Containers/CreativesContainer/SlideBoxFooter.js +9 -3
  75. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -2
  76. package/v2Containers/CreativesContainer/constants.js +1 -2
  77. package/v2Containers/CreativesContainer/index.js +173 -193
  78. package/v2Containers/CreativesContainer/messages.js +4 -4
  79. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  80. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +36 -0
  81. package/v2Containers/Email/actions.js +7 -0
  82. package/v2Containers/Email/constants.js +5 -1
  83. package/v2Containers/Email/index.js +13 -0
  84. package/v2Containers/Email/messages.js +32 -0
  85. package/v2Containers/Email/reducer.js +12 -1
  86. package/v2Containers/Email/sagas.js +41 -6
  87. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  88. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1046 -0
  89. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +193 -7
  90. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  91. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  92. package/v2Containers/EmailWrapper/constants.js +2 -0
  93. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +436 -67
  94. package/v2Containers/EmailWrapper/index.js +99 -23
  95. package/v2Containers/EmailWrapper/messages.js +61 -1
  96. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +26 -1
  97. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +111 -77
  98. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  99. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  100. package/v2Containers/InApp/actions.js +7 -0
  101. package/v2Containers/InApp/constants.js +20 -4
  102. package/v2Containers/InApp/index.js +800 -357
  103. package/v2Containers/InApp/index.scss +4 -3
  104. package/v2Containers/InApp/messages.js +7 -3
  105. package/v2Containers/InApp/reducer.js +21 -3
  106. package/v2Containers/InApp/sagas.js +29 -9
  107. package/v2Containers/InApp/selectors.js +25 -5
  108. package/v2Containers/InApp/tests/index.test.js +154 -50
  109. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  110. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  111. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  112. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +162 -0
  113. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  114. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +9 -0
  115. package/v2Containers/InAppWrapper/constants.js +16 -0
  116. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  117. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  118. package/v2Containers/InAppWrapper/index.js +148 -0
  119. package/v2Containers/InAppWrapper/messages.js +49 -0
  120. package/v2Containers/InappAdvance/index.js +1099 -0
  121. package/v2Containers/InappAdvance/index.scss +10 -0
  122. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  123. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -3
  124. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -2
  125. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -25
  126. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -18
  127. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -46
  128. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +0 -4
  129. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -8
  130. package/v2Containers/TagList/index.js +67 -1
  131. package/v2Containers/Templates/ChannelTypeIllustration.js +1 -13
  132. package/v2Containers/Templates/_templates.scss +56 -200
  133. package/v2Containers/Templates/actions.js +1 -2
  134. package/v2Containers/Templates/constants.js +0 -1
  135. package/v2Containers/Templates/index.js +124 -277
  136. package/v2Containers/Templates/messages.js +4 -24
  137. package/v2Containers/Templates/reducer.js +0 -2
  138. package/v2Containers/Templates/tests/index.test.js +0 -10
  139. package/v2Containers/TemplatesV2/index.js +2 -3
  140. package/v2Containers/TemplatesV2/messages.js +0 -4
  141. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +35 -132
  142. package/v2Components/CapImageUrlUpload/constants.js +0 -19
  143. package/v2Components/CapImageUrlUpload/index.js +0 -455
  144. package/v2Components/CapImageUrlUpload/index.scss +0 -35
  145. package/v2Components/CapImageUrlUpload/messages.js +0 -47
  146. package/v2Containers/WebPush/Create/components/ButtonForm.js +0 -175
  147. package/v2Containers/WebPush/Create/components/ButtonItem.js +0 -101
  148. package/v2Containers/WebPush/Create/components/ButtonList.js +0 -144
  149. package/v2Containers/WebPush/Create/components/_buttons.scss +0 -246
  150. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +0 -554
  151. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +0 -607
  152. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +0 -633
  153. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +0 -666
  154. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +0 -74
  155. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +0 -80
  156. package/v2Containers/WebPush/Create/index.js +0 -1755
  157. package/v2Containers/WebPush/Create/index.scss +0 -123
  158. package/v2Containers/WebPush/Create/messages.js +0 -199
  159. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +0 -241
  160. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +0 -290
  161. package/v2Containers/WebPush/Create/preview/PreviewContent.js +0 -81
  162. package/v2Containers/WebPush/Create/preview/PreviewControls.js +0 -240
  163. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +0 -23
  164. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +0 -144
  165. package/v2Containers/WebPush/Create/preview/assets/Light.svg +0 -53
  166. package/v2Containers/WebPush/Create/preview/assets/Top.svg +0 -5
  167. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  168. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  169. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +0 -106
  170. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +0 -26
  171. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +0 -18
  172. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +0 -29
  173. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +0 -44
  174. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +0 -110
  175. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +0 -45
  176. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +0 -72
  177. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +0 -55
  178. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +0 -70
  179. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +0 -512
  180. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +0 -77
  181. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +0 -527
  182. package/v2Containers/WebPush/Create/preview/constants.js +0 -162
  183. package/v2Containers/WebPush/Create/preview/notification-container.scss +0 -104
  184. package/v2Containers/WebPush/Create/preview/preview.scss +0 -409
  185. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +0 -300
  186. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +0 -12
  187. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +0 -12
  188. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +0 -12
  189. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +0 -303
  190. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +0 -11
  191. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +0 -11
  192. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +0 -11
  193. package/v2Containers/WebPush/Create/preview/styles/_base.scss +0 -188
  194. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +0 -106
  195. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +0 -107
  196. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +0 -75
  197. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +0 -174
  198. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +0 -909
  199. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +0 -1077
  200. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +0 -723
  201. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +0 -943
  202. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +0 -128
  203. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +0 -121
  204. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +0 -144
  205. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +0 -127
  206. package/v2Containers/WebPush/Create/utils/urlValidation.js +0 -116
  207. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +0 -449
  208. package/v2Containers/WebPush/actions.js +0 -60
  209. package/v2Containers/WebPush/constants.js +0 -108
  210. package/v2Containers/WebPush/index.js +0 -2
  211. package/v2Containers/WebPush/reducer.js +0 -104
  212. package/v2Containers/WebPush/sagas.js +0 -119
  213. package/v2Containers/WebPush/selectors.js +0 -65
  214. package/v2Containers/WebPush/tests/reducer.test.js +0 -863
  215. package/v2Containers/WebPush/tests/sagas.test.js +0 -566
  216. package/v2Containers/WebPush/tests/selectors.test.js +0 -960
@@ -1,1755 +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 { FormattedMessage, 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 CapInput from '@capillarytech/cap-ui-library/CapInput';
19
- import CapRow from '@capillarytech/cap-ui-library/CapRow';
20
- import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
21
- import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
22
- import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
23
- import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
24
- import CapError from '@capillarytech/cap-ui-library/CapError';
25
- import CapButton from '@capillarytech/cap-ui-library/CapButton';
26
- import CapRadioGroup from '@capillarytech/cap-ui-library/CapRadioGroup';
27
- import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
28
- import CapImageUpload from '../../../v2Components/CapImageUpload';
29
- import CapImageUrlUpload from '../../../v2Components/CapImageUrlUpload';
30
- import TagList from '../../TagList';
31
- import ButtonForm from './components/ButtonForm';
32
- import ButtonList from './components/ButtonList';
33
- import WebPushPreview from './preview/WebPushPreview';
34
- import isEmpty from 'lodash/isEmpty';
35
- import get from 'lodash/get';
36
- import { WEBPUSH, WEBPUSH_BRAND_ICON } from '../../CreativesContainer/constants.js';
37
- import * as templateActions from '../../Templates/actions';
38
- import { makeSelectTemplates } from '../../Templates/selectors';
39
-
40
- import {
41
- WEBPUSH_MEDIA_TYPES,
42
- WEBPUSH_MEDIA_TYPES_OPTIONS,
43
- ALLOWED_IMAGE_EXTENSIONS_REGEX,
44
- WEBPUSH_IMG_SIZE,
45
- WEBPUSH_RECOMMENDED_DIMENSIONS,
46
- BRAND_ICON_OPTIONS,
47
- IMAGE_UPLOAD_METHODS,
48
- WEBPUSH_BRAND_ICON_SIZE,
49
- WEBPUSH_BRAND_ICON_RECOMMENDED_DIMENSIONS,
50
- UPLOAD_FIELD_TYPES,
51
- WEBPUSH_BUTTON_TYPES,
52
- ON_CLICK_BEHAVIOUR_OPTIONS,
53
- ACTION_TYPES,
54
- } from '../constants';
55
- import * as actions from '../actions';
56
- import {
57
- makeSelectWebPush,
58
- makeSelectCreateError,
59
- makeSelectCreateTemplateInProgress,
60
- makeSelectEditTemplateInProgress,
61
- makeSelectEditError,
62
- } from '../selectors';
63
- import webPushReducer from '../reducer';
64
- import webPushSagas from '../sagas';
65
- import { v2TemplateSaga } from '../../Templates/sagas';
66
- import withCreatives from '../../../hoc/withCreatives';
67
- import messages from './messages';
68
- import globalMessages from '../../Cap/messages';
69
- import { isValidHttpUrl } from './utils/urlValidation';
70
- import { validateTags } from '../../../utils/tagValidations';
71
- import {
72
- makeSelectMetaEntities,
73
- setInjectedTags,
74
- } from '../../Cap/selectors';
75
- import {
76
- ALL,
77
- TAG,
78
- EMBEDDED,
79
- DEFAULT,
80
- FULL,
81
- LIBRARY,
82
- } from '../../Whatsapp/constants';
83
- import { SMS } from '../../CreativesContainer/constants';
84
- import './index.scss';
85
-
86
- // Character count configuration - set to false to disable
87
- const SHOW_CHARACTER_COUNT = true;
88
-
89
- // Maximum character limits for Web Push fields
90
- const NOTIFICATION_TITLE_MAX_LENGTH = 65;
91
- const MESSAGE_MAX_LENGTH = 240;
92
-
93
- // Memoized TagList wrapper components for better performance
94
- const MemoizedTagList = memo(({
95
- moduleFilterEnabled,
96
- label,
97
- onContextChange,
98
- location,
99
- tags,
100
- injectedTags,
101
- selectedOfferDetails,
102
- eventContextTags,
103
- forwardedTags,
104
- onTagSelect,
105
- }) => (
106
- <TagList
107
- moduleFilterEnabled={moduleFilterEnabled}
108
- label={label}
109
- onContextChange={onContextChange}
110
- location={location}
111
- tags={tags}
112
- injectedTags={injectedTags}
113
- selectedOfferDetails={selectedOfferDetails}
114
- eventContextTags={eventContextTags}
115
- forwardedTags={forwardedTags}
116
- onTagSelect={onTagSelect}
117
- />
118
- ), (prevProps, nextProps) => {
119
- // Custom comparison function for better memoization
120
- return (
121
- prevProps.moduleFilterEnabled === nextProps.moduleFilterEnabled
122
- && prevProps.label === nextProps.label
123
- && prevProps.onContextChange === nextProps.onContextChange
124
- && prevProps.location === nextProps.location
125
- && prevProps.tags === nextProps.tags
126
- && prevProps.injectedTags === nextProps.injectedTags
127
- && prevProps.selectedOfferDetails === nextProps.selectedOfferDetails
128
- && prevProps.eventContextTags === nextProps.eventContextTags
129
- && prevProps.forwardedTags === nextProps.forwardedTags
130
- && prevProps.onTagSelect === nextProps.onTagSelect
131
- );
132
- });
133
-
134
- MemoizedTagList.displayName = 'MemoizedTagList';
135
-
136
- const createWebPushPayload = ({
137
- templateName,
138
- notificationTitle,
139
- message,
140
- mediaType,
141
- accountId,
142
- isFullMode,
143
- imageSrc,
144
- imageUrl,
145
- imageUploadMethod,
146
- brandIconOption,
147
- brandIconSrc,
148
- brandIconUrl,
149
- buttons,
150
- onClickBehaviour,
151
- redirectUrl,
152
- websiteLink,
153
- }) => {
154
- const trimmedTemplateName = (templateName || '').trim();
155
- const trimmedTitle = (notificationTitle || '').trim();
156
- const trimmedMessage = (message || '').trim();
157
-
158
- const webpushContent = {
159
- title: trimmedTitle,
160
- message: trimmedMessage,
161
- ...(mediaType && mediaType !== WEBPUSH_MEDIA_TYPES.NONE ? { mediaType } : {}),
162
- };
163
-
164
- // Add image data if media type is IMAGE
165
- if (mediaType === WEBPUSH_MEDIA_TYPES.IMAGE) {
166
- if (imageUploadMethod === IMAGE_UPLOAD_METHODS.UPLOAD_IMAGE && imageSrc) {
167
- webpushContent.image = imageSrc;
168
- } else if (imageUploadMethod === IMAGE_UPLOAD_METHODS.ADD_IMAGE_URL && imageSrc) {
169
- // Use imageSrc (uploaded secure_file_path) instead of imageUrl
170
- // NOTE: To move upload to save event, check imageUrl exists but imageSrc is empty, upload, then use secure_file_path
171
- webpushContent.image = imageSrc;
172
- }
173
- }
174
-
175
- // Add brand icon data if option is not DONT_SHOW
176
- if (brandIconOption !== BRAND_ICON_OPTIONS.DONT_SHOW && brandIconSrc) {
177
- webpushContent.brandIcon = brandIconSrc;
178
- }
179
-
180
- // Add on-click behaviour for the notification body
181
- if (onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL && redirectUrl) {
182
- webpushContent.onClickAction = {
183
- type: ACTION_TYPES.URL,
184
- url: redirectUrl.trim(),
185
- };
186
- } else if (onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL) {
187
- webpushContent.onClickAction = {
188
- type: ACTION_TYPES.SITE_URL,
189
- url: websiteLink || '',
190
- };
191
- }
192
-
193
- // Add CTA buttons if any exist
194
- if (buttons && buttons.length > 0) {
195
- webpushContent.ctas = buttons.map((button) => ({
196
- actionText: button.text,
197
- type: ACTION_TYPES.URL,
198
- actionLink: button.url,
199
- }));
200
- }
201
-
202
- return {
203
- name: trimmedTemplateName || "",
204
- type: WEBPUSH,
205
- definition: {
206
- accountId,
207
- },
208
- versions: {
209
- base: {
210
- content: {
211
- webpush: webpushContent,
212
- },
213
- },
214
- },
215
- };
216
- };
217
-
218
- const WebPushCreate = ({
219
- isFullMode,
220
- handleClose,
221
- intl,
222
- webPushActions,
223
- createTemplateInProgress,
224
- createTemplateError,
225
- editTemplateInProgress,
226
- editTemplateError,
227
- accountData,
228
- webPush,
229
- onCreateComplete,
230
- getFormData,
231
- isGetFormData,
232
- templateData,
233
- creativesMode,
234
- params,
235
- globalActions,
236
- location,
237
- metaEntities,
238
- injectedTags,
239
- getDefaultTags,
240
- supportedTags = [],
241
- forwardedTags,
242
- selectedOfferDetails = [],
243
- eventContextTags = [],
244
- templateActions: templateActionsProps,
245
- Templates,
246
- }) => {
247
- const { formatMessage } = intl;
248
- const { CapHeadingSpan } = CapHeading;
249
- // Convert to state for controlled components
250
- const [templateName, setTemplateName] = useState('');
251
- const [notificationTitle, setNotificationTitle] = useState('');
252
- const [message, setMessage] = useState('');
253
- const [mediaType, setMediaType] = useState(WEBPUSH_MEDIA_TYPES.NONE);
254
- const [templateNameError, setTemplateNameError] = useState(false);
255
- const [titleError, setTitleError] = useState('');
256
- const [messageError, setMessageError] = useState('');
257
- const [fieldCompletion, setFieldCompletion] = useState({
258
- templateName: !isFullMode,
259
- notificationTitle: false,
260
- message: false,
261
- });
262
- const titleCountRef = useRef(null);
263
- const messageCountRef = useRef(null);
264
- // Track if save/edit was initiated to prevent stale response handlers from firing
265
- const saveInitiatedRef = useRef(false);
266
- const [brandIconOption, setBrandIconOption] = useState(BRAND_ICON_OPTIONS.DONT_SHOW);
267
- const [onClickBehaviour, setOnClickBehaviour] = useState(ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL);
268
- const [redirectUrl, setRedirectUrl] = useState('');
269
- const [redirectUrlError, setRedirectUrlError] = useState('');
270
-
271
- // Image upload state
272
- const [imageUploadMethod, setImageUploadMethod] = useState(IMAGE_UPLOAD_METHODS.UPLOAD_IMAGE);
273
- const [imageSrc, setImageSrc] = useState('');
274
- const [imageUrl, setImageUrl] = useState('');
275
- const [isImageValidating, setIsImageValidating] = useState(false);
276
- const [isImageUploading, setIsImageUploading] = useState(false);
277
-
278
- // Brand icon upload state
279
- const [brandIconSrc, setBrandIconSrc] = useState('');
280
- const [brandIconUrl, setBrandIconUrl] = useState('');
281
- const [isBrandIconValidating, setIsBrandIconValidating] = useState(false);
282
- const [isBrandIconUploading, setIsBrandIconUploading] = useState(false);
283
-
284
- // Button state
285
- const [buttons, setButtons] = useState([]);
286
- const [isAddingButton, setIsAddingButton] = useState(false);
287
- const [buttonBeingAdded, setButtonBeingAdded] = useState(null); // WEBPUSH_BUTTON_TYPES.PRIMARY or WEBPUSH_BUTTON_TYPES.SECONDARY
288
- const [editingButtonIndex, setEditingButtonIndex] = useState(null);
289
- const [activeUploadField, setActiveUploadField] = useState(null); // UPLOAD_FIELD_TYPES.IMAGE | UPLOAD_FIELD_TYPES.BRAND_ICON | null
290
- const [tags, setTags] = useState([]);
291
- const tagFetchKeyRef = useRef(null);
292
- const { weCrmAccounts } = Templates;
293
-
294
- // Edit mode detection: check creativesMode or presence of template ID
295
- const isEditMode = useMemo(
296
- () =>
297
- creativesMode === 'editTemplate'
298
- || creativesMode === 'edit'
299
- || !!(templateData?._id)
300
- || !!(params?.id),
301
- [creativesMode, templateData?._id, params?.id],
302
- );
303
-
304
- const accountId = useMemo(() => {
305
- const fallbackAccountId = get(templateData, 'definition.accountId');
306
- return (
307
- accountData?.accountId
308
- || accountData?.id
309
- || fallbackAccountId
310
- || get(accountData, 'sourceAccountIdentifier')
311
- );
312
- }, [
313
- accountData?.accountId,
314
- accountData?.id,
315
- accountData?.sourceAccountIdentifier,
316
- templateData,
317
- ]);
318
-
319
- const selectedWebPushAccount = useMemo(() =>
320
- weCrmAccounts?.find(account => account.id === accountId),
321
- [weCrmAccounts, accountId]);
322
-
323
- const websiteLink = useMemo(
324
- () => accountData?.configs?.websiteLink || selectedWebPushAccount?.configs?.websiteLink || '',
325
- [accountData?.configs?.websiteLink, selectedWebPushAccount?.configs?.websiteLink],
326
- );
327
-
328
- // Fetch account details when websiteLink is missing but accountId exists
329
- useEffect(() => {
330
- if (!websiteLink && accountId && templateActionsProps?.getWeCrmAccounts) {
331
- templateActionsProps.getWeCrmAccounts('WebPush')
332
- }
333
- }, [websiteLink, accountId, templateActionsProps]);
334
-
335
- const previewUrl = useMemo(
336
- () => (
337
- onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL
338
- ? redirectUrl
339
- : websiteLink || redirectUrl
340
- ),
341
- [onClickBehaviour, redirectUrl, websiteLink],
342
- );
343
-
344
- const brandIconOptions = useMemo(
345
- () => ([
346
- { value: BRAND_ICON_OPTIONS.DONT_SHOW, label: formatMessage(messages.dontShow) },
347
- { value: BRAND_ICON_OPTIONS.UPLOAD_IMAGE, label: formatMessage(messages.uploadImage) },
348
- { value: BRAND_ICON_OPTIONS.ADD_IMAGE_URL, label: formatMessage(messages.addImageUrl) },
349
- ]),
350
- [formatMessage],
351
- );
352
-
353
- const onClickBehaviourOptions = useMemo(
354
- () => ([
355
- { value: ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL, label: formatMessage(messages.openSite) },
356
- { value: ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL, label: formatMessage(messages.redirectToSpecificUrl) },
357
- ]),
358
- [formatMessage],
359
- );
360
-
361
- const validateTemplateName = useCallback((value) => !value || value.trim() === '', []);
362
-
363
- const validateTitle = useCallback((value) => {
364
- if (!value || value.trim() === '') {
365
- return formatMessage(messages.titleRequired);
366
- }
367
- return '';
368
- }, [formatMessage]);
369
-
370
- const validateUrl = useCallback((value) => {
371
- if (!value || value.trim() === '') {
372
- return formatMessage(messages.urlRequired);
373
- }
374
-
375
- if (!isValidHttpUrl(value)) {
376
- return formatMessage(messages.urlInvalid);
377
- }
378
-
379
- return '';
380
- }, [formatMessage]);
381
-
382
- const updateFieldCompletion = useCallback((field, hasValue) => {
383
- setFieldCompletion((prev) => {
384
- if (prev[field] === hasValue) {
385
- return prev;
386
- }
387
- return {
388
- ...prev,
389
- [field]: hasValue,
390
- };
391
- });
392
- }, []);
393
-
394
- const updateCharacterCount = useCallback((labelRef, currentLength, maxLength) => {
395
- if (labelRef?.current) {
396
- labelRef.current.textContent = formatMessage(messages.characterCount, {
397
- currentLength,
398
- maxLength,
399
- });
400
- }
401
- }, [formatMessage]);
402
-
403
- // Fetch tags on mount based on context
404
- const locationType = location?.query?.type || '';
405
- const locationModule = location?.query?.module || '';
406
-
407
- const tagFetchKey = useMemo(
408
- () => JSON.stringify({
409
- type: locationType,
410
- module: locationModule,
411
- defaultTags: getDefaultTags || '',
412
- }),
413
- [locationModule, locationType, getDefaultTags],
414
- );
415
-
416
- useEffect(() => {
417
- if (!globalActions?.fetchSchemaForEntity) return;
418
-
419
- const { type, module } = location?.query || {};
420
- const isEmbedded = type === EMBEDDED;
421
- const context = isEmbedded ? module : DEFAULT;
422
- const embedded = isEmbedded ? type : FULL;
423
- const query = {
424
- layout: SMS,
425
- type: TAG,
426
- context,
427
- embedded,
428
- };
429
- if (getDefaultTags) {
430
- query.context = getDefaultTags;
431
- }
432
-
433
- if (tagFetchKeyRef.current === tagFetchKey) {
434
- return;
435
- }
436
- tagFetchKeyRef.current = tagFetchKey;
437
- globalActions.fetchSchemaForEntity(query);
438
- }, [globalActions, getDefaultTags, location?.query, tagFetchKey]);
439
-
440
- // Update tags from metaEntities and supported tags
441
- useEffect(() => {
442
- if (!metaEntities) return;
443
- let tagList = get(metaEntities, 'tags.standard', []);
444
- if (locationType === EMBEDDED && locationModule === LIBRARY && !getDefaultTags) {
445
- tagList = supportedTags;
446
- }
447
- setTags(tagList || []);
448
- }, [metaEntities, locationModule, locationType, supportedTags, getDefaultTags]);
449
-
450
- const handleOnTagsContextChange = useCallback((data) => {
451
- const isEmbedded = locationType === EMBEDDED;
452
- const embedded = isEmbedded ? locationType : FULL;
453
- const context = (data || '').toLowerCase() === ALL ? DEFAULT : (data || '').toLowerCase();
454
- const query = {
455
- layout: SMS,
456
- type: TAG,
457
- context,
458
- embedded,
459
- };
460
- if (getDefaultTags) {
461
- query.context = getDefaultTags;
462
- }
463
- globalActions?.fetchSchemaForEntity(query);
464
- }, [globalActions, getDefaultTags, locationType]);
465
-
466
- // Memoize validation function to avoid recreating on every render
467
- const validationConfig = useMemo(
468
- () => ({
469
- tagsParam: tags,
470
- injectedTagsParams: injectedTags,
471
- location,
472
- tagModule: getDefaultTags,
473
- eventContextTags,
474
- }),
475
- [tags, injectedTags, location, getDefaultTags, eventContextTags],
476
- );
477
-
478
- const validateMessageContent = useCallback((value) => {
479
- if (!value || value.trim() === '') {
480
- return formatMessage(messages.messageRequired);
481
- }
482
- const validationResponse = validateTags({
483
- content: value,
484
- ...validationConfig,
485
- }) || {};
486
- if (validationResponse?.unsupportedTags?.length) {
487
- return formatMessage(globalMessages.unsupportedTagsValidationError, {
488
- unsupportedTags: validationResponse.unsupportedTags.join(', '),
489
- });
490
- }
491
- if (validationResponse?.isBraceError) {
492
- return formatMessage(globalMessages.unbalanacedCurlyBraces);
493
- }
494
- return '';
495
- }, [formatMessage, validationConfig]);
496
-
497
- useEffect(() => {
498
- if (!isEditMode) {
499
- return;
500
- }
501
-
502
- if (isEmpty(templateData)) {
503
- return;
504
- }
505
-
506
- const webpushContent = get(templateData, 'versions.base.content.webpush', {});
507
-
508
- const extractedTemplateName = templateData?.name || '';
509
- const extractedNotificationTitle = webpushContent?.title || '';
510
- const extractedMessage = webpushContent?.message || '';
511
-
512
- // Update state to populate form fields
513
- setTemplateName(extractedTemplateName);
514
- setNotificationTitle(extractedNotificationTitle);
515
- setMessage(extractedMessage);
516
-
517
- const nextMediaType = webpushContent?.mediaType || WEBPUSH_MEDIA_TYPES.NONE;
518
- setMediaType(nextMediaType);
519
-
520
- const existingImage = webpushContent?.image || '';
521
- if (existingImage) {
522
- setImageSrc(existingImage);
523
- setImageUploadMethod(IMAGE_UPLOAD_METHODS.UPLOAD_IMAGE);
524
- setImageUrl('');
525
- } else {
526
- setImageSrc('');
527
- setImageUrl('');
528
- }
529
-
530
- const existingBrandIcon = webpushContent?.brandIcon || '';
531
- if (existingBrandIcon) {
532
- setBrandIconOption(BRAND_ICON_OPTIONS.UPLOAD_IMAGE);
533
- setBrandIconSrc(existingBrandIcon);
534
- setBrandIconUrl('');
535
- } else {
536
- setBrandIconOption(BRAND_ICON_OPTIONS.DONT_SHOW);
537
- setBrandIconSrc('');
538
- setBrandIconUrl('');
539
- }
540
-
541
- const webpushCtas = Array.isArray(webpushContent?.ctas) ? webpushContent.ctas : [];
542
- setButtons(
543
- webpushCtas.map((cta, index) => ({
544
- text: cta?.actionText || '',
545
- url: cta?.actionLink || '',
546
- type: index === 0 ? WEBPUSH_BUTTON_TYPES.PRIMARY : WEBPUSH_BUTTON_TYPES.SECONDARY,
547
- })),
548
- );
549
-
550
- const onClickAction = webpushContent?.onClickAction || {};
551
- if (onClickAction?.type === ACTION_TYPES.URL) {
552
- setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL);
553
- setRedirectUrl(onClickAction?.url || '');
554
- setRedirectUrlError('');
555
- } else if (onClickAction?.type === ACTION_TYPES.SITE_URL) {
556
- setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL);
557
- setRedirectUrl(websiteLink);
558
- setRedirectUrlError('');
559
- } else {
560
- setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL);
561
- setRedirectUrl(websiteLink);
562
- setRedirectUrlError('');
563
- }
564
-
565
- updateFieldCompletion('notificationTitle', extractedNotificationTitle.trim().length > 0);
566
- updateFieldCompletion('message', extractedMessage.trim().length > 0);
567
- if (isFullMode) {
568
- updateFieldCompletion('templateName', extractedTemplateName.trim().length > 0);
569
- setTemplateNameError('');
570
- }
571
- setTitleError('');
572
- setMessageError('');
573
-
574
- setIsAddingButton(false);
575
- setButtonBeingAdded(null);
576
- setEditingButtonIndex(null);
577
-
578
- updateCharacterCount(
579
- titleCountRef,
580
- extractedNotificationTitle.length,
581
- NOTIFICATION_TITLE_MAX_LENGTH,
582
- );
583
- updateCharacterCount(
584
- messageCountRef,
585
- extractedMessage.length,
586
- MESSAGE_MAX_LENGTH,
587
- );
588
- }, [
589
- isEditMode,
590
- templateData,
591
- isFullMode,
592
- updateFieldCompletion,
593
- updateCharacterCount,
594
- ]);
595
-
596
- const handleTemplateNameChange = useCallback((e) => {
597
- const { value } = e.target;
598
- setTemplateName(value);
599
- if (isFullMode) {
600
- const nextError = validateTemplateName(value);
601
- setTemplateNameError((prev) => (prev === nextError ? prev : nextError));
602
- }
603
- updateFieldCompletion('templateName', value.trim().length > 0);
604
- }, [isFullMode, updateFieldCompletion, validateTemplateName]);
605
-
606
- const handleNotificationTitleChange = useCallback((e) => {
607
- const { value } = e.target;
608
- setNotificationTitle(value);
609
- const nextError = validateTitle(value);
610
- setTitleError((prev) => (prev === nextError ? prev : nextError));
611
- updateFieldCompletion('notificationTitle', value.trim().length > 0);
612
- updateCharacterCount(titleCountRef, value.length, NOTIFICATION_TITLE_MAX_LENGTH);
613
- }, [updateCharacterCount, updateFieldCompletion, validateTitle]);
614
-
615
- // Optimize message change handler - use functional updates where possible
616
- const handleMessageChange = useCallback((e) => {
617
- const { value } = e.target;
618
- // Cap length to maximum (maxLength not passed to TextArea to avoid embedded character count)
619
- const cappedValue = value.length > MESSAGE_MAX_LENGTH
620
- ? value.slice(0, MESSAGE_MAX_LENGTH)
621
- : value;
622
- if (cappedValue !== value) {
623
- e.target.value = cappedValue;
624
- }
625
- setMessage(cappedValue);
626
- const nextError = validateMessageContent(cappedValue);
627
- setMessageError((prev) => (prev === nextError ? prev : nextError));
628
- updateFieldCompletion('message', cappedValue.trim().length > 0);
629
- updateCharacterCount(messageCountRef, cappedValue.length, MESSAGE_MAX_LENGTH);
630
- }, [updateCharacterCount, updateFieldCompletion, validateMessageContent]);
631
-
632
- const handleMediaTypeChange = useCallback((value) => {
633
- setMediaType(value);
634
- if (value === WEBPUSH_MEDIA_TYPES.NONE) {
635
- setImageSrc('');
636
- setImageUrl('');
637
- setImageUploadMethod(IMAGE_UPLOAD_METHODS.UPLOAD_IMAGE);
638
- }
639
- }, []);
640
-
641
- const handleImageUploadMethodChange = useCallback((e) => {
642
- const method = e.target.value;
643
- setImageUploadMethod(method);
644
- setImageSrc('');
645
- setImageUrl('');
646
- }, []);
647
-
648
- // Image upload handlers
649
- const setUpdateWebPushImageSrc = useCallback(
650
- (filePath) => {
651
- setImageSrc(filePath);
652
- webPushActions.clearWebPushAsset(0);
653
- },
654
- [webPushActions],
655
- );
656
-
657
- const updateOnWebPushImageReUpload = useCallback(() => {
658
- setImageSrc('');
659
- }, []);
660
-
661
- const uploadWebPushAsset = (file, type, fileParams) => {
662
- setActiveUploadField(UPLOAD_FIELD_TYPES.IMAGE);
663
- webPushActions.uploadWebPushAsset(file, type, fileParams, 0);
664
- };
665
-
666
- const handleImageUrlChange = useCallback((url) => {
667
- setImageUrl(url);
668
- // Clear imageSrc when URL changes
669
- if (url !== imageUrl) {
670
- setImageSrc('');
671
- }
672
- }, [imageUrl]);
673
-
674
- const handleImageValidationStateChange = useCallback((isValidating) => {
675
- setIsImageValidating(isValidating);
676
- }, []);
677
-
678
- const handleImageUploadStateChange = useCallback((isUploading) => {
679
- setIsImageUploading(isUploading);
680
- }, []);
681
-
682
- // Restore image from uploaded asset data
683
- useEffect(() => {
684
- const imageDataObj = get(webPush, 'uploadedAssetData0') || get(webPush, 'uploadedAssetData');
685
- if (!isEmpty(imageDataObj)) {
686
- const { secure_file_path = '' } = get(imageDataObj, 'metaInfo', {});
687
- if (secure_file_path) {
688
- setUpdateWebPushImageSrc(secure_file_path);
689
- }
690
- }
691
- }, [webPush, setUpdateWebPushImageSrc]);
692
-
693
- // Brand icon handlers
694
- const handleBrandIconChange = useCallback((e) => {
695
- const option = e.target.value;
696
- setBrandIconOption(option);
697
- setBrandIconSrc('');
698
- setBrandIconUrl('');
699
- }, []);
700
-
701
- const setUpdateWebPushBrandIconSrc = useCallback(
702
- (filePath) => {
703
- setBrandIconSrc(filePath);
704
- webPushActions.clearWebPushAsset(1);
705
- },
706
- [webPushActions],
707
- );
708
-
709
- const updateOnWebPushBrandIconReUpload = useCallback(() => {
710
- setBrandIconSrc('');
711
- }, []);
712
-
713
- const uploadWebPushBrandIconAsset = (file, type, fileParams) => {
714
- setActiveUploadField(UPLOAD_FIELD_TYPES.BRAND_ICON);
715
- webPushActions.uploadWebPushAsset(file, type, fileParams, 1);
716
- };
717
-
718
- const handleBrandIconUrlChange = useCallback((url) => {
719
- setBrandIconUrl(url);
720
- // Clear brandIconSrc when URL changes
721
- if (url !== brandIconUrl) {
722
- setBrandIconSrc('');
723
- }
724
- }, [brandIconUrl]);
725
-
726
- const handleBrandIconValidationStateChange = useCallback((isValidating) => {
727
- setIsBrandIconValidating(isValidating);
728
- }, []);
729
-
730
- const handleBrandIconUploadStateChange = useCallback((isUploading) => {
731
- setIsBrandIconUploading(isUploading);
732
- }, []);
733
-
734
- // Restore brand icon from uploaded asset data
735
- useEffect(() => {
736
- const brandIconDataObj = get(webPush, 'uploadedAssetData1');
737
- if (!isEmpty(brandIconDataObj)) {
738
- const { secure_file_path = '' } = get(brandIconDataObj, 'metaInfo', {});
739
- if (secure_file_path) {
740
- setUpdateWebPushBrandIconSrc(secure_file_path);
741
- }
742
- }
743
- }, [webPush, setUpdateWebPushBrandIconSrc]);
744
-
745
-
746
- // Render character count for notification title
747
- const renderTitleCharacterCount = (className = "webpush-character-count") => {
748
- if (!SHOW_CHARACTER_COUNT) return null;
749
-
750
- const maxLength = NOTIFICATION_TITLE_MAX_LENGTH;
751
-
752
- return (
753
- <CapLabel type="label2" className={className}>
754
- <span ref={titleCountRef}>
755
- {formatMessage(messages.characterCount, {
756
- currentLength: notificationTitle.length,
757
- maxLength,
758
- })}
759
- </span>
760
- </CapLabel>
761
- );
762
- };
763
-
764
- // Render character count for message
765
- const renderMessageCharacterCount = (className = "webpush-character-count") => {
766
- if (!SHOW_CHARACTER_COUNT) return null;
767
-
768
- const maxLength = MESSAGE_MAX_LENGTH;
769
-
770
- return (
771
- <CapLabel type="label2" className={className}>
772
- <span ref={messageCountRef}>
773
- {formatMessage(messages.characterCount, {
774
- currentLength: message.length,
775
- maxLength,
776
- })}
777
- </span>
778
- </CapLabel>
779
- );
780
- };
781
-
782
- const handleOnClickBehaviourChange = useCallback((e) => {
783
- const value = e.target.value;
784
- setOnClickBehaviour(value);
785
- if (value !== ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL) {
786
- setRedirectUrl('');
787
- setRedirectUrlError('');
788
- }
789
- }, []);
790
-
791
- const handleRedirectUrlChange = useCallback((e) => {
792
- const { value } = e.target;
793
- setRedirectUrl(value);
794
- const nextError = validateUrl(value);
795
- setRedirectUrlError((prev) => (prev === nextError ? prev : nextError));
796
- }, [validateUrl]);
797
-
798
- // Optimized tag insertion handlers - split into separate callbacks
799
- const handleTagSelectTitle = useCallback((tagValue) => {
800
- const tagText = `{{${tagValue}}}`;
801
- setNotificationTitle((prevTitle) => {
802
- const nextTitle = `${prevTitle}${tagText}`.slice(0, NOTIFICATION_TITLE_MAX_LENGTH);
803
- // Use functional updates to avoid dependency on notificationTitle
804
- const nextError = validateTitle(nextTitle);
805
- setTitleError((prev) => (prev === nextError ? prev : nextError));
806
- updateFieldCompletion('notificationTitle', nextTitle.trim().length > 0);
807
- updateCharacterCount(titleCountRef, nextTitle.length, NOTIFICATION_TITLE_MAX_LENGTH);
808
- return nextTitle;
809
- });
810
- }, [updateCharacterCount, updateFieldCompletion, validateTitle]);
811
-
812
- const handleTagSelectMessage = useCallback((tagValue) => {
813
- const tagText = `{{${tagValue}}}`;
814
- setMessage((prevMessage) => {
815
- const nextMessage = `${prevMessage}${tagText}`.slice(0, MESSAGE_MAX_LENGTH);
816
- // Use functional updates to avoid dependency on message
817
- const nextError = validateMessageContent(nextMessage);
818
- setMessageError((prev) => (prev === nextError ? prev : nextError));
819
- updateFieldCompletion('message', nextMessage.trim().length > 0);
820
- updateCharacterCount(messageCountRef, nextMessage.length, MESSAGE_MAX_LENGTH);
821
- return nextMessage;
822
- });
823
- }, [updateCharacterCount, updateFieldCompletion, validateMessageContent]);
824
-
825
- // Button handlers
826
- const handleAddPrimaryButton = useCallback(() => {
827
- setIsAddingButton(true);
828
- setButtonBeingAdded(WEBPUSH_BUTTON_TYPES.PRIMARY);
829
- setEditingButtonIndex(null);
830
- }, []);
831
-
832
- const handleAddSecondaryButton = useCallback(() => {
833
- setIsAddingButton(true);
834
- setButtonBeingAdded(WEBPUSH_BUTTON_TYPES.SECONDARY);
835
- setEditingButtonIndex(null);
836
- }, []);
837
-
838
- const handleButtonSave = useCallback((buttonData) => {
839
- if (editingButtonIndex !== null) {
840
- // Editing existing button
841
- const newButtons = [...buttons];
842
- newButtons[editingButtonIndex] = buttonData;
843
- setButtons(newButtons);
844
- } else {
845
- // Adding new button
846
- setButtons((prev) => [...prev, buttonData]);
847
- }
848
- setIsAddingButton(false);
849
- setButtonBeingAdded(null);
850
- setEditingButtonIndex(null);
851
- }, [buttons, editingButtonIndex]);
852
-
853
- const handleButtonCancel = useCallback(() => {
854
- setIsAddingButton(false);
855
- setButtonBeingAdded(null);
856
- setEditingButtonIndex(null);
857
- }, []);
858
-
859
- const handleButtonEdit = useCallback((index) => {
860
- setIsAddingButton(true);
861
- setButtonBeingAdded(buttons[index].type);
862
- setEditingButtonIndex(index);
863
- }, [buttons]);
864
-
865
- const handleButtonDelete = useCallback((index) => {
866
- const newButtons = [...buttons];
867
- newButtons.splice(index, 1);
868
- setButtons(newButtons);
869
- }, [buttons]);
870
-
871
- const handleButtonReorder = useCallback((fromIndex, toIndex) => {
872
- const newButtons = [...buttons];
873
- const [movedButton] = newButtons.splice(fromIndex, 1);
874
- newButtons.splice(toIndex, 0, movedButton);
875
- setButtons(newButtons);
876
- }, [buttons]);
877
-
878
- const isAddFlow = useMemo(
879
- () => isAddingButton && editingButtonIndex === null,
880
- [isAddingButton, editingButtonIndex],
881
- );
882
- const isEditFlow = useMemo(
883
- () => isAddingButton && editingButtonIndex !== null,
884
- [isAddingButton, editingButtonIndex],
885
- );
886
-
887
- const showAddPrimaryButton = useMemo(
888
- () => buttons.length === 0 && !isAddingButton,
889
- [buttons.length, isAddingButton],
890
- );
891
- const showAddSecondaryButton = useMemo(
892
- () => buttons.length === 1 && !isAddingButton,
893
- [buttons.length, isAddingButton],
894
- );
895
- const showDisabledSecondaryDuringPrimary = useMemo(
896
- () => isAddFlow && buttonBeingAdded === WEBPUSH_BUTTON_TYPES.PRIMARY && buttons.length === 0,
897
- [isAddFlow, buttonBeingAdded, buttons.length],
898
- );
899
- const disableSecondaryAddButton = useMemo(
900
- () => isAddFlow && buttonBeingAdded === WEBPUSH_BUTTON_TYPES.PRIMARY,
901
- [isAddFlow, buttonBeingAdded],
902
- );
903
-
904
- const renderButtonForm = (isEditMode) => (
905
- <ButtonForm
906
- buttonType={buttonBeingAdded}
907
- formatMessage={formatMessage}
908
- onSave={handleButtonSave}
909
- onCancel={handleButtonCancel}
910
- initialData={isEditMode && editingButtonIndex !== null ? buttons[editingButtonIndex] : null}
911
- isEditMode={isEditMode}
912
- />
913
- );
914
-
915
- useEffect(() => {
916
- updateCharacterCount(
917
- titleCountRef,
918
- notificationTitle.length,
919
- NOTIFICATION_TITLE_MAX_LENGTH,
920
- );
921
- updateCharacterCount(
922
- messageCountRef,
923
- message.length,
924
- MESSAGE_MAX_LENGTH,
925
- );
926
- }, [updateCharacterCount, notificationTitle, message]);
927
-
928
- useEffect(() => {
929
- setFieldCompletion((prev) => {
930
- const nextTemplateNameComplete = !isFullMode
931
- || templateName.trim().length > 0;
932
- if (prev.templateName === nextTemplateNameComplete) {
933
- return prev;
934
- }
935
- return {
936
- ...prev,
937
- templateName: nextTemplateNameComplete,
938
- };
939
- });
940
- }, [isFullMode, templateName]);
941
-
942
- const isFormValid = () => {
943
- const templateNameInvalid = isFullMode && validateTemplateName(templateName);
944
- const titleValidation = validateTitle(notificationTitle);
945
- const messageValidation = validateMessageContent(message);
946
-
947
- setTemplateNameError((prev) => (prev === templateNameInvalid ? prev : templateNameInvalid));
948
- setTitleError((prev) => (prev === titleValidation ? prev : titleValidation));
949
- setMessageError((prev) => (prev === messageValidation ? prev : messageValidation));
950
-
951
- return !(templateNameInvalid || titleValidation || messageValidation);
952
- };
953
-
954
- const handleSave = () => {
955
- if (!isFormValid()) {
956
- return;
957
- }
958
-
959
- // Set flag to indicate save/edit operation has been initiated
960
- saveInitiatedRef.current = true;
961
-
962
- const payload = createWebPushPayload({
963
- templateName,
964
- notificationTitle,
965
- message,
966
- mediaType,
967
- accountId,
968
- isFullMode,
969
- imageSrc,
970
- imageUrl,
971
- imageUploadMethod,
972
- brandIconOption,
973
- brandIconSrc,
974
- brandIconUrl,
975
- buttons,
976
- onClickBehaviour,
977
- redirectUrl,
978
- websiteLink,
979
- });
980
-
981
- // In library mode (not full mode), use getFormData to communicate with parent
982
- if (!isFullMode && getFormData) {
983
- const formDataForLibrary = {
984
- validity: true,
985
- value: payload,
986
- type: 'WEBPUSH',
987
- };
988
- getFormData(formDataForLibrary);
989
- if (handleClose) {
990
- handleClose();
991
- }
992
- return;
993
- }
994
-
995
- // Full mode: proceed with API calls
996
- if (isEditMode) {
997
- // Get template ID from params or templateData
998
- const templateId = params?.id || templateData?._id;
999
- if (templateId) {
1000
- webPushActions.editTemplate(
1001
- {
1002
- ...payload,
1003
- _id: templateId,
1004
- },
1005
- (resp, errorMessage) => {
1006
- if (!errorMessage) {
1007
- if (onCreateComplete) {
1008
- onCreateComplete(true);
1009
- }
1010
- if (handleClose) {
1011
- handleClose();
1012
- }
1013
- }
1014
- },
1015
- );
1016
- }
1017
- } else {
1018
- webPushActions.createTemplate(payload);
1019
- }
1020
- };
1021
-
1022
- // Clear response state on mount to prevent stale responses from triggering callbacks
1023
- useEffect(() => {
1024
- webPushActions.clearCreateResponse();
1025
- webPushActions.clearEditResponse();
1026
- saveInitiatedRef.current = false;
1027
- }, []);
1028
-
1029
- // Handle create response
1030
- useEffect(() => {
1031
- const response = webPush?.response || {};
1032
- if (
1033
- response &&
1034
- Object.keys(response).length > 0 &&
1035
- !isEditMode &&
1036
- saveInitiatedRef.current
1037
- ) {
1038
- // Reset flag after handling response
1039
- saveInitiatedRef.current = false;
1040
- if (onCreateComplete) {
1041
- onCreateComplete(true);
1042
- }
1043
- if (handleClose) {
1044
- handleClose();
1045
- }
1046
- }
1047
- }, [webPush?.response, onCreateComplete, handleClose, isEditMode]);
1048
-
1049
- // Handle edit response
1050
- useEffect(() => {
1051
- const editResponse = webPush?.editResponse || {};
1052
- if (
1053
- editResponse &&
1054
- Object.keys(editResponse).length > 0 &&
1055
- isEditMode &&
1056
- saveInitiatedRef.current
1057
- ) {
1058
- // Reset flag after handling response
1059
- saveInitiatedRef.current = false;
1060
- if (onCreateComplete) {
1061
- onCreateComplete(true);
1062
- }
1063
- if (handleClose) {
1064
- handleClose();
1065
- }
1066
- }
1067
- }, [webPush?.editResponse, onCreateComplete, handleClose, isEditMode]);
1068
-
1069
- // Handle getFormData request from parent (library mode)
1070
- useEffect(() => {
1071
- if (isGetFormData && getFormData && isFormValid()) {
1072
- const payload = createWebPushPayload({
1073
- templateName,
1074
- notificationTitle,
1075
- message,
1076
- mediaType,
1077
- accountId,
1078
- isFullMode,
1079
- imageSrc,
1080
- imageUrl,
1081
- imageUploadMethod,
1082
- brandIconOption,
1083
- brandIconSrc,
1084
- brandIconUrl,
1085
- buttons,
1086
- onClickBehaviour,
1087
- redirectUrl,
1088
- });
1089
- const formDataForLibrary = {
1090
- validity: true,
1091
- value: payload,
1092
- type: 'WEBPUSH',
1093
- };
1094
- getFormData(formDataForLibrary);
1095
- }
1096
- }, [isGetFormData, getFormData, templateName, notificationTitle, message, mediaType, accountId, isFullMode, imageSrc, imageUrl, imageUploadMethod, brandIconOption, brandIconSrc, brandIconUrl, buttons, onClickBehaviour, redirectUrl]);
1097
-
1098
- const isUploadingAsset = useMemo(
1099
- () => webPush?.assetUploading || false,
1100
- [webPush?.assetUploading],
1101
- );
1102
-
1103
- // Track which field is currently controlling uploads/validation
1104
- useEffect(() => {
1105
- if (isImageValidating || isImageUploading) {
1106
- setActiveUploadField(UPLOAD_FIELD_TYPES.IMAGE);
1107
- return;
1108
- }
1109
- if (isBrandIconValidating || isBrandIconUploading) {
1110
- setActiveUploadField(UPLOAD_FIELD_TYPES.BRAND_ICON);
1111
- return;
1112
- }
1113
- if (!isUploadingAsset) {
1114
- setActiveUploadField(null);
1115
- }
1116
- }, [
1117
- isImageValidating,
1118
- isImageUploading,
1119
- isBrandIconValidating,
1120
- isBrandIconUploading,
1121
- isUploadingAsset,
1122
- ]);
1123
-
1124
- const isImageFieldActive = useMemo(
1125
- () => (
1126
- isImageValidating
1127
- || isImageUploading
1128
- || (isUploadingAsset && activeUploadField === UPLOAD_FIELD_TYPES.IMAGE)
1129
- ),
1130
- [
1131
- isImageValidating,
1132
- isImageUploading,
1133
- isUploadingAsset,
1134
- activeUploadField,
1135
- ],
1136
- );
1137
-
1138
- const isBrandIconFieldActive = useMemo(
1139
- () => (
1140
- isBrandIconValidating
1141
- || isBrandIconUploading
1142
- || (isUploadingAsset && activeUploadField === UPLOAD_FIELD_TYPES.BRAND_ICON)
1143
- ),
1144
- [
1145
- isBrandIconValidating,
1146
- isBrandIconUploading,
1147
- isUploadingAsset,
1148
- activeUploadField,
1149
- ],
1150
- );
1151
-
1152
- const isAnyUploadActive = useMemo(
1153
- () => isImageFieldActive || isBrandIconFieldActive,
1154
- [isImageFieldActive, isBrandIconFieldActive],
1155
- );
1156
-
1157
- const isMediaSectionLocked = useMemo(
1158
- () => isBrandIconFieldActive,
1159
- [isBrandIconFieldActive],
1160
- );
1161
-
1162
- const isBrandIconSectionLocked = useMemo(
1163
- () => isImageFieldActive,
1164
- [isImageFieldActive],
1165
- );
1166
-
1167
- // Optimize tagListCommonProps - split into stable and dynamic parts
1168
- const moduleFilterEnabled = useMemo(
1169
- () => location?.query?.type !== 'embedded',
1170
- [location?.query?.type],
1171
- );
1172
-
1173
- const tagListLabel = useMemo(
1174
- () => formatMessage(messages.addLabels),
1175
- [formatMessage],
1176
- );
1177
-
1178
- // Stable props that don't change often
1179
- const tagListStableProps = useMemo(
1180
- () => ({
1181
- moduleFilterEnabled,
1182
- label: tagListLabel,
1183
- onContextChange: handleOnTagsContextChange,
1184
- location,
1185
- }),
1186
- [moduleFilterEnabled, tagListLabel, handleOnTagsContextChange, location],
1187
- );
1188
-
1189
- // Dynamic props that change with tags
1190
- const tagListDynamicProps = useMemo(
1191
- () => ({
1192
- tags,
1193
- injectedTags,
1194
- selectedOfferDetails,
1195
- eventContextTags,
1196
- forwardedTags,
1197
- }),
1198
- [tags, injectedTags, selectedOfferDetails, eventContextTags, forwardedTags],
1199
- );
1200
-
1201
- // Memoized TagList components with optimized props
1202
- const titleTagList = useMemo(
1203
- () => (
1204
- <MemoizedTagList
1205
- {...tagListStableProps}
1206
- {...tagListDynamicProps}
1207
- onTagSelect={handleTagSelectTitle}
1208
- />
1209
- ),
1210
- [tagListStableProps, tagListDynamicProps, handleTagSelectTitle],
1211
- );
1212
-
1213
- const messageTagList = useMemo(
1214
- () => (
1215
- <MemoizedTagList
1216
- {...tagListStableProps}
1217
- {...tagListDynamicProps}
1218
- onTagSelect={handleTagSelectMessage}
1219
- />
1220
- ),
1221
- [tagListStableProps, tagListDynamicProps, handleTagSelectMessage],
1222
- );
1223
-
1224
- const isSaveDisabled = useMemo(
1225
- () => (
1226
- createTemplateInProgress
1227
- || editTemplateInProgress
1228
- || isUploadingAsset
1229
- || isImageValidating
1230
- || isImageUploading
1231
- || isBrandIconValidating
1232
- || isBrandIconUploading
1233
- || isAddingButton
1234
- || (isFullMode && !fieldCompletion.templateName)
1235
- || !fieldCompletion.notificationTitle
1236
- || !fieldCompletion.message
1237
- || !accountId
1238
- || (
1239
- mediaType === WEBPUSH_MEDIA_TYPES.IMAGE
1240
- && (
1241
- (imageUploadMethod === IMAGE_UPLOAD_METHODS.UPLOAD_IMAGE && !imageSrc)
1242
- || (imageUploadMethod === IMAGE_UPLOAD_METHODS.ADD_IMAGE_URL && !imageSrc)
1243
- )
1244
- )
1245
- || (
1246
- brandIconOption !== BRAND_ICON_OPTIONS.DONT_SHOW
1247
- && (
1248
- (brandIconOption === BRAND_ICON_OPTIONS.UPLOAD_IMAGE && !brandIconSrc)
1249
- || (brandIconOption === BRAND_ICON_OPTIONS.ADD_IMAGE_URL && !brandIconSrc)
1250
- )
1251
- )
1252
- || (onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL && (!redirectUrl.trim() || redirectUrlError))
1253
- ),
1254
- [
1255
- createTemplateInProgress,
1256
- editTemplateInProgress,
1257
- isUploadingAsset,
1258
- isImageValidating,
1259
- isImageUploading,
1260
- isBrandIconValidating,
1261
- isBrandIconUploading,
1262
- isAddingButton,
1263
- isFullMode,
1264
- fieldCompletion.templateName,
1265
- fieldCompletion.notificationTitle,
1266
- fieldCompletion.message,
1267
- accountId,
1268
- mediaType,
1269
- imageUploadMethod,
1270
- imageSrc,
1271
- brandIconOption,
1272
- brandIconSrc,
1273
- onClickBehaviour,
1274
- redirectUrl,
1275
- redirectUrlError,
1276
- ],
1277
- );
1278
-
1279
- const errorText = useMemo(() => {
1280
- const error = isEditMode ? editTemplateError : createTemplateError;
1281
- if (!error) {
1282
- return '';
1283
- }
1284
- if (typeof error === 'string') {
1285
- return error;
1286
- }
1287
- if (error?.message) {
1288
- return error.message;
1289
- }
1290
- if (typeof error?.toJS === 'function') {
1291
- const errorObject = error.toJS();
1292
- if (typeof errorObject === 'string') {
1293
- return errorObject;
1294
- }
1295
- if (errorObject?.message) {
1296
- return errorObject.message;
1297
- }
1298
- try {
1299
- return JSON.stringify(errorObject);
1300
- } catch (err) {
1301
- return '';
1302
- }
1303
- }
1304
- try {
1305
- return JSON.stringify(error);
1306
- } catch (err) {
1307
- return '';
1308
- }
1309
- }, [createTemplateError, editTemplateError, isEditMode]);
1310
-
1311
- const accountErrorText = useMemo(
1312
- () => (!accountId ? formatMessage(messages.accountRequired) : ''),
1313
- [accountId, formatMessage],
1314
- );
1315
-
1316
- return (
1317
- <CapRow className="webpush-container">
1318
- <CapColumn className="content-section" span={14}>
1319
- {isFullMode && (
1320
- <CapRow className="input-group creative-name-container">
1321
- <CapInput
1322
- id="webpush-template-name-input"
1323
- className="webpush-template-name-input"
1324
- label={formatMessage(messages.creativeName)}
1325
- placeholder={formatMessage(messages.creativeNamePlaceholder)}
1326
- value={templateName}
1327
- onChange={handleTemplateNameChange}
1328
- size="default"
1329
- status={templateNameError ? 'error' : ''}
1330
- help={
1331
- templateNameError
1332
- ? formatMessage(messages.emptyTemplateErrorMessage)
1333
- : ''
1334
- }
1335
- />
1336
- </CapRow>
1337
- )}
1338
- {isFullMode && <CapDivider />}
1339
- <CapRow className="creatives-webpush-title">
1340
- <CapRow className="tooltip-add-label-container webpush-title-taglist">
1341
- {titleTagList}
1342
- </CapRow>
1343
- <CapHeading type="h3" className="webpush-title">
1344
- <FormattedMessage {...messages.notificationTitle} />
1345
- </CapHeading>
1346
- <CapInput
1347
- id="webpush-notification-title-input"
1348
- value={notificationTitle}
1349
- onChange={handleNotificationTitleChange}
1350
- placeholder={formatMessage(messages.notificationTitlePlaceholder)}
1351
- size="default"
1352
- isRequired
1353
- maxLength={NOTIFICATION_TITLE_MAX_LENGTH}
1354
- errorMessage={
1355
- titleError && (
1356
- <CapError className="webpush-template-title-error">
1357
- {titleError}
1358
- </CapError>
1359
- )
1360
- }
1361
- />
1362
- {renderTitleCharacterCount()}
1363
- </CapRow>
1364
- <CapRow className="creatives-webpush-message">
1365
- <CapRow className="tooltip-add-label-container webpush-message-taglist">
1366
- {messageTagList}
1367
- </CapRow>
1368
- <CapHeading type="h3" className="webpush-message">
1369
- <FormattedMessage {...messages.message} />
1370
- </CapHeading>
1371
- <CapInput.TextArea
1372
- id="webpush-message-input"
1373
- value={message}
1374
- onChange={handleMessageChange}
1375
- placeholder={formatMessage(messages.messagePlaceholder)}
1376
- size="default"
1377
- isRequired
1378
- autosize={{ minRows: 3, maxRows: 5 }}
1379
- errorMessage={
1380
- messageError && (
1381
- <CapError className="webpush-template-message-error">
1382
- {messageError}
1383
- </CapError>
1384
- )
1385
- }
1386
- />
1387
- {renderMessageCharacterCount()}
1388
- </CapRow>
1389
- <CapDivider className="webpush-message-divider" />
1390
- <CapRow className="creatives-webpush-media">
1391
- <CapHeading type="h3" className="webpush-media-type">
1392
- <FormattedMessage {...messages.mediaType} />
1393
- </CapHeading>
1394
- <CapSelect.CapCustomSelect
1395
- width="100%"
1396
- className="margin-t-4"
1397
- options={WEBPUSH_MEDIA_TYPES_OPTIONS}
1398
- value={mediaType}
1399
- onChange={handleMediaTypeChange}
1400
- disabled={isAnyUploadActive}
1401
- />
1402
- </CapRow>
1403
- {mediaType === WEBPUSH_MEDIA_TYPES.IMAGE && (
1404
- <>
1405
- <CapRow className="webpush-image-upload-method">
1406
- <CapRadioGroup
1407
- options={[
1408
- { value: IMAGE_UPLOAD_METHODS.UPLOAD_IMAGE, label: formatMessage(messages.uploadImage) },
1409
- { value: IMAGE_UPLOAD_METHODS.ADD_IMAGE_URL, label: formatMessage(messages.addImageUrl) },
1410
- ]}
1411
- value={imageUploadMethod}
1412
- onChange={handleImageUploadMethodChange}
1413
- disabled={isAnyUploadActive}
1414
- />
1415
- </CapRow>
1416
- {imageUploadMethod === IMAGE_UPLOAD_METHODS.UPLOAD_IMAGE && (
1417
- <CapRow
1418
- className="webpush-image-upload-section"
1419
- style={isMediaSectionLocked ? { pointerEvents: 'none', opacity: 0.5 } : undefined}
1420
- aria-disabled={isMediaSectionLocked}
1421
- >
1422
- {/* Note: image width/height validation will be ignored for the web push channel inside CapImageUpload */}
1423
- <CapImageUpload
1424
- allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1425
- imgSize={WEBPUSH_IMG_SIZE}
1426
- uploadAsset={uploadWebPushAsset}
1427
- isFullMode={isFullMode}
1428
- imageSrc={imageSrc}
1429
- updateImageSrc={setUpdateWebPushImageSrc}
1430
- updateOnReUpload={updateOnWebPushImageReUpload}
1431
- index={0}
1432
- className="cap-custom-image-upload"
1433
- key="webpush-uploaded-image"
1434
- imageData={webPush}
1435
- channel={WEBPUSH}
1436
- showReUploadButton
1437
- recommendedDimensions={WEBPUSH_RECOMMENDED_DIMENSIONS}
1438
- />
1439
- </CapRow>
1440
- )}
1441
- {imageUploadMethod === IMAGE_UPLOAD_METHODS.ADD_IMAGE_URL && (
1442
- <CapRow
1443
- className="webpush-image-url-section"
1444
- style={isMediaSectionLocked ? { pointerEvents: 'none', opacity: 0.5 } : undefined}
1445
- aria-disabled={isMediaSectionLocked}
1446
- >
1447
- <CapImageUrlUpload
1448
- uploadAsset={uploadWebPushAsset}
1449
- imgSize={WEBPUSH_IMG_SIZE}
1450
- recommendedDimensions={WEBPUSH_RECOMMENDED_DIMENSIONS}
1451
- sizeLabel={formatMessage(messages.sizeLimit)}
1452
- formatLabel={formatMessage(messages.formatTypes)}
1453
- imageUrl={imageUrl}
1454
- imageSrc={imageSrc}
1455
- onUrlChange={handleImageUrlChange}
1456
- onValidationStateChange={handleImageValidationStateChange}
1457
- onUploadStateChange={handleImageUploadStateChange}
1458
- isExternalUploading={isImageFieldActive && isUploadingAsset}
1459
- disabled={isMediaSectionLocked}
1460
- className="webpush-image-url-upload"
1461
- fileNamePrefix="webpush-image"
1462
- />
1463
- </CapRow>
1464
- )}
1465
- </>
1466
- )}
1467
- <CapDivider />
1468
- <CapRow className="creatives-webpush-brand-icon">
1469
- <CapHeading type="h3" className="webpush-brand-icon">
1470
- <FormattedMessage {...messages.brandIconLogo} />
1471
- </CapHeading>
1472
- <CapRadioGroup
1473
- options={brandIconOptions}
1474
- value={brandIconOption}
1475
- onChange={handleBrandIconChange}
1476
- disabled={isAnyUploadActive}
1477
- />
1478
- </CapRow>
1479
- {brandIconOption === BRAND_ICON_OPTIONS.UPLOAD_IMAGE && (
1480
- <CapRow
1481
- className="webpush-brand-icon-upload-section"
1482
- style={isBrandIconSectionLocked ? { pointerEvents: 'none', opacity: 0.5 } : undefined}
1483
- aria-disabled={isBrandIconSectionLocked}
1484
- >
1485
- {/* Note: image width/height validation will be ignored for the web push channel inside CapImageUpload */}
1486
- <CapImageUpload
1487
- allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1488
- imgSize={WEBPUSH_BRAND_ICON_SIZE}
1489
- uploadAsset={uploadWebPushBrandIconAsset}
1490
- isFullMode={isFullMode}
1491
- imageSrc={brandIconSrc}
1492
- updateImageSrc={setUpdateWebPushBrandIconSrc}
1493
- updateOnReUpload={updateOnWebPushBrandIconReUpload}
1494
- index={1}
1495
- className="cap-custom-image-upload"
1496
- key="webpush-brand-icon-uploaded-image"
1497
- imageData={webPush}
1498
- channel={WEBPUSH_BRAND_ICON}
1499
- showReUploadButton
1500
- recommendedDimensions={WEBPUSH_BRAND_ICON_RECOMMENDED_DIMENSIONS}
1501
- />
1502
- </CapRow>
1503
- )}
1504
- {brandIconOption === BRAND_ICON_OPTIONS.ADD_IMAGE_URL && (
1505
- <CapRow
1506
- className="webpush-brand-icon-url-section"
1507
- style={isBrandIconSectionLocked ? { pointerEvents: 'none', opacity: 0.5 } : undefined}
1508
- aria-disabled={isBrandIconSectionLocked}
1509
- >
1510
- <CapImageUrlUpload
1511
- uploadAsset={uploadWebPushBrandIconAsset}
1512
- imgSize={WEBPUSH_BRAND_ICON_SIZE}
1513
- recommendedDimensions={WEBPUSH_BRAND_ICON_RECOMMENDED_DIMENSIONS}
1514
- sizeLabel={formatMessage(messages.sizeLimitBrandIcon)}
1515
- formatLabel={formatMessage(messages.formatTypes)}
1516
- imageUrl={brandIconUrl}
1517
- imageSrc={brandIconSrc}
1518
- onUrlChange={handleBrandIconUrlChange}
1519
- onValidationStateChange={handleBrandIconValidationStateChange}
1520
- onUploadStateChange={handleBrandIconUploadStateChange}
1521
- isExternalUploading={isBrandIconFieldActive && isUploadingAsset}
1522
- disabled={isBrandIconSectionLocked}
1523
- className="webpush-brand-icon-url-upload"
1524
- fileNamePrefix="webpush-brand-icon"
1525
- />
1526
- </CapRow>
1527
- )}
1528
- <CapDivider />
1529
- <CapRow className="creatives-webpush-buttons-links">
1530
- <CapHeading type="h3" className="webpush-buttons-links">
1531
- <FormattedMessage {...messages.buttonsAndLinks} />{' '}
1532
- <span className="optional-text">
1533
- <FormattedMessage {...messages.optional} />
1534
- </span>
1535
- </CapHeading>
1536
- <CapHeading type="h4" className="webpush-on-click-behaviour">
1537
- <FormattedMessage {...messages.onClickBehaviour} />
1538
- </CapHeading>
1539
- <CapRadioGroup
1540
- options={onClickBehaviourOptions}
1541
- value={onClickBehaviour}
1542
- onChange={handleOnClickBehaviourChange}
1543
- />
1544
- {onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL && (
1545
- <CapInput
1546
- id="webpush-redirect-url-input"
1547
- className="webpush-redirect-url-input"
1548
- placeholder={formatMessage(messages.enterUrl)}
1549
- value={redirectUrl}
1550
- onChange={handleRedirectUrlChange}
1551
- size="default"
1552
- status={redirectUrlError ? 'error' : ''}
1553
- help={redirectUrlError || ''}
1554
- />
1555
- )}
1556
- </CapRow>
1557
- <CapRow className="creatives-webpush-buttons-section">
1558
- <CapHeading type="h4" className="webpush-buttons-section-heading">
1559
- <FormattedMessage {...messages.buttons} />
1560
- </CapHeading>
1561
- <ButtonList
1562
- buttons={buttons}
1563
- onEdit={handleButtonEdit}
1564
- onDelete={handleButtonDelete}
1565
- onReorder={handleButtonReorder}
1566
- onAddPrimary={handleAddPrimaryButton}
1567
- onAddSecondary={handleAddSecondaryButton}
1568
- showAddPrimary={false}
1569
- showAddSecondary={false}
1570
- disabled={isAddFlow}
1571
- disableSecondaryButton={disableSecondaryAddButton}
1572
- isInlineFormVisible={isEditFlow}
1573
- inlineFormIndex={isEditFlow ? editingButtonIndex : null}
1574
- renderInlineForm={isEditFlow ? () => renderButtonForm(true) : null}
1575
- />
1576
- {isAddFlow && buttonBeingAdded && renderButtonForm(false)}
1577
- {(showAddPrimaryButton || showAddSecondaryButton || showDisabledSecondaryDuringPrimary) && (
1578
- <div className="button-add-controls">
1579
- {showAddPrimaryButton && (
1580
- <CapButton
1581
- type="flat"
1582
- onClick={handleAddPrimaryButton}
1583
- className="add-primary-button button-add-trigger"
1584
- icon="plus"
1585
- >
1586
- <FormattedMessage {...messages.addButton} />
1587
- </CapButton>
1588
- )}
1589
- {showAddSecondaryButton && (
1590
- <CapButton
1591
- type="flat"
1592
- onClick={handleAddSecondaryButton}
1593
- className="add-secondary-button button-add-trigger"
1594
- icon="plus"
1595
- >
1596
- <FormattedMessage {...messages.addButton} />
1597
- </CapButton>
1598
- )}
1599
- {showDisabledSecondaryDuringPrimary && (
1600
- <CapButton
1601
- type="flat"
1602
- className="add-secondary-button button-add-trigger"
1603
- icon="plus"
1604
- disabled
1605
- >
1606
- <FormattedMessage {...messages.addButton} />
1607
- </CapButton>
1608
- )}
1609
- </div>
1610
- )}
1611
- </CapRow>
1612
- <CapRow className="creatives-webpush-actions">
1613
- <CapButton
1614
- type="primary"
1615
- onClick={handleSave}
1616
- disabled={isSaveDisabled}
1617
- >
1618
- {formatMessage(messages.saveTemplate)}
1619
- </CapButton>
1620
- </CapRow>
1621
- {(createTemplateError || editTemplateError) && (
1622
- <CapRow>
1623
- <CapError className="webpush-template-error">
1624
- {errorText}
1625
- </CapError>
1626
- </CapRow>
1627
- )}
1628
- {accountErrorText && (
1629
- <CapRow>
1630
- <CapError className="webpush-template-account-error">
1631
- {accountErrorText}
1632
- </CapError>
1633
- </CapRow>
1634
- )}
1635
- </CapColumn>
1636
- <CapColumn className="preview-section" span={10}>
1637
- <WebPushPreview
1638
- notificationTitle={notificationTitle}
1639
- notificationBody={message}
1640
- url={previewUrl}
1641
- imageSrc={imageSrc}
1642
- brandIconSrc={brandIconSrc}
1643
- />
1644
- </CapColumn>
1645
- </CapRow>
1646
- );
1647
- };
1648
-
1649
- WebPushCreate.propTypes = {
1650
- isFullMode: PropTypes.bool,
1651
- handleClose: PropTypes.func,
1652
- intl: intlShape.isRequired,
1653
- webPushActions: PropTypes.object,
1654
- createTemplateInProgress: PropTypes.bool,
1655
- createTemplateError: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
1656
- editTemplateInProgress: PropTypes.bool,
1657
- editTemplateError: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
1658
- accountData: PropTypes.object,
1659
- webPush: PropTypes.object,
1660
- onCreateComplete: PropTypes.func,
1661
- getFormData: PropTypes.func,
1662
- isGetFormData: PropTypes.bool,
1663
- templateData: PropTypes.object,
1664
- creativesMode: PropTypes.string,
1665
- params: PropTypes.object,
1666
- globalActions: PropTypes.object,
1667
- location: PropTypes.object,
1668
- metaEntities: PropTypes.object,
1669
- injectedTags: PropTypes.object,
1670
- getDefaultTags: PropTypes.string,
1671
- supportedTags: PropTypes.array,
1672
- forwardedTags: PropTypes.object,
1673
- selectedOfferDetails: PropTypes.array,
1674
- eventContextTags: PropTypes.array,
1675
- templateActions: PropTypes.object,
1676
- };
1677
-
1678
- WebPushCreate.defaultProps = {
1679
- isFullMode: true,
1680
- handleClose: () => { },
1681
- webPushActions: {},
1682
- createTemplateInProgress: false,
1683
- createTemplateError: '',
1684
- accountData: {},
1685
- webPush: {},
1686
- onCreateComplete: () => { },
1687
- getFormData: null,
1688
- isGetFormData: false,
1689
- templateData: null,
1690
- creativesMode: 'createTemplate',
1691
- params: null,
1692
- globalActions: {},
1693
- location: null,
1694
- metaEntities: null,
1695
- injectedTags: {},
1696
- getDefaultTags: '',
1697
- supportedTags: [],
1698
- forwardedTags: {},
1699
- selectedOfferDetails: [],
1700
- eventContextTags: [],
1701
- templateActions: {},
1702
- };
1703
-
1704
- const mapStateToProps = createStructuredSelector({
1705
- webPush: makeSelectWebPush(),
1706
- createTemplateInProgress: makeSelectCreateTemplateInProgress(),
1707
- createTemplateError: makeSelectCreateError(),
1708
- editTemplateInProgress: makeSelectEditTemplateInProgress(),
1709
- editTemplateError: makeSelectEditError(),
1710
- metaEntities: makeSelectMetaEntities(),
1711
- injectedTags: setInjectedTags(),
1712
- Templates: makeSelectTemplates(),
1713
- accountData: createSelector(
1714
- (state) => state.get('templates'),
1715
- (templatesState) => {
1716
- if (!templatesState) {
1717
- return {};
1718
- }
1719
- const templates = templatesState.toJS();
1720
- return templates?.selectedWebPushAccount || {};
1721
- },
1722
- ),
1723
- });
1724
-
1725
- const mapDispatchToProps = (dispatch) => ({
1726
- webPushActions: bindActionCreators(actions, dispatch),
1727
- templateActions: bindActionCreators(templateActions, dispatch),
1728
- });
1729
-
1730
- const withWebPushSaga = injectSaga({
1731
- key: 'webPush',
1732
- saga: webPushSagas,
1733
- mode: DAEMON,
1734
- });
1735
-
1736
- const withTemplateSaga = injectSaga({
1737
- key: 'templates',
1738
- saga: v2TemplateSaga,
1739
- mode: DAEMON,
1740
- });
1741
-
1742
- const withReducer = injectReducer({
1743
- key: 'webPush',
1744
- reducer: webPushReducer,
1745
- });
1746
-
1747
- export default withCreatives({
1748
- WrappedComponent: injectIntl(WebPushCreate),
1749
- mapStateToProps,
1750
- mapDispatchToProps,
1751
- userAuth: true,
1752
- sagas: [withWebPushSaga, withTemplateSaga],
1753
- reducers: [withReducer],
1754
- });
1755
-