@capillarytech/creatives-library 8.0.263 → 8.0.265
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/Android.png +0 -0
- package/assets/iOS.png +0 -0
- package/constants/unified.js +1 -3
- package/initialReducer.js +0 -2
- package/package.json +1 -1
- package/services/api.js +0 -15
- package/services/tests/api.test.js +0 -34
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +35 -17
- package/tests/integration/TemplateCreation/api-response.js +1 -31
- package/tests/integration/TemplateCreation/msw-handler.js +0 -2
- package/utils/common.js +0 -11
- package/utils/commonUtils.js +5 -28
- package/utils/tests/commonUtil.test.js +0 -224
- package/utils/tests/transformerUtils.test.js +0 -297
- package/utils/transformTemplateConfig.js +10 -0
- package/utils/transformerUtils.js +0 -40
- package/v2Components/CapDeviceContent/index.js +56 -61
- package/v2Components/CapImageUpload/constants.js +0 -2
- package/v2Components/CapImageUpload/index.js +16 -65
- package/v2Components/CapImageUpload/index.scss +1 -4
- package/v2Components/CapImageUpload/messages.js +1 -5
- package/v2Components/CapTagList/index.js +1 -6
- package/v2Components/CapTagListWithInput/index.js +1 -5
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
- package/v2Components/ErrorInfoNote/index.js +72 -402
- package/v2Components/ErrorInfoNote/messages.js +6 -32
- package/v2Components/ErrorInfoNote/style.scss +6 -278
- package/v2Components/FormBuilder/tests/index.test.js +4 -13
- package/v2Components/HtmlEditor/HTMLEditor.js +99 -418
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1882
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +16 -27
- package/v2Components/HtmlEditor/_htmlEditor.scss +45 -108
- package/v2Components/HtmlEditor/_index.lazy.scss +1 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +102 -23
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +140 -148
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +1 -9
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +6 -31
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -22
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +7 -4
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +45 -35
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +3 -1
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +6 -7
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +10 -7
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +43 -22
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +152 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +0 -18
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -36
- package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +34 -46
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +46 -52
- package/v2Components/HtmlEditor/constants.js +20 -45
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +16 -351
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
- package/v2Components/HtmlEditor/hooks/useValidation.js +56 -213
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +94 -102
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +45 -214
- package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +0 -134
- package/v2Components/HtmlEditor/utils/contentSanitizer.js +41 -40
- package/v2Components/HtmlEditor/utils/htmlValidator.js +72 -71
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +124 -158
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +25 -23
- package/v2Components/HtmlEditor/utils/validationAdapter.js +41 -66
- package/v2Components/MobilePushPreviewV2/index.js +7 -33
- package/v2Components/TemplatePreview/_templatePreview.scss +24 -55
- package/v2Components/TemplatePreview/index.js +32 -47
- package/v2Components/TemplatePreview/messages.js +0 -4
- package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +0 -1
- package/v2Containers/App/constants.js +0 -5
- package/v2Containers/BeeEditor/index.js +90 -172
- package/v2Containers/CreativesContainer/SlideBoxContent.js +53 -184
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -163
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -3
- package/v2Containers/CreativesContainer/constants.js +0 -4
- package/v2Containers/CreativesContainer/index.js +46 -408
- package/v2Containers/CreativesContainer/messages.js +0 -12
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +0 -210
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +2 -11
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +50 -342
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -103
- package/v2Containers/Email/actions.js +0 -7
- package/v2Containers/Email/constants.js +1 -5
- package/v2Containers/Email/index.js +36 -237
- package/v2Containers/Email/messages.js +0 -32
- package/v2Containers/Email/reducer.js +1 -12
- package/v2Containers/Email/sagas.js +7 -61
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -2
- package/v2Containers/Email/tests/reducer.test.js +0 -46
- package/v2Containers/Email/tests/sagas.test.js +29 -320
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +21 -211
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
- package/v2Containers/EmailWrapper/constants.js +0 -2
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +77 -629
- package/v2Containers/EmailWrapper/index.js +23 -103
- package/v2Containers/EmailWrapper/messages.js +1 -65
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +214 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -594
- package/v2Containers/InApp/actions.js +0 -7
- package/v2Containers/InApp/constants.js +4 -20
- package/v2Containers/InApp/index.js +359 -802
- package/v2Containers/InApp/index.scss +3 -4
- package/v2Containers/InApp/messages.js +3 -7
- package/v2Containers/InApp/reducer.js +3 -21
- package/v2Containers/InApp/sagas.js +9 -29
- package/v2Containers/InApp/selectors.js +5 -25
- package/v2Containers/InApp/tests/index.test.js +50 -154
- package/v2Containers/InApp/tests/reducer.test.js +0 -34
- package/v2Containers/InApp/tests/sagas.test.js +9 -61
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +0 -3
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +0 -2
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +0 -2
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +0 -9
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -12
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +0 -4
- package/v2Containers/TagList/index.js +19 -62
- package/v2Containers/Templates/ChannelTypeIllustration.js +1 -13
- package/v2Containers/Templates/_templates.scss +1 -265
- package/v2Containers/Templates/actions.js +1 -2
- package/v2Containers/Templates/constants.js +0 -1
- package/v2Containers/Templates/index.js +38 -363
- package/v2Containers/Templates/messages.js +0 -28
- package/v2Containers/Templates/reducer.js +0 -2
- package/v2Containers/Templates/tests/index.test.js +0 -10
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +2 -4
- package/v2Containers/TemplatesV2/index.js +7 -15
- package/v2Containers/TemplatesV2/messages.js +0 -4
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +0 -34
- package/utils/imageUrlUpload.js +0 -141
- package/v2Components/CapImageUrlUpload/constants.js +0 -26
- package/v2Components/CapImageUrlUpload/index.js +0 -365
- package/v2Components/CapImageUrlUpload/index.scss +0 -35
- package/v2Components/CapImageUrlUpload/messages.js +0 -47
- package/v2Components/ErrorInfoNote/constants.js +0 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -870
- package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +0 -6
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -281
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -295
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
- package/v2Components/HtmlEditor/utils/validationConstants.js +0 -38
- package/v2Components/MobilePushPreviewV2/constants.js +0 -6
- package/v2Containers/BeePopupEditor/_beePopupEditor.scss +0 -14
- package/v2Containers/BeePopupEditor/constants.js +0 -10
- package/v2Containers/BeePopupEditor/index.js +0 -194
- package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1246
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +0 -2472
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +0 -520
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +0 -956
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
- package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
- package/v2Containers/InApp/tests/selectors.test.js +0 -612
- package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -151
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -23
- package/v2Containers/InAppWrapper/constants.js +0 -16
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
- package/v2Containers/InAppWrapper/index.js +0 -148
- package/v2Containers/InAppWrapper/messages.js +0 -49
- package/v2Containers/InappAdvance/index.js +0 -1099
- package/v2Containers/InappAdvance/index.scss +0 -10
- package/v2Containers/InappAdvance/tests/index.test.js +0 -448
- package/v2Containers/WebPush/Create/components/BrandIconSection.js +0 -108
- package/v2Containers/WebPush/Create/components/ButtonForm.js +0 -172
- package/v2Containers/WebPush/Create/components/ButtonItem.js +0 -101
- package/v2Containers/WebPush/Create/components/ButtonList.js +0 -145
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.js +0 -164
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +0 -463
- package/v2Containers/WebPush/Create/components/FormActions.js +0 -54
- package/v2Containers/WebPush/Create/components/FormActions.test.js +0 -163
- package/v2Containers/WebPush/Create/components/MediaSection.js +0 -142
- package/v2Containers/WebPush/Create/components/MediaSection.test.js +0 -341
- package/v2Containers/WebPush/Create/components/MessageSection.js +0 -103
- package/v2Containers/WebPush/Create/components/MessageSection.test.js +0 -268
- package/v2Containers/WebPush/Create/components/NotificationTitleSection.js +0 -87
- package/v2Containers/WebPush/Create/components/NotificationTitleSection.test.js +0 -210
- package/v2Containers/WebPush/Create/components/TemplateNameSection.js +0 -54
- package/v2Containers/WebPush/Create/components/TemplateNameSection.test.js +0 -143
- package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +0 -86
- package/v2Containers/WebPush/Create/components/__snapshots__/FormActions.test.js.snap +0 -16
- package/v2Containers/WebPush/Create/components/__snapshots__/MediaSection.test.js.snap +0 -41
- package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +0 -54
- package/v2Containers/WebPush/Create/components/__snapshots__/NotificationTitleSection.test.js.snap +0 -37
- package/v2Containers/WebPush/Create/components/__snapshots__/TemplateNameSection.test.js.snap +0 -21
- package/v2Containers/WebPush/Create/components/_buttons.scss +0 -246
- package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +0 -554
- package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +0 -607
- package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +0 -633
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +0 -666
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +0 -74
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +0 -78
- package/v2Containers/WebPush/Create/hooks/useButtonManagement.js +0 -138
- package/v2Containers/WebPush/Create/hooks/useButtonManagement.test.js +0 -406
- package/v2Containers/WebPush/Create/hooks/useCharacterCount.js +0 -30
- package/v2Containers/WebPush/Create/hooks/useCharacterCount.test.js +0 -151
- package/v2Containers/WebPush/Create/hooks/useImageUpload.js +0 -104
- package/v2Containers/WebPush/Create/hooks/useImageUpload.test.js +0 -538
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -122
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -633
- package/v2Containers/WebPush/Create/index.js +0 -1148
- package/v2Containers/WebPush/Create/index.scss +0 -134
- package/v2Containers/WebPush/Create/messages.js +0 -211
- package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +0 -228
- package/v2Containers/WebPush/Create/preview/NotificationContainer.js +0 -294
- package/v2Containers/WebPush/Create/preview/PreviewContent.js +0 -90
- package/v2Containers/WebPush/Create/preview/PreviewControls.js +0 -305
- package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +0 -25
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +0 -155
- package/v2Containers/WebPush/Create/preview/assets/Light.svg +0 -53
- package/v2Containers/WebPush/Create/preview/assets/Top.svg +0 -5
- package/v2Containers/WebPush/Create/preview/assets/android-arrow-down.svg +0 -9
- package/v2Containers/WebPush/Create/preview/assets/android-arrow-up.svg +0 -9
- package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +0 -106
- package/v2Containers/WebPush/Create/preview/assets/iOS.svg +0 -26
- package/v2Containers/WebPush/Create/preview/assets/macos-arrow-down-icon.svg +0 -9
- package/v2Containers/WebPush/Create/preview/assets/macos-triple-dot-icon.svg +0 -9
- package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +0 -18
- package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +0 -29
- package/v2Containers/WebPush/Create/preview/assets/windows-close-icon.svg +0 -9
- package/v2Containers/WebPush/Create/preview/assets/windows-triple-dot-icon.svg +0 -9
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +0 -51
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +0 -145
- package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +0 -45
- package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +0 -68
- package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +0 -61
- package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +0 -99
- package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +0 -733
- package/v2Containers/WebPush/Create/preview/components/tests/WindowsChromeExpanded.test.js +0 -571
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +0 -85
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/WindowsChromeExpanded.test.js.snap +0 -81
- package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +0 -50
- package/v2Containers/WebPush/Create/preview/constants.js +0 -637
- package/v2Containers/WebPush/Create/preview/notification-container.scss +0 -79
- package/v2Containers/WebPush/Create/preview/preview.scss +0 -358
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +0 -370
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +0 -12
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +0 -12
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +0 -12
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +0 -47
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +0 -11
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +0 -11
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +0 -11
- package/v2Containers/WebPush/Create/preview/styles/_base.scss +0 -207
- package/v2Containers/WebPush/Create/preview/styles/_ios.scss +0 -153
- package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +0 -107
- package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +0 -101
- package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +0 -229
- package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +0 -909
- package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +0 -1081
- package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +0 -723
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +0 -1327
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +0 -131
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +0 -112
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +0 -144
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +0 -129
- package/v2Containers/WebPush/Create/utils/payloadBuilder.js +0 -96
- package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +0 -396
- package/v2Containers/WebPush/Create/utils/previewUtils.js +0 -89
- package/v2Containers/WebPush/Create/utils/urlValidation.js +0 -115
- package/v2Containers/WebPush/Create/utils/urlValidation.test.js +0 -449
- package/v2Containers/WebPush/Create/utils/validation.js +0 -75
- package/v2Containers/WebPush/Create/utils/validation.test.js +0 -283
- package/v2Containers/WebPush/actions.js +0 -60
- package/v2Containers/WebPush/constants.js +0 -132
- package/v2Containers/WebPush/index.js +0 -2
- package/v2Containers/WebPush/reducer.js +0 -104
- package/v2Containers/WebPush/sagas.js +0 -119
- package/v2Containers/WebPush/selectors.js +0 -65
- package/v2Containers/WebPush/tests/reducer.test.js +0 -863
- package/v2Containers/WebPush/tests/sagas.test.js +0 -566
- package/v2Containers/WebPush/tests/selectors.test.js +0 -960
|
@@ -1,1246 +0,0 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useState, useEffect, useMemo, useCallback, useRef, useImperativeHandle, forwardRef,
|
|
3
|
-
} from 'react';
|
|
4
|
-
import PropTypes from 'prop-types';
|
|
5
|
-
import { injectIntl, FormattedMessage } from 'react-intl';
|
|
6
|
-
import isEmpty from 'lodash/isEmpty';
|
|
7
|
-
import get from 'lodash/get';
|
|
8
|
-
import _ from 'lodash';
|
|
9
|
-
import { CAP_SPACE_16 } from '@capillarytech/cap-ui-library/styled/variables';
|
|
10
|
-
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
11
|
-
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
12
|
-
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
13
|
-
import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
14
|
-
import HTMLEditor from '../../../v2Components/HtmlEditor';
|
|
15
|
-
import CapTagListWithInput from '../../../v2Components/CapTagListWithInput';
|
|
16
|
-
import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
|
|
17
|
-
import { validateLiquidTemplateContent } from '../../../utils/commonUtils';
|
|
18
|
-
import { hasLiquidSupportFeature, isEmailUnsubscribeTagMandatory } from '../../../utils/common';
|
|
19
|
-
import history from '../../../utils/history';
|
|
20
|
-
import messages from '../messages';
|
|
21
|
-
import emailMessages from '../../Email/messages';
|
|
22
|
-
import { validateTags } from '../../../utils/tagValidations';
|
|
23
|
-
import {
|
|
24
|
-
TAG, EMBEDDED, DEFAULT, FULL, LIBRARY,
|
|
25
|
-
} from '../../Whatsapp/constants';
|
|
26
|
-
import { EMAIL } from '../../CreativesContainer/constants';
|
|
27
|
-
import { OUTBOUND } from '../../../v2Components/FormBuilder/constants';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* EmailHTMLEditor Component
|
|
31
|
-
*
|
|
32
|
-
* IMPORTANT: This component is ONLY used when supportCKEditor flag is FALSE (new flow).
|
|
33
|
-
* When supportCKEditor is TRUE, the existing Email component with FormBuilder is used (legacy flow).
|
|
34
|
-
*
|
|
35
|
-
* A completely self-contained component for Email HTML Editor that handles:
|
|
36
|
-
* - Tag loading and management
|
|
37
|
-
* - Tag validation
|
|
38
|
-
* - Content editing with HTMLEditor
|
|
39
|
-
* - Template data extraction for edit mode
|
|
40
|
-
* - Save logic (full mode & library mode) with liquid validation
|
|
41
|
-
* - API calls via Email actions/sagas
|
|
42
|
-
*
|
|
43
|
-
* This component is independent and reusable, similar to Whatsapp and InApp channels.
|
|
44
|
-
*/
|
|
45
|
-
const EmailHTMLEditor = (props) => {
|
|
46
|
-
const {
|
|
47
|
-
intl,
|
|
48
|
-
location,
|
|
49
|
-
params,
|
|
50
|
-
getDefaultTags,
|
|
51
|
-
supportedTags,
|
|
52
|
-
metaEntities,
|
|
53
|
-
injectedTags,
|
|
54
|
-
globalActions,
|
|
55
|
-
loadingTags,
|
|
56
|
-
eventContextTags,
|
|
57
|
-
forwardedTags,
|
|
58
|
-
selectedOfferDetails,
|
|
59
|
-
currentOrgDetails,
|
|
60
|
-
isReadOnly = false,
|
|
61
|
-
fetchingLiquidTags = false,
|
|
62
|
-
createTemplateInProgress = false,
|
|
63
|
-
fetchingCmsData = false,
|
|
64
|
-
// Email Redux state
|
|
65
|
-
Email,
|
|
66
|
-
// Email actions for API calls
|
|
67
|
-
emailActions,
|
|
68
|
-
// Full mode props
|
|
69
|
-
isFullMode,
|
|
70
|
-
templateName,
|
|
71
|
-
showTemplateName,
|
|
72
|
-
isGetFormData,
|
|
73
|
-
getFormdata,
|
|
74
|
-
// Library mode props
|
|
75
|
-
templateData: templateDataProp,
|
|
76
|
-
// Uploaded content from zip file
|
|
77
|
-
EmailLayout,
|
|
78
|
-
// Liquid validation
|
|
79
|
-
getLiquidTags,
|
|
80
|
-
showLiquidErrorInFooter,
|
|
81
|
-
onValidationFail,
|
|
82
|
-
// Preview/Test
|
|
83
|
-
// Parent loading control
|
|
84
|
-
setIsLoadingContent,
|
|
85
|
-
forwardedRef,
|
|
86
|
-
// Module type for unsubscribe tag validation
|
|
87
|
-
moduleType,
|
|
88
|
-
// Validation state callback
|
|
89
|
-
onHtmlEditorValidationStateChange,
|
|
90
|
-
} = props;
|
|
91
|
-
|
|
92
|
-
const { formatMessage } = intl;
|
|
93
|
-
|
|
94
|
-
// State for content and subject
|
|
95
|
-
const [htmlContent, setHtmlContent] = useState('');
|
|
96
|
-
const [loadedHtmlContent, setLoadedHtmlContent] = useState(''); // Stable content for HTMLEditor initialization
|
|
97
|
-
const [subject, setSubject] = useState('');
|
|
98
|
-
const [subjectError, setSubjectError] = useState('');
|
|
99
|
-
// State for template name (extracted from template data in Edit mode)
|
|
100
|
-
const [extractedTemplateName, setExtractedTemplateName] = useState('');
|
|
101
|
-
const [tagValidationError, setTagValidationError] = useState(null);
|
|
102
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
103
|
-
const [errorsAcknowledged, setErrorsAcknowledged] = useState(false); // Track if user has acknowledged errors by clicking redirection icon
|
|
104
|
-
// State for API validation errors (from validateLiquidTemplateContent)
|
|
105
|
-
const [apiValidationErrors, setApiValidationErrors] = useState({
|
|
106
|
-
liquidErrors: [],
|
|
107
|
-
standardErrors: [],
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Refs for tracking initialization and previous values
|
|
111
|
-
const contentInitializedRef = useRef(false);
|
|
112
|
-
const subjectInitializedRef = useRef(false);
|
|
113
|
-
const lastTemplateIdRef = useRef(null);
|
|
114
|
-
const fetchingTemplateIdRef = useRef(null); // Track which template we're currently fetching
|
|
115
|
-
const prevIsGetFormDataRef = useRef(false);
|
|
116
|
-
const prevErrorCountRef = useRef(0); // Track previous error count to detect new errors
|
|
117
|
-
const handleSaveRef = useRef(null);
|
|
118
|
-
|
|
119
|
-
// Ref to HTMLEditor to access validation state
|
|
120
|
-
const htmlEditorRef = useRef(null);
|
|
121
|
-
|
|
122
|
-
// Compute tags directly from metaEntities (same approach as Email component)
|
|
123
|
-
// Use deep equality check to prevent TagList from re-processing when tags haven't actually changed
|
|
124
|
-
// This fixes the slow expansion and React Intl errors when clicking nested tags
|
|
125
|
-
const tags = useMemo(() => {
|
|
126
|
-
let tagList = get(metaEntities, 'tags.standard', []);
|
|
127
|
-
const { type, module } = location?.query || {};
|
|
128
|
-
if (type === EMBEDDED && module === LIBRARY && !getDefaultTags) {
|
|
129
|
-
tagList = supportedTags || [];
|
|
130
|
-
}
|
|
131
|
-
return tagList;
|
|
132
|
-
}, [metaEntities, supportedTags, location, getDefaultTags]);
|
|
133
|
-
|
|
134
|
-
// Expose method to get formData for TestAndPreviewSlidebox
|
|
135
|
-
useImperativeHandle(forwardedRef, () => ({
|
|
136
|
-
getFormDataForPreview: () => {
|
|
137
|
-
const baseLanguage = get(currentOrgDetails, 'basic_details.base_language', 'en');
|
|
138
|
-
return {
|
|
139
|
-
"0": {
|
|
140
|
-
[baseLanguage]: {
|
|
141
|
-
'template-content': htmlContent || '',
|
|
142
|
-
'is_drag_drop': false,
|
|
143
|
-
},
|
|
144
|
-
activeTab: baseLanguage,
|
|
145
|
-
selectedLanguages: [baseLanguage],
|
|
146
|
-
base: true,
|
|
147
|
-
},
|
|
148
|
-
'template-subject': subject || '',
|
|
149
|
-
};
|
|
150
|
-
},
|
|
151
|
-
getContentForPreview: () => htmlContent || '',
|
|
152
|
-
getValidationState: () => {
|
|
153
|
-
if (htmlEditorRef.current && htmlEditorRef.current.getValidation) {
|
|
154
|
-
return htmlEditorRef.current.getValidation();
|
|
155
|
-
}
|
|
156
|
-
return null;
|
|
157
|
-
},
|
|
158
|
-
isContentEmpty: () => {
|
|
159
|
-
if (htmlEditorRef.current && htmlEditorRef.current.isContentEmpty) {
|
|
160
|
-
return htmlEditorRef.current.isContentEmpty();
|
|
161
|
-
}
|
|
162
|
-
return !htmlContent || htmlContent.trim() === '';
|
|
163
|
-
},
|
|
164
|
-
getIssueCounts: () => {
|
|
165
|
-
if (htmlEditorRef.current && htmlEditorRef.current.getIssueCounts) {
|
|
166
|
-
return htmlEditorRef.current.getIssueCounts();
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
html: 0, label: 0, liquid: 0, total: 0,
|
|
170
|
-
};
|
|
171
|
-
},
|
|
172
|
-
}), [htmlContent, subject, currentOrgDetails]);
|
|
173
|
-
|
|
174
|
-
// Check if liquid support is enabled
|
|
175
|
-
const isLiquidEnabled = hasLiquidSupportFeature();
|
|
176
|
-
|
|
177
|
-
// Detect edit mode
|
|
178
|
-
const hasParamsId = params?.id || location?.query?.id || location?.params?.id || location?.pathname?.includes('/edit/');
|
|
179
|
-
const currentTemplateId = templateDataProp?._id || params?.id || location?.query?.id || location?.params?.id
|
|
180
|
-
|| location?.pathname?.match(/\/edit\/([^/]+)/)?.[1];
|
|
181
|
-
const isEditMode = !!currentTemplateId || !!hasParamsId;
|
|
182
|
-
|
|
183
|
-
// Load tags on component mount
|
|
184
|
-
useEffect(() => {
|
|
185
|
-
const { type, module } = location?.query || {};
|
|
186
|
-
const isEmbedded = type === EMBEDDED;
|
|
187
|
-
const query = {
|
|
188
|
-
layout: EMAIL,
|
|
189
|
-
type: TAG,
|
|
190
|
-
context: isEmbedded ? module : DEFAULT,
|
|
191
|
-
embedded: isEmbedded ? type : FULL,
|
|
192
|
-
};
|
|
193
|
-
if (getDefaultTags) {
|
|
194
|
-
query.context = getDefaultTags;
|
|
195
|
-
}
|
|
196
|
-
if (globalActions && globalActions.fetchSchemaForEntity) {
|
|
197
|
-
globalActions.fetchSchemaForEntity(query);
|
|
198
|
-
}
|
|
199
|
-
}, []);
|
|
200
|
-
|
|
201
|
-
// Initialize content from EmailLayout (uploaded zip) in create mode
|
|
202
|
-
useEffect(() => {
|
|
203
|
-
// Only check EmailLayout in create mode (not edit mode)
|
|
204
|
-
if (isEditMode) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Check if EmailLayout has content from zip upload
|
|
209
|
-
if (EmailLayout) {
|
|
210
|
-
// EmailLayout can be a string (HTML content) or an object with html property
|
|
211
|
-
const uploadedContent = typeof EmailLayout === 'string'
|
|
212
|
-
? EmailLayout
|
|
213
|
-
: (EmailLayout.html || EmailLayout.content || '');
|
|
214
|
-
|
|
215
|
-
if (uploadedContent) {
|
|
216
|
-
// IMPORTANT: Set both htmlContent and loadedHtmlContent for ZIP upload
|
|
217
|
-
// loadedHtmlContent is used by HTMLEditor's initialContent prop
|
|
218
|
-
setHtmlContent(uploadedContent);
|
|
219
|
-
setLoadedHtmlContent(uploadedContent);
|
|
220
|
-
setIsLoading(false);
|
|
221
|
-
if (setIsLoadingContent) {
|
|
222
|
-
setIsLoadingContent(false);
|
|
223
|
-
}
|
|
224
|
-
} else {
|
|
225
|
-
// No uploaded content, stop loading
|
|
226
|
-
setIsLoading(false);
|
|
227
|
-
if (setIsLoadingContent) {
|
|
228
|
-
setIsLoadingContent(false);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
} else {
|
|
232
|
-
// No EmailLayout, stop loading
|
|
233
|
-
setIsLoading(false);
|
|
234
|
-
if (setIsLoadingContent) {
|
|
235
|
-
setIsLoadingContent(false);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}, [EmailLayout, isEditMode, setIsLoadingContent]);
|
|
239
|
-
|
|
240
|
-
// Edit mode: Extract template data and load content/subject
|
|
241
|
-
useEffect(() => {
|
|
242
|
-
if (!isEditMode) {
|
|
243
|
-
// Create mode: stop loading immediately
|
|
244
|
-
setIsLoading(false);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const templateDataFromRedux = Email?.templateDetails || Email?.BEETemplate;
|
|
249
|
-
const isTemplateLoading = Email?.getTemplateDetailsInProgress || Email?.fetchingCmsData;
|
|
250
|
-
|
|
251
|
-
// Check if template ID changed (switching templates)
|
|
252
|
-
const templateIdChanged = currentTemplateId
|
|
253
|
-
&& lastTemplateIdRef.current
|
|
254
|
-
&& currentTemplateId !== lastTemplateIdRef.current;
|
|
255
|
-
|
|
256
|
-
// Reset refs when switching templates
|
|
257
|
-
if (templateIdChanged) {
|
|
258
|
-
contentInitializedRef.current = false;
|
|
259
|
-
subjectInitializedRef.current = false;
|
|
260
|
-
lastTemplateIdRef.current = currentTemplateId;
|
|
261
|
-
setHtmlContent('');
|
|
262
|
-
setSubject('');
|
|
263
|
-
setExtractedTemplateName('');
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Set last template ID on first load
|
|
267
|
-
if (currentTemplateId && !lastTemplateIdRef.current) {
|
|
268
|
-
lastTemplateIdRef.current = currentTemplateId;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Check if templateDataProp has complete data
|
|
272
|
-
// Check if templateDataProp has complete data (must have actual content string)
|
|
273
|
-
const hasCompleteTemplateData = templateDataProp && (
|
|
274
|
-
(templateDataProp.emailBody || templateDataProp.html_content || templateDataProp['template-content'])
|
|
275
|
-
|| (templateDataProp.base && (templateDataProp.base.html_content || templateDataProp.base.emailBody || templateDataProp.base['template-content']))
|
|
276
|
-
|| (templateDataProp.versions?.base && (
|
|
277
|
-
// Check active tab content in versions
|
|
278
|
-
(() => {
|
|
279
|
-
const { base } = templateDataProp.versions;
|
|
280
|
-
const activeTab = base.activeTab || 'en';
|
|
281
|
-
const langData = base[activeTab] || {};
|
|
282
|
-
return langData['template-content'] || langData.html_content || base.html_content || base.emailBody;
|
|
283
|
-
})()
|
|
284
|
-
))
|
|
285
|
-
);
|
|
286
|
-
if (hasCompleteTemplateData && !contentInitializedRef.current) {
|
|
287
|
-
let extractedContent = '';
|
|
288
|
-
let extractedSubject = '';
|
|
289
|
-
let extractedName = '';
|
|
290
|
-
|
|
291
|
-
if (templateDataProp.base) {
|
|
292
|
-
extractedContent = templateDataProp.base['template-content'] || templateDataProp.base.html_content || templateDataProp.base.emailBody || '';
|
|
293
|
-
extractedSubject = templateDataProp.base.subject || templateDataProp.base.emailSubject || '';
|
|
294
|
-
} else if (templateDataProp.versions && templateDataProp.versions.base) {
|
|
295
|
-
const { base } = templateDataProp.versions;
|
|
296
|
-
const activeTab = base.activeTab || 'en';
|
|
297
|
-
const languageData = base[activeTab] || {};
|
|
298
|
-
|
|
299
|
-
extractedContent = languageData['template-content']
|
|
300
|
-
|| languageData.html_content
|
|
301
|
-
|| base.html_content
|
|
302
|
-
|| base.emailBody
|
|
303
|
-
|| '';
|
|
304
|
-
|
|
305
|
-
extractedSubject = base.subject || base.emailSubject || '';
|
|
306
|
-
} else {
|
|
307
|
-
extractedContent = templateDataProp['template-content'] || templateDataProp.emailBody || templateDataProp.html_content || '';
|
|
308
|
-
extractedSubject = templateDataProp.emailSubject || templateDataProp.subject || '';
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Extract template name from templateDataProp
|
|
312
|
-
extractedName = templateDataProp.name || get(templateDataProp, 'versions.base.name') || '';
|
|
313
|
-
|
|
314
|
-
// IMPORTANT: Set both htmlContent and loadedHtmlContent
|
|
315
|
-
// loadedHtmlContent is used by HTMLEditor's initialContent prop
|
|
316
|
-
setHtmlContent(extractedContent);
|
|
317
|
-
setLoadedHtmlContent(extractedContent);
|
|
318
|
-
setSubject(extractedSubject);
|
|
319
|
-
setExtractedTemplateName(extractedName);
|
|
320
|
-
contentInitializedRef.current = true;
|
|
321
|
-
subjectInitializedRef.current = true;
|
|
322
|
-
setIsLoading(false);
|
|
323
|
-
if (setIsLoadingContent) {
|
|
324
|
-
setIsLoadingContent(false);
|
|
325
|
-
}
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (currentTemplateId && emailActions?.getTemplateDetails && !isTemplateLoading) {
|
|
330
|
-
const isTemplateIdChanged = lastTemplateIdRef.current !== currentTemplateId;
|
|
331
|
-
const needsContent = !contentInitializedRef.current;
|
|
332
|
-
const alreadyFetching = fetchingTemplateIdRef.current === currentTemplateId;
|
|
333
|
-
const shouldFetch = (isTemplateIdChanged || needsContent) && !alreadyFetching;
|
|
334
|
-
|
|
335
|
-
// Fetch fresh data when template ID changes OR when we don't have content loaded yet
|
|
336
|
-
// BUT only if we're not already fetching this template
|
|
337
|
-
if (shouldFetch) {
|
|
338
|
-
fetchingTemplateIdRef.current = currentTemplateId; // Mark as fetching
|
|
339
|
-
emailActions.getTemplateDetails(currentTemplateId, 'email');
|
|
340
|
-
lastTemplateIdRef.current = currentTemplateId;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// **IMPORTANT: Clear stale Redux data in create mode**
|
|
345
|
-
// When not in edit mode (create mode), clear any existing template data from Redux
|
|
346
|
-
// This prevents previous template data from persisting when switching from Edit to Create
|
|
347
|
-
if (!currentTemplateId && !isEditMode && (templateDataFromRedux?._id || templateDataFromRedux?.name)) {
|
|
348
|
-
// Clear stale template data - component will handle via resetTemplateData or clearAllValues
|
|
349
|
-
if (emailActions?.clearAllValues) {
|
|
350
|
-
emailActions.clearAllValues();
|
|
351
|
-
}
|
|
352
|
-
// Reset component state to ensure clean slate for create mode
|
|
353
|
-
setExtractedTemplateName('');
|
|
354
|
-
contentInitializedRef.current = false;
|
|
355
|
-
subjectInitializedRef.current = false;
|
|
356
|
-
lastTemplateIdRef.current = null;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Stop loading if template is loading
|
|
360
|
-
if (isTemplateLoading) {
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Extract from Redux data
|
|
365
|
-
const hasTemplateDataFromRedux = templateDataFromRedux
|
|
366
|
-
&& (templateDataFromRedux._id || templateDataFromRedux.name || templateDataFromRedux.versions);
|
|
367
|
-
|
|
368
|
-
if (hasTemplateDataFromRedux && currentTemplateId) {
|
|
369
|
-
const reduxTemplateId = templateDataFromRedux?._id;
|
|
370
|
-
|
|
371
|
-
if (reduxTemplateId === currentTemplateId) {
|
|
372
|
-
const baseData = get(templateDataFromRedux, 'versions.base') || get(templateDataFromRedux, 'base') || {};
|
|
373
|
-
const activeTab = baseData.activeTab
|
|
374
|
-
|| get(currentOrgDetails, 'basic_details.base_language', 'en');
|
|
375
|
-
const languageData = baseData[activeTab] || {};
|
|
376
|
-
|
|
377
|
-
const extractedContent = languageData['template-content']
|
|
378
|
-
|| languageData.html_content
|
|
379
|
-
|| baseData.html_content
|
|
380
|
-
|| baseData['template-content']
|
|
381
|
-
|| get(templateDataFromRedux, 'html_content')
|
|
382
|
-
|| get(templateDataFromRedux, 'template-content')
|
|
383
|
-
|| '';
|
|
384
|
-
|
|
385
|
-
const extractedSubject = baseData.subject
|
|
386
|
-
|| get(templateDataFromRedux, 'subject')
|
|
387
|
-
|| get(templateDataFromRedux, 'versions.base.subject')
|
|
388
|
-
|| '';
|
|
389
|
-
|
|
390
|
-
// Extract template name from Redux data
|
|
391
|
-
const extractedName = templateDataFromRedux.name || '';
|
|
392
|
-
|
|
393
|
-
// Smart update logic:
|
|
394
|
-
// 1. Always update loadedHtmlContent if Redux data is different (keep sync with backend)
|
|
395
|
-
// 2. Update htmlContent ONLY if it matches the OLD loadedHtmlContent (user hasn't edited)
|
|
396
|
-
// OR if it's the first initialization
|
|
397
|
-
if (extractedContent !== loadedHtmlContent) {
|
|
398
|
-
setLoadedHtmlContent(extractedContent);
|
|
399
|
-
|
|
400
|
-
if (!contentInitializedRef.current || htmlContent === loadedHtmlContent) {
|
|
401
|
-
setHtmlContent(extractedContent);
|
|
402
|
-
setSubject(extractedSubject);
|
|
403
|
-
setExtractedTemplateName(extractedName);
|
|
404
|
-
}
|
|
405
|
-
} else if (!contentInitializedRef.current) {
|
|
406
|
-
// First load, even if content matches (e.g. empty), ensure we set state
|
|
407
|
-
setHtmlContent(extractedContent);
|
|
408
|
-
setLoadedHtmlContent(extractedContent);
|
|
409
|
-
setSubject(extractedSubject);
|
|
410
|
-
setExtractedTemplateName(extractedName);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
contentInitializedRef.current = true;
|
|
414
|
-
subjectInitializedRef.current = true;
|
|
415
|
-
|
|
416
|
-
// Only clear fetching ref if we are not loading anymore
|
|
417
|
-
// This prevents race conditions where we load stale data while real fetch is pending
|
|
418
|
-
if (!isTemplateLoading) {
|
|
419
|
-
fetchingTemplateIdRef.current = null;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
setIsLoading(false);
|
|
424
|
-
if (setIsLoadingContent) {
|
|
425
|
-
setIsLoadingContent(false);
|
|
426
|
-
}
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Fallback: stop loading anyway
|
|
431
|
-
if (!isTemplateLoading && !hasTemplateDataFromRedux && !hasCompleteTemplateData) {
|
|
432
|
-
setHtmlContent('');
|
|
433
|
-
setLoadedHtmlContent(''); // Set stable loaded content
|
|
434
|
-
setSubject('');
|
|
435
|
-
setExtractedTemplateName('');
|
|
436
|
-
setIsLoading(false);
|
|
437
|
-
if (setIsLoadingContent) {
|
|
438
|
-
setIsLoadingContent(false);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}, [
|
|
442
|
-
Email?.templateDetails,
|
|
443
|
-
Email?.BEETemplate,
|
|
444
|
-
Email?.getTemplateDetailsInProgress,
|
|
445
|
-
Email?.fetchingCmsData,
|
|
446
|
-
templateDataProp,
|
|
447
|
-
currentTemplateId,
|
|
448
|
-
isEditMode,
|
|
449
|
-
emailActions,
|
|
450
|
-
currentOrgDetails,
|
|
451
|
-
]);
|
|
452
|
-
|
|
453
|
-
// Handle loading state
|
|
454
|
-
useEffect(() => {
|
|
455
|
-
const isAnyApiInProgress = loadingTags || fetchingLiquidTags || createTemplateInProgress || fetchingCmsData;
|
|
456
|
-
|
|
457
|
-
// Stop loading when no API is in progress and tags are loaded
|
|
458
|
-
// Use optional chaining to safely access tags
|
|
459
|
-
if (!isAnyApiInProgress && Array.isArray(tags) && tags?.length >= 0) {
|
|
460
|
-
setIsLoading(false);
|
|
461
|
-
} else if (isAnyApiInProgress) {
|
|
462
|
-
setIsLoading(true);
|
|
463
|
-
}
|
|
464
|
-
}, [loadingTags, tags, fetchingLiquidTags, createTemplateInProgress, fetchingCmsData]);
|
|
465
|
-
|
|
466
|
-
// Handle content change from HTMLEditor
|
|
467
|
-
const handleContentChange = useCallback((content) => {
|
|
468
|
-
setHtmlContent(content);
|
|
469
|
-
|
|
470
|
-
// Validate tags
|
|
471
|
-
if (tags.length > 0 || !isEmpty(injectedTags)) {
|
|
472
|
-
const validationResult = validateTags({
|
|
473
|
-
content,
|
|
474
|
-
tagsParam: tags,
|
|
475
|
-
injectedTagsParams: injectedTags,
|
|
476
|
-
location,
|
|
477
|
-
tagModule: getDefaultTags,
|
|
478
|
-
eventContextTags,
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
if (!validationResult.valid) {
|
|
482
|
-
setTagValidationError(validationResult);
|
|
483
|
-
} else {
|
|
484
|
-
setTagValidationError(null);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}, [tags, injectedTags, location, getDefaultTags, eventContextTags]);
|
|
488
|
-
|
|
489
|
-
// Store the last validation state received from HTMLEditor
|
|
490
|
-
const lastValidationStateRef = useRef(null);
|
|
491
|
-
|
|
492
|
-
// Use ref to store callback to avoid infinite loops (callback in deps would cause re-runs)
|
|
493
|
-
const onValidationStateChangeRef = useRef(onHtmlEditorValidationStateChange);
|
|
494
|
-
useEffect(() => {
|
|
495
|
-
onValidationStateChangeRef.current = onHtmlEditorValidationStateChange;
|
|
496
|
-
}, [onHtmlEditorValidationStateChange]);
|
|
497
|
-
|
|
498
|
-
// Handle error acknowledgment - called when user clicks redirection icon
|
|
499
|
-
const handleErrorAcknowledged = useCallback(() => {
|
|
500
|
-
setErrorsAcknowledged(true);
|
|
501
|
-
// Immediately update parent with acknowledged state
|
|
502
|
-
if (onValidationStateChangeRef.current && lastValidationStateRef.current) {
|
|
503
|
-
onValidationStateChangeRef.current({
|
|
504
|
-
...lastValidationStateRef.current,
|
|
505
|
-
errorsAcknowledged: true,
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
}, []); // No deps needed - using ref
|
|
509
|
-
|
|
510
|
-
// Track last sent state to parent to prevent duplicate updates
|
|
511
|
-
const lastSentToParentRef = useRef(null);
|
|
512
|
-
|
|
513
|
-
// Callback to receive validation state from HTMLEditor
|
|
514
|
-
const handleValidationChange = useCallback((validationState) => {
|
|
515
|
-
const {
|
|
516
|
-
isContentEmpty, issueCounts, validationComplete, hasErrors,
|
|
517
|
-
} = validationState;
|
|
518
|
-
const currentErrorCount = issueCounts?.total || 0;
|
|
519
|
-
|
|
520
|
-
// Reset acknowledgment when new errors appear
|
|
521
|
-
let shouldAcknowledgeErrors = errorsAcknowledged;
|
|
522
|
-
if (hasErrors && validationComplete) {
|
|
523
|
-
const isFirstTimeErrors = prevErrorCountRef.current === 0 || prevErrorCountRef.current === undefined;
|
|
524
|
-
const errorCountChanged = currentErrorCount !== prevErrorCountRef.current;
|
|
525
|
-
|
|
526
|
-
if (isFirstTimeErrors || (errorCountChanged && currentErrorCount > 0)) {
|
|
527
|
-
setErrorsAcknowledged(false);
|
|
528
|
-
shouldAcknowledgeErrors = false;
|
|
529
|
-
}
|
|
530
|
-
} else if (!hasErrors && validationComplete) {
|
|
531
|
-
// No errors - allow buttons to be enabled
|
|
532
|
-
prevErrorCountRef.current = 0;
|
|
533
|
-
shouldAcknowledgeErrors = true;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Update previous error count
|
|
537
|
-
if (validationComplete) {
|
|
538
|
-
prevErrorCountRef.current = currentErrorCount;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Store last validation state (hasErrors = Rule Group #1 only, for save gating)
|
|
542
|
-
lastValidationStateRef.current = {
|
|
543
|
-
isContentEmpty,
|
|
544
|
-
issueCounts,
|
|
545
|
-
validationComplete,
|
|
546
|
-
hasErrors: hasErrors === true,
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
// Notify parent about validation state (using ref to avoid infinite loops)
|
|
550
|
-
// hasErrors = only Rule Group #1 (Input & Sanitization) – used by SlideBoxFooter for button gating
|
|
551
|
-
if (onValidationStateChangeRef.current) {
|
|
552
|
-
const newState = {
|
|
553
|
-
isContentEmpty,
|
|
554
|
-
issueCounts,
|
|
555
|
-
validationComplete,
|
|
556
|
-
hasErrors: hasErrors === true,
|
|
557
|
-
errorsAcknowledged: hasErrors ? shouldAcknowledgeErrors : true,
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
const lastState = lastSentToParentRef.current;
|
|
561
|
-
const hasChanged = !lastState
|
|
562
|
-
|| lastState.isContentEmpty !== newState.isContentEmpty
|
|
563
|
-
|| lastState.validationComplete !== newState.validationComplete
|
|
564
|
-
|| lastState.hasErrors !== newState.hasErrors
|
|
565
|
-
|| lastState.errorsAcknowledged !== newState.errorsAcknowledged
|
|
566
|
-
|| lastState.issueCounts?.total !== newState.issueCounts?.total;
|
|
567
|
-
|
|
568
|
-
if (hasChanged) {
|
|
569
|
-
lastSentToParentRef.current = newState;
|
|
570
|
-
onValidationStateChangeRef.current(newState);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}, [errorsAcknowledged]);
|
|
574
|
-
|
|
575
|
-
// Note: Initial validation state is now sent by HTMLEditor's onValidationChange callback
|
|
576
|
-
// Removed the separate useEffect that was causing infinite loops by depending on htmlContent
|
|
577
|
-
|
|
578
|
-
// Handle tag insertion into Subject field
|
|
579
|
-
const handleSubjectTagSelect = useCallback((data) => {
|
|
580
|
-
if (data) {
|
|
581
|
-
const tagToInsert = `{{${data}}}`;
|
|
582
|
-
const input = document.getElementById('template-subject') || document.querySelector('#template-subject input');
|
|
583
|
-
let subjectValue = subject || '';
|
|
584
|
-
try {
|
|
585
|
-
if (input && (typeof input.selectionStart === 'number')) {
|
|
586
|
-
const startPos = input.selectionStart;
|
|
587
|
-
const endPos = input.selectionEnd;
|
|
588
|
-
subjectValue = `${subjectValue.substring(0, startPos)}${tagToInsert}${subjectValue.substring(endPos)}`;
|
|
589
|
-
setSubject(subjectValue);
|
|
590
|
-
try {
|
|
591
|
-
input.focus();
|
|
592
|
-
const newPos = startPos + tagToInsert.length;
|
|
593
|
-
input.selectionStart = newPos;
|
|
594
|
-
input.selectionEnd = newPos;
|
|
595
|
-
} catch (e) {
|
|
596
|
-
// Ignore focus errors
|
|
597
|
-
}
|
|
598
|
-
} else {
|
|
599
|
-
subjectValue = `${subjectValue}${tagToInsert}`;
|
|
600
|
-
setSubject(subjectValue);
|
|
601
|
-
if (input) {
|
|
602
|
-
try {
|
|
603
|
-
input.value = subjectValue;
|
|
604
|
-
} catch (e) {
|
|
605
|
-
// Ignore value setting errors
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
} catch (e) {
|
|
610
|
-
// Fallback: safe append
|
|
611
|
-
subjectValue = `${subjectValue}${tagToInsert}`;
|
|
612
|
-
setSubject(subjectValue);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}, [subject]);
|
|
616
|
-
|
|
617
|
-
// Handle subject change
|
|
618
|
-
const handleSubjectChange = useCallback((e) => {
|
|
619
|
-
const newSubject = e.target.value;
|
|
620
|
-
setSubject(newSubject);
|
|
621
|
-
if (newSubject && subjectError) {
|
|
622
|
-
setSubjectError('');
|
|
623
|
-
}
|
|
624
|
-
}, [subjectError]);
|
|
625
|
-
|
|
626
|
-
// Handle Save/Update with liquid validation
|
|
627
|
-
const handleSave = useCallback(() => {
|
|
628
|
-
// IMPORTANT: Clear API validation errors FIRST before checking for validation errors
|
|
629
|
-
// This ensures that old API errors don't block the save when user fixes content and clicks Update again
|
|
630
|
-
// We'll re-validate with fresh API call anyway
|
|
631
|
-
if (isLiquidEnabled && getLiquidTags) {
|
|
632
|
-
setApiValidationErrors({
|
|
633
|
-
liquidErrors: [],
|
|
634
|
-
standardErrors: [],
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// 1. Validate Subject - BLOCKING (matches CK/BEE behavior)
|
|
639
|
-
if (!subject || !subject.trim()) {
|
|
640
|
-
const errorMessage = formatMessage(emailMessages["Email Subject cannot be empty."]);
|
|
641
|
-
setSubjectError(errorMessage);
|
|
642
|
-
// Reset parent state so next click is detected as a change
|
|
643
|
-
if (onValidationFail) {
|
|
644
|
-
onValidationFail();
|
|
645
|
-
}
|
|
646
|
-
// IMPORTANT: Return here to block save - matches CK/BEE editor behavior
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
// Clear error if subject is valid
|
|
650
|
-
if (subjectError) {
|
|
651
|
-
setSubjectError('');
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// 1.5. Check for validation errors (Errors = blocking, Warnings = non-blocking)
|
|
655
|
-
// Get issue counts from ref or stored state
|
|
656
|
-
let issueCounts = { errors: 0, warnings: 0, total: 0 };
|
|
657
|
-
if (htmlEditorRef.current && typeof htmlEditorRef.current.getIssueCounts === 'function') {
|
|
658
|
-
issueCounts = htmlEditorRef.current.getIssueCounts();
|
|
659
|
-
} else if (lastValidationStateRef.current?.issueCounts) {
|
|
660
|
-
issueCounts = lastValidationStateRef.current.issueCounts;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Only Rule Group #1 (Input & Sanitization) blocks save; warnings do not block
|
|
664
|
-
// Check both lastValidationStateRef (from onValidationChange callback) and getValidationState (current state)
|
|
665
|
-
const validationState = htmlEditorRef.current && typeof htmlEditorRef.current.getValidationState === 'function'
|
|
666
|
-
? htmlEditorRef.current.getValidationState()
|
|
667
|
-
: null;
|
|
668
|
-
const hasBlockingErrors = lastValidationStateRef.current?.hasErrors === true
|
|
669
|
-
|| (validationState && validationState.hasErrors === true);
|
|
670
|
-
|
|
671
|
-
if (hasBlockingErrors) {
|
|
672
|
-
setErrorsAcknowledged(false);
|
|
673
|
-
if (onValidationStateChangeRef.current) {
|
|
674
|
-
onValidationStateChangeRef.current({
|
|
675
|
-
isContentEmpty: !htmlContent || !htmlContent.trim(),
|
|
676
|
-
issueCounts,
|
|
677
|
-
validationComplete: true,
|
|
678
|
-
hasErrors: true,
|
|
679
|
-
errorsAcknowledged: false,
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
if (onValidationFail) {
|
|
683
|
-
onValidationFail();
|
|
684
|
-
}
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// 2. Validate Unsubscribe Tag (if mandatory)
|
|
689
|
-
// Check if unsubscribe tag is mandatory and if it exists in content
|
|
690
|
-
if (isEmailUnsubscribeTagMandatory() && moduleType === OUTBOUND) {
|
|
691
|
-
// Check if content contains unsubscribe tag (either {{unsubscribe}} or {{unsubscribe(#...)})
|
|
692
|
-
const unsubscribeRegex = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g; // eslint-disable-line no-useless-escape
|
|
693
|
-
const hasUnsubscribeTag = unsubscribeRegex.test(htmlContent);
|
|
694
|
-
|
|
695
|
-
if (!hasUnsubscribeTag) {
|
|
696
|
-
// Show error notification
|
|
697
|
-
const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
|
|
698
|
-
const errorMessage = `${missingTagsMsg} unsubscribe`;
|
|
699
|
-
CapNotification.error({
|
|
700
|
-
message: 'ERROR ! ! !',
|
|
701
|
-
description: errorMessage,
|
|
702
|
-
duration: 5,
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
// Reset parent state so next click is detected as a change
|
|
706
|
-
if (onValidationFail) {
|
|
707
|
-
onValidationFail();
|
|
708
|
-
}
|
|
709
|
-
// Block save - unsubscribe tag is mandatory
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// 3. Validate Content Tags
|
|
715
|
-
// For NON-liquid orgs: BLOCKING validation (matches CK/BEE behavior)
|
|
716
|
-
// For liquid orgs: Non-blocking (extractTags API will validate)
|
|
717
|
-
if (tags.length > 0 || !isEmpty(injectedTags)) {
|
|
718
|
-
const validationResult = validateTags({
|
|
719
|
-
content: htmlContent,
|
|
720
|
-
tagsParam: tags,
|
|
721
|
-
injectedTagsParams: injectedTags,
|
|
722
|
-
location,
|
|
723
|
-
tagModule: getDefaultTags,
|
|
724
|
-
eventContextTags,
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
const hasUnsupportedTags = validationResult?.unsupportedTags?.length > 0;
|
|
728
|
-
if (!validationResult?.valid || hasUnsupportedTags) {
|
|
729
|
-
setTagValidationError(validationResult);
|
|
730
|
-
|
|
731
|
-
// IMPORTANT: For non-liquid orgs, block save (like CK/BEE editor)
|
|
732
|
-
// For liquid orgs, continue (extractTags API will validate)
|
|
733
|
-
if (!isLiquidEnabled) {
|
|
734
|
-
// Show notification popup like CK/BEE editor
|
|
735
|
-
const baseLanguage = get(currentOrgDetails, 'basic_details.base_language', 'en');
|
|
736
|
-
|
|
737
|
-
const contentNotValidMsg = intl.formatMessage(formBuilderMessages.contentNotValidLanguage);
|
|
738
|
-
let errorMessage = `${contentNotValidMsg} ${baseLanguage}`;
|
|
739
|
-
|
|
740
|
-
if (hasUnsupportedTags) {
|
|
741
|
-
const unsupportedTagsMsg = intl.formatMessage(formBuilderMessages.unsupportedTags);
|
|
742
|
-
errorMessage += `\n${unsupportedTagsMsg} ${validationResult?.unsupportedTags?.join(', ')}`;
|
|
743
|
-
}
|
|
744
|
-
if (validationResult?.missingTags?.length > 0) {
|
|
745
|
-
const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
|
|
746
|
-
errorMessage += `\n${missingTagsMsg} ${validationResult?.missingTags?.join(', ')}`;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
const type = 'error';
|
|
750
|
-
CapNotification[type]({
|
|
751
|
-
message: `${type.toUpperCase()} ! ! ! `,
|
|
752
|
-
description: errorMessage,
|
|
753
|
-
duration: 5,
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
// Reset parent state so next click is detected as a change
|
|
757
|
-
if (onValidationFail) {
|
|
758
|
-
onValidationFail();
|
|
759
|
-
}
|
|
760
|
-
// Block save for non-liquid orgs
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
// For liquid orgs, just show warning and continue
|
|
764
|
-
}
|
|
765
|
-
// Clear tag errors if valid
|
|
766
|
-
if (tagValidationError && validationResult?.valid && !hasUnsupportedTags) {
|
|
767
|
-
setTagValidationError(null);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const baseLanguage = get(currentOrgDetails, 'basic_details.base_language', 'en');
|
|
773
|
-
|
|
774
|
-
// Actual save function - called after liquid validation (if enabled) or directly
|
|
775
|
-
const performSave = () => {
|
|
776
|
-
if (isFullMode) {
|
|
777
|
-
// Full mode: Call email actions directly
|
|
778
|
-
const templateDataFromRedux = Email?.templateDetails || Email?.BEETemplate;
|
|
779
|
-
// IMPORTANT: Only use currentTemplateId from URL/params - don't fallback to Redux data
|
|
780
|
-
// which might be stale from previous template in create mode
|
|
781
|
-
const templateId = currentTemplateId;
|
|
782
|
-
const isEditModeForSave = !!templateId;
|
|
783
|
-
|
|
784
|
-
const langId = get(currentOrgDetails, `basic_details.languages.${baseLanguage}.lang_id`, '');
|
|
785
|
-
const language = get(currentOrgDetails, `basic_details.languages.${baseLanguage}.language`, baseLanguage);
|
|
786
|
-
|
|
787
|
-
// Generate or reuse tabKey
|
|
788
|
-
let tabKey = _.uniqueId();
|
|
789
|
-
if (isEditMode && templateDataFromRedux) {
|
|
790
|
-
const existingTabKey = get(templateDataFromRedux, 'versions.base.tabKey')
|
|
791
|
-
|| get(templateDataFromRedux, 'base.tabKey');
|
|
792
|
-
if (existingTabKey) {
|
|
793
|
-
tabKey = existingTabKey;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
const languageData = {
|
|
798
|
-
'template-content': htmlContent || '',
|
|
799
|
-
"is_drag_drop": false,
|
|
800
|
-
"drag_drop_id": '',
|
|
801
|
-
"lang_id": langId,
|
|
802
|
-
"iso_code": baseLanguage,
|
|
803
|
-
"language": language,
|
|
804
|
-
"tabKey": tabKey,
|
|
805
|
-
};
|
|
806
|
-
|
|
807
|
-
const baseStructure = {
|
|
808
|
-
[baseLanguage]: languageData,
|
|
809
|
-
activeTab: baseLanguage,
|
|
810
|
-
selectedLanguages: [baseLanguage],
|
|
811
|
-
base: true,
|
|
812
|
-
tabKey,
|
|
813
|
-
subject: subject || '',
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
const historyEntry = {
|
|
817
|
-
[baseLanguage]: { ...languageData },
|
|
818
|
-
activeTab: baseLanguage,
|
|
819
|
-
selectedLanguages: [baseLanguage],
|
|
820
|
-
base: true,
|
|
821
|
-
tabKey,
|
|
822
|
-
subject: subject || '',
|
|
823
|
-
};
|
|
824
|
-
|
|
825
|
-
const finalTemplateName = isEditModeForSave ? (extractedTemplateName || '') : (templateName || '');
|
|
826
|
-
|
|
827
|
-
const obj = {
|
|
828
|
-
type: EMAIL,
|
|
829
|
-
// In Edit mode, use extractedTemplateName; in Create mode, use templateName prop
|
|
830
|
-
name: finalTemplateName,
|
|
831
|
-
versions: {
|
|
832
|
-
base: baseStructure,
|
|
833
|
-
history: [historyEntry],
|
|
834
|
-
},
|
|
835
|
-
};
|
|
836
|
-
|
|
837
|
-
if (isEditModeForSave && templateId) {
|
|
838
|
-
obj._id = templateId;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
if (emailActions?.transformEmailTemplate && emailActions?.createTemplate) {
|
|
842
|
-
emailActions.transformEmailTemplate(obj, (newEmail) => {
|
|
843
|
-
emailActions.createTemplate(newEmail, (createResponse) => {
|
|
844
|
-
// Handle error response (from 409 or other failures)
|
|
845
|
-
if (createResponse?.error) {
|
|
846
|
-
// Error already shown by Email component via stopValidation
|
|
847
|
-
// Reset acknowledgment so buttons are disabled again
|
|
848
|
-
setErrorsAcknowledged(false);
|
|
849
|
-
// Just reset parent state so next click is detected
|
|
850
|
-
if (onValidationFail) {
|
|
851
|
-
onValidationFail();
|
|
852
|
-
}
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// Handle success response
|
|
857
|
-
if (createResponse && createResponse.templateId) {
|
|
858
|
-
const successMessage = formatMessage(
|
|
859
|
-
isEditModeForSave ? emailMessages.emailEditSuccess : emailMessages.emailCreateSuccess
|
|
860
|
-
);
|
|
861
|
-
CapNotification.success({
|
|
862
|
-
message: successMessage,
|
|
863
|
-
key: isEditModeForSave ? 'edit-template-success' : 'create-template-success',
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
const module = location?.query?.module || 'default';
|
|
867
|
-
const type = location?.query?.type;
|
|
868
|
-
const isLanguageSupport = location?.query?.isLanguageSupport || false;
|
|
869
|
-
const isBEESupport = (location?.query?.isBEESupport !== "false") || false;
|
|
870
|
-
|
|
871
|
-
if (getFormdata) {
|
|
872
|
-
const { versions, ...rest } = createResponse.templateId;
|
|
873
|
-
getFormdata({ value: versions, ...rest, validity: true });
|
|
874
|
-
} else {
|
|
875
|
-
const queryParams = type === 'embedded'
|
|
876
|
-
? {
|
|
877
|
-
type: 'embedded', module, isLanguageSupport, isBEESupport,
|
|
878
|
-
}
|
|
879
|
-
: { module, isLanguageSupport, isBEESupport };
|
|
880
|
-
|
|
881
|
-
const searchParams = new URLSearchParams();
|
|
882
|
-
Object.keys(queryParams).forEach((key) => {
|
|
883
|
-
if (queryParams[key] !== undefined && queryParams[key] !== null) {
|
|
884
|
-
searchParams.append(key, queryParams[key]);
|
|
885
|
-
}
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
history.push({
|
|
889
|
-
pathname: '/email',
|
|
890
|
-
search: searchParams.toString() ? `?${searchParams.toString()}` : '',
|
|
891
|
-
});
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
});
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
} else {
|
|
898
|
-
// Library mode: Use getFormdata flow
|
|
899
|
-
const tmpData = {
|
|
900
|
-
html_content: htmlContent || '',
|
|
901
|
-
is_drag_drop: false,
|
|
902
|
-
lang_id: get(currentOrgDetails, `basic_details.languages.${baseLanguage}.lang_id`, ''),
|
|
903
|
-
iso_code: baseLanguage,
|
|
904
|
-
language: get(currentOrgDetails, `basic_details.languages.${baseLanguage}.language`, baseLanguage),
|
|
905
|
-
};
|
|
906
|
-
|
|
907
|
-
const libraryPayload = {
|
|
908
|
-
base: tmpData,
|
|
909
|
-
secondary_templates: [{ template_data: tmpData }],
|
|
910
|
-
};
|
|
911
|
-
libraryPayload.base.subject = subject || '';
|
|
912
|
-
|
|
913
|
-
if (location?.query?.module === 'library' && isGetFormData && getFormdata) {
|
|
914
|
-
const response = {
|
|
915
|
-
action: "getFormData",
|
|
916
|
-
postAction: 'next',
|
|
917
|
-
id: get(Email, 'templateDetails._id', ''),
|
|
918
|
-
value: libraryPayload,
|
|
919
|
-
validity: true,
|
|
920
|
-
type: EMAIL,
|
|
921
|
-
};
|
|
922
|
-
getFormdata(response);
|
|
923
|
-
} else if (getFormdata) {
|
|
924
|
-
getFormdata({
|
|
925
|
-
value: libraryPayload,
|
|
926
|
-
validity: true,
|
|
927
|
-
type: EMAIL,
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
};
|
|
932
|
-
|
|
933
|
-
// If liquid enabled, validate first using extractTags API
|
|
934
|
-
if (isLiquidEnabled && getLiquidTags) {
|
|
935
|
-
// Note: API validation errors are already cleared at the start of handleSave
|
|
936
|
-
// This ensures fresh validation on every save attempt
|
|
937
|
-
|
|
938
|
-
const onError = ({ standardErrors, liquidErrors }) => {
|
|
939
|
-
// Store API validation errors in state so they can be displayed in UI
|
|
940
|
-
setApiValidationErrors({
|
|
941
|
-
liquidErrors: liquidErrors || [],
|
|
942
|
-
standardErrors: standardErrors || [],
|
|
943
|
-
});
|
|
944
|
-
|
|
945
|
-
if (showLiquidErrorInFooter) {
|
|
946
|
-
showLiquidErrorInFooter({
|
|
947
|
-
STANDARD_ERROR_MSG: standardErrors || [],
|
|
948
|
-
LIQUID_ERROR_MSG: liquidErrors || [],
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
// Don't reset ref here - liquid validation is async and resetting causes infinite loop
|
|
952
|
-
// The parent's isGetFormData will be reset by onValidationFail, and the next click will be detected
|
|
953
|
-
if (onValidationFail) {
|
|
954
|
-
onValidationFail();
|
|
955
|
-
}
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
const onSuccess = () => {
|
|
959
|
-
// Clear API validation errors on success
|
|
960
|
-
setApiValidationErrors({
|
|
961
|
-
liquidErrors: [],
|
|
962
|
-
standardErrors: [],
|
|
963
|
-
});
|
|
964
|
-
performSave();
|
|
965
|
-
};
|
|
966
|
-
|
|
967
|
-
validateLiquidTemplateContent(htmlContent || '', {
|
|
968
|
-
getLiquidTags: getLiquidTags
|
|
969
|
-
? (inputContent, callback) => getLiquidTags(inputContent, callback)
|
|
970
|
-
: (inputContent, callback) => globalActions?.getLiquidTags?.(inputContent, callback),
|
|
971
|
-
formatMessage: intl.formatMessage,
|
|
972
|
-
messages: formBuilderMessages,
|
|
973
|
-
onError,
|
|
974
|
-
onSuccess,
|
|
975
|
-
tagLookupMap: metaEntities?.tagLookupMap,
|
|
976
|
-
eventContextTags,
|
|
977
|
-
isLiquidFlow: true,
|
|
978
|
-
forwardedTags: forwardedTags || {},
|
|
979
|
-
});
|
|
980
|
-
} else {
|
|
981
|
-
performSave();
|
|
982
|
-
}
|
|
983
|
-
}, [
|
|
984
|
-
subject,
|
|
985
|
-
htmlContent,
|
|
986
|
-
tags,
|
|
987
|
-
injectedTags,
|
|
988
|
-
location,
|
|
989
|
-
getDefaultTags,
|
|
990
|
-
eventContextTags,
|
|
991
|
-
formatMessage,
|
|
992
|
-
subjectError,
|
|
993
|
-
isFullMode,
|
|
994
|
-
currentOrgDetails,
|
|
995
|
-
Email,
|
|
996
|
-
currentTemplateId,
|
|
997
|
-
templateDataProp,
|
|
998
|
-
templateName,
|
|
999
|
-
emailActions,
|
|
1000
|
-
getFormdata,
|
|
1001
|
-
isGetFormData,
|
|
1002
|
-
isLiquidEnabled,
|
|
1003
|
-
getLiquidTags,
|
|
1004
|
-
showLiquidErrorInFooter,
|
|
1005
|
-
metaEntities,
|
|
1006
|
-
forwardedTags,
|
|
1007
|
-
globalActions,
|
|
1008
|
-
intl,
|
|
1009
|
-
extractedTemplateName,
|
|
1010
|
-
]);
|
|
1011
|
-
|
|
1012
|
-
// Keep handleSave ref up to date - MUST be before isGetFormData effect
|
|
1013
|
-
useEffect(() => {
|
|
1014
|
-
handleSaveRef.current = handleSave;
|
|
1015
|
-
}, [handleSave]);
|
|
1016
|
-
|
|
1017
|
-
// Trigger save when isGetFormData becomes true (Create/Done button clicked)
|
|
1018
|
-
useEffect(() => {
|
|
1019
|
-
const isGetFormDataChanged = isGetFormData && !prevIsGetFormDataRef.current;
|
|
1020
|
-
const wasReset = !isGetFormData && prevIsGetFormDataRef.current;
|
|
1021
|
-
|
|
1022
|
-
if (isGetFormDataChanged) {
|
|
1023
|
-
// Update ref immediately to prevent duplicate calls
|
|
1024
|
-
prevIsGetFormDataRef.current = true;
|
|
1025
|
-
// Call handleSave via ref to ensure we're using the latest version
|
|
1026
|
-
// This avoids the effect re-running when handleSave changes
|
|
1027
|
-
if (handleSaveRef.current) {
|
|
1028
|
-
handleSaveRef.current();
|
|
1029
|
-
} else {
|
|
1030
|
-
console.warn('[EmailHTMLEditor] handleSaveRef.current is null!');
|
|
1031
|
-
}
|
|
1032
|
-
} else if (wasReset) {
|
|
1033
|
-
// Reset ref when parent resets isGetFormData (e.g., after validation failure)
|
|
1034
|
-
prevIsGetFormDataRef.current = false;
|
|
1035
|
-
} else {
|
|
1036
|
-
// Update ref to current value for next comparison
|
|
1037
|
-
prevIsGetFormDataRef.current = isGetFormData;
|
|
1038
|
-
}
|
|
1039
|
-
}, [isGetFormData, isEditMode]); // Include isEditMode for logging
|
|
1040
|
-
|
|
1041
|
-
// Handle tag context change
|
|
1042
|
-
const handleOnTagsContextChange = useCallback((data) => {
|
|
1043
|
-
const { type, module } = location?.query || {};
|
|
1044
|
-
const isEmbedded = type === EMBEDDED;
|
|
1045
|
-
const contextValue = data || (isEmbedded ? module : DEFAULT);
|
|
1046
|
-
const query = {
|
|
1047
|
-
layout: EMAIL,
|
|
1048
|
-
type: TAG,
|
|
1049
|
-
context: contextValue ? contextValue.toLowerCase() : contextValue,
|
|
1050
|
-
embedded: isEmbedded ? type : FULL,
|
|
1051
|
-
};
|
|
1052
|
-
if (globalActions && globalActions.fetchSchemaForEntity) {
|
|
1053
|
-
globalActions.fetchSchemaForEntity(query);
|
|
1054
|
-
}
|
|
1055
|
-
}, [location, globalActions]);
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const spinTip = fetchingLiquidTags ? <FormattedMessage {...formBuilderMessages.liquidSpinText} /> : '';
|
|
1059
|
-
|
|
1060
|
-
// Handle template name change
|
|
1061
|
-
// Call showTemplateName when templateName is available (for CreativesContainer header)
|
|
1062
|
-
// This matches the behavior of Email component which calls showTemplateName in onFormDataChange
|
|
1063
|
-
// In Edit mode, use extractedTemplateName from template data; in Create mode, use templateName prop
|
|
1064
|
-
const { onFormDataChange: onFormDataChangeProp } = props;
|
|
1065
|
-
|
|
1066
|
-
// Create onFormDataChange callback that updates extractedTemplateName in Edit mode
|
|
1067
|
-
// This matches the Email component's onFormDataChange which updates its state
|
|
1068
|
-
const handleFormDataChange = useCallback((updatedFormData) => {
|
|
1069
|
-
const newTemplateName = updatedFormData?.['template-name'] || '';
|
|
1070
|
-
|
|
1071
|
-
// In Edit mode, update extractedTemplateName state (similar to Email component updating its formData state)
|
|
1072
|
-
if (isEditMode && newTemplateName !== extractedTemplateName) {
|
|
1073
|
-
setExtractedTemplateName(newTemplateName);
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// Call the parent's onFormDataChange if provided
|
|
1077
|
-
if (onFormDataChangeProp) {
|
|
1078
|
-
onFormDataChangeProp(updatedFormData);
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
// Call showTemplateName again with updated formData (same pattern as Email component)
|
|
1082
|
-
if (showTemplateName && isFullMode) {
|
|
1083
|
-
showTemplateName({ formData: updatedFormData, onFormDataChange: handleFormDataChange });
|
|
1084
|
-
}
|
|
1085
|
-
}, [isEditMode, extractedTemplateName, onFormDataChangeProp, showTemplateName, isFullMode]);
|
|
1086
|
-
|
|
1087
|
-
useEffect(() => {
|
|
1088
|
-
if (showTemplateName && isFullMode) {
|
|
1089
|
-
// In Edit mode, use extractedTemplateName; in Create mode, use templateName prop
|
|
1090
|
-
const nameToUse = isEditMode ? extractedTemplateName : templateName;
|
|
1091
|
-
const formData = {
|
|
1092
|
-
'template-name': nameToUse || '',
|
|
1093
|
-
};
|
|
1094
|
-
// Pass handleFormDataChange callback so CreativesContainer can update template name
|
|
1095
|
-
// This is the same pattern used in Email component
|
|
1096
|
-
showTemplateName({ formData, onFormDataChange: handleFormDataChange });
|
|
1097
|
-
}
|
|
1098
|
-
}, [showTemplateName, isFullMode, templateName, extractedTemplateName, isEditMode, handleFormDataChange]);
|
|
1099
|
-
|
|
1100
|
-
return (
|
|
1101
|
-
<CapSpin spinning={isLoading} tip={spinTip}>
|
|
1102
|
-
<CapRow>
|
|
1103
|
-
<CapColumn span={24}>
|
|
1104
|
-
{/* Subject Field */}
|
|
1105
|
-
<CapColumn span={24} style={{ marginBottom: CAP_SPACE_16 }}>
|
|
1106
|
-
<CapTagListWithInput
|
|
1107
|
-
inputId="template-subject"
|
|
1108
|
-
inputValue={subject}
|
|
1109
|
-
inputOnChange={handleSubjectChange}
|
|
1110
|
-
inputPlaceholder={formatMessage(messages.enterEmailSubject)}
|
|
1111
|
-
inputRequired
|
|
1112
|
-
inputErrorMessage={subjectError}
|
|
1113
|
-
headingText={formatMessage(messages.subject)}
|
|
1114
|
-
headingType="h4"
|
|
1115
|
-
headingStyle={{ marginRight: '85%' }}
|
|
1116
|
-
onTagSelect={handleSubjectTagSelect}
|
|
1117
|
-
onContextChange={handleOnTagsContextChange}
|
|
1118
|
-
location={location}
|
|
1119
|
-
tags={tags}
|
|
1120
|
-
injectedTags={injectedTags || {}}
|
|
1121
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
1122
|
-
eventContextTags={eventContextTags}
|
|
1123
|
-
showHeading
|
|
1124
|
-
showTagList
|
|
1125
|
-
showInput
|
|
1126
|
-
containerStyle={{
|
|
1127
|
-
display: 'flex',
|
|
1128
|
-
flexDirection: 'column',
|
|
1129
|
-
}}
|
|
1130
|
-
popoverPlacement="bottomRight"
|
|
1131
|
-
/>
|
|
1132
|
-
</CapColumn>
|
|
1133
|
-
|
|
1134
|
-
<HTMLEditor
|
|
1135
|
-
ref={htmlEditorRef}
|
|
1136
|
-
variant="email"
|
|
1137
|
-
initialContent={loadedHtmlContent || ''}
|
|
1138
|
-
onContentChange={handleContentChange}
|
|
1139
|
-
onSave={handleSave}
|
|
1140
|
-
readOnly={isReadOnly}
|
|
1141
|
-
showFullscreenButton
|
|
1142
|
-
autoSave={false}
|
|
1143
|
-
tags={tags}
|
|
1144
|
-
injectedTags={injectedTags}
|
|
1145
|
-
location={location}
|
|
1146
|
-
eventContextTags={eventContextTags}
|
|
1147
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
1148
|
-
channel={EMAIL}
|
|
1149
|
-
userLocale={intl.locale || 'en'}
|
|
1150
|
-
moduleFilterEnabled={location?.query?.type !== EMBEDDED}
|
|
1151
|
-
onTagContextChange={handleOnTagsContextChange}
|
|
1152
|
-
isLiquidEnabled={isLiquidEnabled}
|
|
1153
|
-
isFullMode={isFullMode}
|
|
1154
|
-
onErrorAcknowledged={handleErrorAcknowledged}
|
|
1155
|
-
onValidationChange={handleValidationChange}
|
|
1156
|
-
apiValidationErrors={apiValidationErrors}
|
|
1157
|
-
/>
|
|
1158
|
-
</CapColumn>
|
|
1159
|
-
</CapRow>
|
|
1160
|
-
</CapSpin>
|
|
1161
|
-
);
|
|
1162
|
-
};
|
|
1163
|
-
|
|
1164
|
-
EmailHTMLEditor.propTypes = {
|
|
1165
|
-
intl: PropTypes.object.isRequired,
|
|
1166
|
-
location: PropTypes.object,
|
|
1167
|
-
params: PropTypes.object,
|
|
1168
|
-
getDefaultTags: PropTypes.string,
|
|
1169
|
-
supportedTags: PropTypes.array,
|
|
1170
|
-
metaEntities: PropTypes.object,
|
|
1171
|
-
injectedTags: PropTypes.object,
|
|
1172
|
-
globalActions: PropTypes.object,
|
|
1173
|
-
loadingTags: PropTypes.bool,
|
|
1174
|
-
eventContextTags: PropTypes.array,
|
|
1175
|
-
forwardedTags: PropTypes.object,
|
|
1176
|
-
selectedOfferDetails: PropTypes.array,
|
|
1177
|
-
currentOrgDetails: PropTypes.object,
|
|
1178
|
-
isReadOnly: PropTypes.bool,
|
|
1179
|
-
fetchingLiquidTags: PropTypes.bool,
|
|
1180
|
-
createTemplateInProgress: PropTypes.bool,
|
|
1181
|
-
fetchingCmsData: PropTypes.bool,
|
|
1182
|
-
// Email Redux state
|
|
1183
|
-
Email: PropTypes.object,
|
|
1184
|
-
// Email actions
|
|
1185
|
-
emailActions: PropTypes.object,
|
|
1186
|
-
// Full mode props
|
|
1187
|
-
isFullMode: PropTypes.bool,
|
|
1188
|
-
templateName: PropTypes.string,
|
|
1189
|
-
showTemplateName: PropTypes.func,
|
|
1190
|
-
onFormDataChange: PropTypes.func,
|
|
1191
|
-
isGetFormData: PropTypes.bool,
|
|
1192
|
-
getFormdata: PropTypes.func,
|
|
1193
|
-
// Library mode props
|
|
1194
|
-
templateData: PropTypes.object,
|
|
1195
|
-
// Uploaded content from zip file
|
|
1196
|
-
EmailLayout: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), // eslint-disable-line react/require-default-props
|
|
1197
|
-
// Liquid validation
|
|
1198
|
-
getLiquidTags: PropTypes.func,
|
|
1199
|
-
showLiquidErrorInFooter: PropTypes.func,
|
|
1200
|
-
// Preview/Test
|
|
1201
|
-
// Parent loading control
|
|
1202
|
-
setIsLoadingContent: PropTypes.func,
|
|
1203
|
-
forwardedRef: PropTypes.object,
|
|
1204
|
-
onValidationFail: PropTypes.func,
|
|
1205
|
-
moduleType: PropTypes.string,
|
|
1206
|
-
onHtmlEditorValidationStateChange: PropTypes.func,
|
|
1207
|
-
};
|
|
1208
|
-
|
|
1209
|
-
EmailHTMLEditor.defaultProps = {
|
|
1210
|
-
location: {},
|
|
1211
|
-
params: {},
|
|
1212
|
-
getDefaultTags: null,
|
|
1213
|
-
supportedTags: [],
|
|
1214
|
-
showTemplateName: null,
|
|
1215
|
-
metaEntities: {},
|
|
1216
|
-
injectedTags: {},
|
|
1217
|
-
globalActions: {},
|
|
1218
|
-
loadingTags: false,
|
|
1219
|
-
eventContextTags: [],
|
|
1220
|
-
forwardedTags: {},
|
|
1221
|
-
selectedOfferDetails: [],
|
|
1222
|
-
currentOrgDetails: {},
|
|
1223
|
-
isReadOnly: false,
|
|
1224
|
-
fetchingLiquidTags: false,
|
|
1225
|
-
createTemplateInProgress: false,
|
|
1226
|
-
fetchingCmsData: false,
|
|
1227
|
-
Email: {},
|
|
1228
|
-
emailActions: {},
|
|
1229
|
-
isFullMode: false,
|
|
1230
|
-
templateName: '',
|
|
1231
|
-
onFormDataChange: null,
|
|
1232
|
-
isGetFormData: false,
|
|
1233
|
-
getFormdata: null,
|
|
1234
|
-
templateData: null,
|
|
1235
|
-
getLiquidTags: null,
|
|
1236
|
-
showLiquidErrorInFooter: null,
|
|
1237
|
-
setIsLoadingContent: null,
|
|
1238
|
-
forwardedRef: null,
|
|
1239
|
-
onValidationFail: null,
|
|
1240
|
-
moduleType: null,
|
|
1241
|
-
onHtmlEditorValidationStateChange: null,
|
|
1242
|
-
};
|
|
1243
|
-
|
|
1244
|
-
const EmailHTMLEditorWithIntl = injectIntl(EmailHTMLEditor);
|
|
1245
|
-
|
|
1246
|
-
export default forwardRef((props, ref) => <EmailHTMLEditorWithIntl {...props} forwardedRef={ref} />);
|