@capillarytech/creatives-library 8.0.239-alpha.0 → 8.0.239

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