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

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 (215) 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 +210 -89
  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 +8 -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/index.js +1 -1
  38. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +1 -0
  39. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  40. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  41. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  43. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  44. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
  45. package/v2Components/HtmlEditor/components/PreviewPane/index.js +10 -11
  46. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  47. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +87 -62
  48. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
  49. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
  50. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +362 -0
  51. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  52. package/v2Components/HtmlEditor/constants.js +29 -20
  53. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  54. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  55. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  56. package/v2Components/HtmlEditor/index.js +1 -1
  57. package/v2Components/HtmlEditor/messages.js +95 -85
  58. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +99 -101
  59. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  60. package/v2Components/HtmlEditor/utils/validationAdapter.js +34 -41
  61. package/v2Components/MobilePushPreviewV2/index.js +32 -7
  62. package/v2Components/TemplatePreview/_templatePreview.scss +44 -24
  63. package/v2Components/TemplatePreview/index.js +47 -32
  64. package/v2Components/TemplatePreview/messages.js +4 -0
  65. package/v2Components/TestAndPreviewSlidebox/index.js +31 -25
  66. package/v2Containers/App/constants.js +0 -5
  67. package/v2Containers/BeeEditor/index.js +82 -80
  68. package/v2Containers/BeePopupEditor/constants.js +10 -0
  69. package/v2Containers/BeePopupEditor/index.js +193 -0
  70. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  71. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +0 -1
  72. package/v2Containers/CreativesContainer/SlideBoxContent.js +148 -120
  73. package/v2Containers/CreativesContainer/SlideBoxFooter.js +9 -3
  74. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -2
  75. package/v2Containers/CreativesContainer/constants.js +1 -2
  76. package/v2Containers/CreativesContainer/index.js +173 -193
  77. package/v2Containers/CreativesContainer/messages.js +4 -4
  78. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  79. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +36 -0
  80. package/v2Containers/Email/actions.js +7 -0
  81. package/v2Containers/Email/constants.js +5 -1
  82. package/v2Containers/Email/index.js +13 -0
  83. package/v2Containers/Email/messages.js +32 -0
  84. package/v2Containers/Email/reducer.js +12 -1
  85. package/v2Containers/Email/sagas.js +41 -6
  86. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  87. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1045 -0
  88. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +193 -7
  89. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  90. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  91. package/v2Containers/EmailWrapper/constants.js +2 -0
  92. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +436 -67
  93. package/v2Containers/EmailWrapper/index.js +99 -23
  94. package/v2Containers/EmailWrapper/messages.js +61 -1
  95. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +26 -1
  96. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +111 -77
  97. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  98. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  99. package/v2Containers/InApp/actions.js +7 -0
  100. package/v2Containers/InApp/constants.js +20 -4
  101. package/v2Containers/InApp/index.js +800 -357
  102. package/v2Containers/InApp/index.scss +4 -3
  103. package/v2Containers/InApp/messages.js +7 -3
  104. package/v2Containers/InApp/reducer.js +21 -3
  105. package/v2Containers/InApp/sagas.js +29 -9
  106. package/v2Containers/InApp/selectors.js +25 -5
  107. package/v2Containers/InApp/tests/index.test.js +154 -50
  108. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  109. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  110. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  111. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +162 -0
  112. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  113. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +9 -0
  114. package/v2Containers/InAppWrapper/constants.js +16 -0
  115. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  116. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  117. package/v2Containers/InAppWrapper/index.js +148 -0
  118. package/v2Containers/InAppWrapper/messages.js +49 -0
  119. package/v2Containers/InappAdvance/index.js +1099 -0
  120. package/v2Containers/InappAdvance/index.scss +10 -0
  121. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  122. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -3
  123. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -2
  124. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -25
  125. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -18
  126. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -46
  127. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +0 -4
  128. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -8
  129. package/v2Containers/TagList/index.js +67 -1
  130. package/v2Containers/Templates/ChannelTypeIllustration.js +1 -13
  131. package/v2Containers/Templates/_templates.scss +56 -200
  132. package/v2Containers/Templates/actions.js +1 -2
  133. package/v2Containers/Templates/constants.js +0 -1
  134. package/v2Containers/Templates/index.js +124 -277
  135. package/v2Containers/Templates/messages.js +4 -24
  136. package/v2Containers/Templates/reducer.js +0 -2
  137. package/v2Containers/Templates/tests/index.test.js +0 -10
  138. package/v2Containers/TemplatesV2/index.js +2 -3
  139. package/v2Containers/TemplatesV2/messages.js +0 -4
  140. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +35 -132
  141. package/v2Components/CapImageUrlUpload/constants.js +0 -19
  142. package/v2Components/CapImageUrlUpload/index.js +0 -455
  143. package/v2Components/CapImageUrlUpload/index.scss +0 -35
  144. package/v2Components/CapImageUrlUpload/messages.js +0 -47
  145. package/v2Containers/WebPush/Create/components/ButtonForm.js +0 -175
  146. package/v2Containers/WebPush/Create/components/ButtonItem.js +0 -101
  147. package/v2Containers/WebPush/Create/components/ButtonList.js +0 -144
  148. package/v2Containers/WebPush/Create/components/_buttons.scss +0 -246
  149. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +0 -554
  150. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +0 -607
  151. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +0 -633
  152. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +0 -666
  153. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +0 -74
  154. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +0 -80
  155. package/v2Containers/WebPush/Create/index.js +0 -1755
  156. package/v2Containers/WebPush/Create/index.scss +0 -123
  157. package/v2Containers/WebPush/Create/messages.js +0 -199
  158. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +0 -241
  159. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +0 -290
  160. package/v2Containers/WebPush/Create/preview/PreviewContent.js +0 -81
  161. package/v2Containers/WebPush/Create/preview/PreviewControls.js +0 -240
  162. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +0 -23
  163. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +0 -144
  164. package/v2Containers/WebPush/Create/preview/assets/Light.svg +0 -53
  165. package/v2Containers/WebPush/Create/preview/assets/Top.svg +0 -5
  166. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  167. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  168. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +0 -106
  169. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +0 -26
  170. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +0 -18
  171. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +0 -29
  172. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +0 -44
  173. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +0 -110
  174. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +0 -45
  175. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +0 -72
  176. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +0 -55
  177. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +0 -70
  178. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +0 -512
  179. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +0 -77
  180. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +0 -527
  181. package/v2Containers/WebPush/Create/preview/constants.js +0 -162
  182. package/v2Containers/WebPush/Create/preview/notification-container.scss +0 -104
  183. package/v2Containers/WebPush/Create/preview/preview.scss +0 -409
  184. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +0 -300
  185. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +0 -12
  186. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +0 -12
  187. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +0 -12
  188. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +0 -303
  189. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +0 -11
  190. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +0 -11
  191. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +0 -11
  192. package/v2Containers/WebPush/Create/preview/styles/_base.scss +0 -188
  193. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +0 -106
  194. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +0 -107
  195. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +0 -75
  196. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +0 -174
  197. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +0 -909
  198. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +0 -1077
  199. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +0 -723
  200. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +0 -943
  201. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +0 -128
  202. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +0 -121
  203. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +0 -144
  204. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +0 -127
  205. package/v2Containers/WebPush/Create/utils/urlValidation.js +0 -116
  206. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +0 -449
  207. package/v2Containers/WebPush/actions.js +0 -60
  208. package/v2Containers/WebPush/constants.js +0 -108
  209. package/v2Containers/WebPush/index.js +0 -2
  210. package/v2Containers/WebPush/reducer.js +0 -104
  211. package/v2Containers/WebPush/sagas.js +0 -119
  212. package/v2Containers/WebPush/selectors.js +0 -65
  213. package/v2Containers/WebPush/tests/reducer.test.js +0 -863
  214. package/v2Containers/WebPush/tests/sagas.test.js +0 -566
  215. 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
-