@capillarytech/creatives-library 8.0.271 → 8.0.272
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 +2 -1
- package/initialReducer.js +2 -0
- package/package.json +1 -1
- package/services/api.js +10 -0
- package/services/tests/api.test.js +34 -0
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +17 -35
- package/tests/integration/TemplateCreation/api-response.js +31 -1
- package/tests/integration/TemplateCreation/msw-handler.js +2 -0
- package/utils/common.js +5 -0
- package/utils/commonUtils.js +28 -5
- package/utils/tests/commonUtil.test.js +224 -0
- package/utils/transformTemplateConfig.js +0 -10
- package/v2Components/CapDeviceContent/index.js +61 -56
- package/v2Components/CapTagList/index.js +6 -1
- package/v2Components/CapTagListWithInput/index.js +5 -1
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
- package/v2Components/ErrorInfoNote/constants.js +1 -0
- package/v2Components/ErrorInfoNote/index.js +402 -72
- package/v2Components/ErrorInfoNote/messages.js +32 -6
- package/v2Components/ErrorInfoNote/style.scss +278 -6
- package/v2Components/FormBuilder/tests/index.test.js +13 -4
- package/v2Components/HtmlEditor/HTMLEditor.js +418 -99
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +870 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1882 -133
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
- package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
- package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +23 -102
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -140
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -1
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +31 -6
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +7 -10
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +22 -43
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +18 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +36 -31
- package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +46 -34
- package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +52 -46
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +277 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +295 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
- package/v2Components/HtmlEditor/constants.js +45 -20
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +351 -16
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
- package/v2Components/HtmlEditor/hooks/useValidation.js +213 -56
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +102 -94
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +214 -45
- package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
- package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
- package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +158 -124
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
- package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
- package/v2Components/HtmlEditor/utils/validationConstants.js +38 -0
- package/v2Components/MobilePushPreviewV2/constants.js +6 -0
- package/v2Components/MobilePushPreviewV2/index.js +33 -7
- package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
- package/v2Components/TemplatePreview/index.js +47 -32
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
- package/v2Containers/BeeEditor/index.js +172 -90
- package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
- package/v2Containers/BeePopupEditor/constants.js +10 -0
- package/v2Containers/BeePopupEditor/index.js +194 -0
- package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +156 -13
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
- package/v2Containers/CreativesContainer/constants.js +1 -0
- package/v2Containers/CreativesContainer/index.js +251 -47
- package/v2Containers/CreativesContainer/messages.js +8 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +103 -0
- package/v2Containers/Email/actions.js +7 -0
- package/v2Containers/Email/constants.js +5 -1
- package/v2Containers/Email/index.js +234 -29
- package/v2Containers/Email/messages.js +32 -0
- package/v2Containers/Email/reducer.js +12 -1
- package/v2Containers/Email/sagas.js +61 -7
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
- package/v2Containers/Email/tests/reducer.test.js +46 -0
- package/v2Containers/Email/tests/sagas.test.js +320 -29
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1246 -0
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +212 -21
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +2472 -0
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
- package/v2Containers/EmailWrapper/constants.js +2 -0
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +627 -79
- package/v2Containers/EmailWrapper/index.js +103 -23
- package/v2Containers/EmailWrapper/messages.js +65 -1
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +955 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +596 -82
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
- package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
- package/v2Containers/InApp/actions.js +7 -0
- package/v2Containers/InApp/constants.js +20 -4
- package/v2Containers/InApp/index.js +802 -360
- package/v2Containers/InApp/index.scss +4 -3
- package/v2Containers/InApp/messages.js +7 -3
- package/v2Containers/InApp/reducer.js +21 -3
- package/v2Containers/InApp/sagas.js +29 -9
- package/v2Containers/InApp/selectors.js +25 -5
- package/v2Containers/InApp/tests/index.test.js +154 -50
- package/v2Containers/InApp/tests/reducer.test.js +34 -0
- package/v2Containers/InApp/tests/sagas.test.js +61 -9
- package/v2Containers/InApp/tests/selectors.test.js +612 -0
- package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
- package/v2Containers/InAppWrapper/constants.js +16 -0
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
- package/v2Containers/InAppWrapper/index.js +148 -0
- package/v2Containers/InAppWrapper/messages.js +49 -0
- package/v2Containers/InappAdvance/index.js +1099 -0
- package/v2Containers/InappAdvance/index.scss +10 -0
- package/v2Containers/InappAdvance/tests/index.test.js +448 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
- package/v2Containers/TagList/index.js +62 -19
- package/v2Containers/Templates/_templates.scss +60 -1
- package/v2Containers/Templates/index.js +89 -4
- package/v2Containers/Templates/messages.js +4 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useState, useEffect, useCallback, useMemo, useRef,
|
|
3
|
+
} from "react";
|
|
2
4
|
import isEmpty from 'lodash/isEmpty';
|
|
3
5
|
import get from 'lodash/get';
|
|
4
6
|
import { bindActionCreators } from "redux";
|
|
@@ -7,21 +9,19 @@ import { injectIntl, FormattedMessage } from "react-intl";
|
|
|
7
9
|
import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
|
|
8
10
|
import CapHeading from "@capillarytech/cap-ui-library/CapHeading";
|
|
9
11
|
import CapSpin from "@capillarytech/cap-ui-library/CapSpin";
|
|
10
|
-
import CapInput from "@capillarytech/cap-ui-library/CapInput";
|
|
11
12
|
import CapRadioGroup from "@capillarytech/cap-ui-library/CapRadioGroup";
|
|
12
13
|
import CapRow from "@capillarytech/cap-ui-library/CapRow";
|
|
13
14
|
import CapColumn from "@capillarytech/cap-ui-library/CapColumn";
|
|
14
15
|
import CapButton from "@capillarytech/cap-ui-library/CapButton";
|
|
15
|
-
import CapTab from "@capillarytech/cap-ui-library/CapTab";
|
|
16
16
|
import CapNotification from "@capillarytech/cap-ui-library/CapNotification";
|
|
17
|
-
import { makeSelectInApp, makeSelectAccount } from "./selectors";
|
|
17
|
+
import { makeSelectInApp, makeSelectAccount, makeSelectGetTemplateDetailsInProgress } from "./selectors";
|
|
18
18
|
import * as globalActions from '../Cap/actions';
|
|
19
19
|
import {
|
|
20
20
|
isLoadingMetaEntities,
|
|
21
21
|
makeSelectMetaEntities,
|
|
22
22
|
setInjectedTags,
|
|
23
23
|
selectCurrentOrgDetails,
|
|
24
|
-
selectLiquidStateDetails
|
|
24
|
+
selectLiquidStateDetails,
|
|
25
25
|
} from "../Cap/selectors";
|
|
26
26
|
import * as inAppActions from "./actions";
|
|
27
27
|
import './index.scss';
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
ANDROID,
|
|
41
41
|
BIG_PICTURE,
|
|
42
42
|
BIG_TEXT,
|
|
43
|
+
BIG_HTML,
|
|
43
44
|
DEEP_LINK,
|
|
44
45
|
DEVICE_SUPPORTED,
|
|
45
46
|
INAPP_BUTTON_TYPES,
|
|
@@ -48,19 +49,22 @@ import {
|
|
|
48
49
|
INITIAL_CTA_DATA,
|
|
49
50
|
IOS,
|
|
50
51
|
LAYOUT_RADIO_OPTIONS,
|
|
51
|
-
|
|
52
|
+
IOS_CAPITAL,
|
|
52
53
|
} from "./constants";
|
|
53
54
|
import { INAPP, SMS } from "../CreativesContainer/constants";
|
|
54
55
|
import {
|
|
55
56
|
ALL, TAG, EMBEDDED, DEFAULT, FULL, LIBRARY,
|
|
56
57
|
} from "../Whatsapp/constants";
|
|
57
|
-
import CapDeviceContent from "../../v2Components/CapDeviceContent";
|
|
58
58
|
import { getCdnUrl } from "../../utils/cdnTransformation";
|
|
59
59
|
import { getCtaObject, hasAnyErrors, getSingleTab } from "./utils";
|
|
60
60
|
import { validateInAppContent } from "../../utils/commonUtils";
|
|
61
|
-
import ErrorInfoNote from "../../v2Components/ErrorInfoNote";
|
|
62
61
|
import { hasLiquidSupportFeature } from "../../utils/common";
|
|
63
62
|
import formBuilderMessages from "../../v2Components/FormBuilder/messages";
|
|
63
|
+
import HTMLEditor from "../../v2Components/HtmlEditor";
|
|
64
|
+
import { HTML_EDITOR_VARIANTS } from "../../v2Components/HtmlEditor/constants";
|
|
65
|
+
import { INAPP_EDITOR_TYPES } from "../InAppWrapper/constants";
|
|
66
|
+
import InappAdvanced from "../InappAdvance/index";
|
|
67
|
+
import { ErrorInfoNote } from "../../v2Components/ErrorInfoNote";
|
|
64
68
|
|
|
65
69
|
let editContent = {};
|
|
66
70
|
|
|
@@ -68,13 +72,14 @@ export const InApp = (props) => {
|
|
|
68
72
|
const {
|
|
69
73
|
intl,
|
|
70
74
|
actions,
|
|
75
|
+
globalActions,
|
|
71
76
|
isFullMode,
|
|
72
77
|
onCreateComplete,
|
|
73
78
|
params,
|
|
74
79
|
templateData = {},
|
|
80
|
+
defaultData = {},
|
|
75
81
|
editData = {},
|
|
76
82
|
accountData = {},
|
|
77
|
-
globalActions,
|
|
78
83
|
location,
|
|
79
84
|
getDefaultTags,
|
|
80
85
|
supportedTags,
|
|
@@ -82,8 +87,19 @@ export const InApp = (props) => {
|
|
|
82
87
|
injectedTags,
|
|
83
88
|
getFormData,
|
|
84
89
|
selectedOfferDetails,
|
|
85
|
-
currentOrgDetails,
|
|
86
90
|
fetchingLiquidValidation,
|
|
91
|
+
templateName,
|
|
92
|
+
getTemplateDetailsInProgress,
|
|
93
|
+
isEditInApp,
|
|
94
|
+
setIsLoadingContent,
|
|
95
|
+
query,
|
|
96
|
+
inAppCreateMode,
|
|
97
|
+
isGetFormData,
|
|
98
|
+
showTemplateName,
|
|
99
|
+
onValidationFail,
|
|
100
|
+
type,
|
|
101
|
+
forwardedTags,
|
|
102
|
+
currentOrgDetails,
|
|
87
103
|
// TestAndPreviewSlidebox props
|
|
88
104
|
showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
|
|
89
105
|
handleTestAndPreview: propsHandleTestAndPreview,
|
|
@@ -96,7 +112,7 @@ export const InApp = (props) => {
|
|
|
96
112
|
const [templateMediaType, setTemplateMediaType] = useState(
|
|
97
113
|
INAPP_MEDIA_TYPES.TEXT
|
|
98
114
|
);
|
|
99
|
-
const [
|
|
115
|
+
const [tempName, setTempName] = useState("");
|
|
100
116
|
const [templateLayoutType, setTemplateLayoutType] = useState(
|
|
101
117
|
INAPP_MESSAGE_LAYOUT_TYPES.MODAL
|
|
102
118
|
);
|
|
@@ -110,6 +126,25 @@ export const InApp = (props) => {
|
|
|
110
126
|
const [templateMessageErrorIos, setTemplateMessageErrorIos] = useState(false);
|
|
111
127
|
const [templateTitleErrorAndroid, setTemplateTitleErrorAndroid] = useState(false);
|
|
112
128
|
const [templateTitleErrorIos, setTemplateTitleErrorIos] = useState(false);
|
|
129
|
+
// HTML Editor content state (for INAPP HTML variant)
|
|
130
|
+
// Initialize HTML content from edit data if available
|
|
131
|
+
const getInitialHtmlContent = (device) => {
|
|
132
|
+
const editContent = isFullMode
|
|
133
|
+
? get(editData?.templateDetails?.versions, `base.content`, {})
|
|
134
|
+
: get(templateData?.versions, `base.content`, {});
|
|
135
|
+
const deviceContent = editContent?.[device];
|
|
136
|
+
return deviceContent?.message || "";
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const [htmlContentAndroid, setHtmlContentAndroid] = useState(() => getInitialHtmlContent("ANDROID"));
|
|
140
|
+
const [htmlContentIos, setHtmlContentIos] = useState(() => getInitialHtmlContent("IOS"));
|
|
141
|
+
// Track if this is an HTML template (type === HTML or style === BIG_HTML)
|
|
142
|
+
const [isHTMLTemplate, setIsHTMLTemplate] = useState(false);
|
|
143
|
+
// Version to force HTMLEditor remount on layout changes
|
|
144
|
+
const [htmlEditorContentVersion, setHtmlEditorContentVersion] = useState(0);
|
|
145
|
+
// Refs to store latest content before layout changes
|
|
146
|
+
const htmlContentAndroidRef = useRef(htmlContentAndroid);
|
|
147
|
+
const htmlContentIosRef = useRef(htmlContentIos);
|
|
113
148
|
const [accountId, setAccountId] = useState("");
|
|
114
149
|
const [accessToken, setAccessToken] = useState("");
|
|
115
150
|
const [accountName, setAccountName] = useState("");
|
|
@@ -218,9 +253,6 @@ export const InApp = (props) => {
|
|
|
218
253
|
}
|
|
219
254
|
}, [propsHandleCloseTestAndPreview]);
|
|
220
255
|
const { accessibleFeatures = [] } = currentOrgDetails || {};
|
|
221
|
-
const isAiContentBotDisabled = accessibleFeatures?.includes(
|
|
222
|
-
AI_CONTENT_BOT_DISABLED
|
|
223
|
-
);
|
|
224
256
|
const [errorMessage, setErrorMessage] = useState({
|
|
225
257
|
STANDARD_ERROR_MSG: {
|
|
226
258
|
ANDROID: [],
|
|
@@ -248,7 +280,7 @@ export const InApp = (props) => {
|
|
|
248
280
|
// DEVICE_SUPPORTED is '1', which indicates if the particular account is supported, and '0' if the devive is not supported
|
|
249
281
|
//get deep link keys in an array
|
|
250
282
|
const deepLinkKeys = Object.values(JSON.parse(deepLinkObj || '{}'));
|
|
251
|
-
const keys = deepLinkKeys?.map((link) => ({label: link.name, value: link.link, title: link.link }));
|
|
283
|
+
const keys = deepLinkKeys?.map((link) => ({ label: link.name, value: link.link, title: link.link }));
|
|
252
284
|
setPanes(isAndroidSupported ? ANDROID : IOS);
|
|
253
285
|
setDeepLink(keys);
|
|
254
286
|
setAccountId(sourceAccountIdentifier);
|
|
@@ -273,6 +305,32 @@ export const InApp = (props) => {
|
|
|
273
305
|
};
|
|
274
306
|
}, [paramObj.id]);
|
|
275
307
|
|
|
308
|
+
// Initialize template name from defaultData (from wrapper) or editData/templateData
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
const defaultTemplateName = defaultData?.['template-name'] || '';
|
|
311
|
+
if (defaultTemplateName && !isEditFlow) {
|
|
312
|
+
setTempName(defaultTemplateName);
|
|
313
|
+
// Call showTemplateName callback if provided (for header display)
|
|
314
|
+
if (isFullMode && showTemplateName && defaultTemplateName) {
|
|
315
|
+
showTemplateName({
|
|
316
|
+
formData: { 'template-name': defaultTemplateName },
|
|
317
|
+
onFormDataChange: (updatedFormData) => {
|
|
318
|
+
const newName = updatedFormData?.['template-name'] || '';
|
|
319
|
+
setTempName(newName);
|
|
320
|
+
if (showTemplateName) {
|
|
321
|
+
showTemplateName({
|
|
322
|
+
formData: { 'template-name': newName },
|
|
323
|
+
onFormDataChange: (formData) => {
|
|
324
|
+
setTempName(formData?.['template-name'] || '');
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}, [defaultData?.['template-name'], showTemplateName, isEditFlow]);
|
|
333
|
+
|
|
276
334
|
useEffect(() => {
|
|
277
335
|
const {
|
|
278
336
|
name = "",
|
|
@@ -280,16 +338,48 @@ export const InApp = (props) => {
|
|
|
280
338
|
createdAt = "",
|
|
281
339
|
} = isFullMode ? editData?.templateDetails || {} : templateData || {};
|
|
282
340
|
editContent = get(versions, `base.content`, {});
|
|
341
|
+
|
|
342
|
+
// LIBRARY MODE FIX: Backend stores bodyType as 'POPUP' but UI expects 'MODAL'
|
|
343
|
+
// Convert back to MODAL for display
|
|
344
|
+
if (!isFullMode && editContent) {
|
|
345
|
+
if (editContent.ANDROID?.bodyType === INAPP_MESSAGE_LAYOUT_TYPES.POPUP) {
|
|
346
|
+
editContent.ANDROID.bodyType = INAPP_MESSAGE_LAYOUT_TYPES.MODAL;
|
|
347
|
+
}
|
|
348
|
+
if (editContent.IOS?.bodyType === INAPP_MESSAGE_LAYOUT_TYPES.POPUP) {
|
|
349
|
+
editContent.IOS.bodyType = INAPP_MESSAGE_LAYOUT_TYPES.MODAL;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
283
353
|
if (editContent && !isEmpty(editContent)) {
|
|
284
354
|
setEditFlow(true);
|
|
285
|
-
|
|
355
|
+
setTempName(name);
|
|
286
356
|
setTemplateDate(createdAt);
|
|
287
357
|
setTemplateLayoutType(editContent?.ANDROID?.bodyType);
|
|
358
|
+
// Call showTemplateName callback when in edit mode + full mode to show template name header
|
|
359
|
+
if (showTemplateName && name) {
|
|
360
|
+
showTemplateName({
|
|
361
|
+
formData: { 'template-name': name },
|
|
362
|
+
onFormDataChange: (updatedFormData) => {
|
|
363
|
+
const newName = updatedFormData?.['template-name'] || '';
|
|
364
|
+
setTempName(newName);
|
|
365
|
+
if (showTemplateName) {
|
|
366
|
+
showTemplateName({
|
|
367
|
+
formData: { 'template-name': newName },
|
|
368
|
+
onFormDataChange: (formData) => {
|
|
369
|
+
setTempName(formData?.['template-name'] || '');
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
}
|
|
288
376
|
const androidContent = editContent?.ANDROID;
|
|
377
|
+
let androidIsHTML = false;
|
|
289
378
|
if (!isEmpty(androidContent)) {
|
|
290
379
|
const {
|
|
291
380
|
title: androidTitle = '',
|
|
292
381
|
message: androidMessage = '',
|
|
382
|
+
type: androidType = '',
|
|
293
383
|
ctas: androidCta = {},
|
|
294
384
|
expandableDetails: androidExpandableDetails = {},
|
|
295
385
|
} = androidContent || {};
|
|
@@ -299,12 +389,30 @@ export const InApp = (props) => {
|
|
|
299
389
|
ctas: androidCtas,
|
|
300
390
|
} = androidExpandableDetails || {};
|
|
301
391
|
const androidCtaLength = androidCtas?.length;
|
|
392
|
+
// Check if this is a Bee editor template
|
|
393
|
+
const isBEEeditor = get(androidContent, 'isBEEeditor');
|
|
394
|
+
const isBeeFreeTemplate = !isEmpty(androidTitle) && androidTitle.toLowerCase() === 'bee free template';
|
|
395
|
+
// Check if this is an HTML editor template (identified by special title)
|
|
396
|
+
const isHTMLEditorTemplate = !isEmpty(androidTitle) && androidTitle.toLowerCase() === 'html editor template';
|
|
397
|
+
// Check if this is an HTML template
|
|
398
|
+
// Prioritize HTML editor template title, then check type/style
|
|
399
|
+
// But exclude if it's a Bee editor template
|
|
400
|
+
androidIsHTML = isHTMLEditorTemplate || ((androidType === INAPP_MEDIA_TYPES.HTML || androidStyle === BIG_HTML)
|
|
401
|
+
&& !isBEEeditor
|
|
402
|
+
&& !isBeeFreeTemplate);
|
|
403
|
+
setIsHTMLTemplate(androidIsHTML);
|
|
404
|
+
|
|
302
405
|
setTitleAndroid(androidTitle);
|
|
303
406
|
setTemplateMessageAndroid(androidMessage);
|
|
407
|
+
// Initialize HTML content for HTMLEditor if feature is enabled and it's an HTML template
|
|
408
|
+
if (androidIsHTML) {
|
|
409
|
+
setHtmlContentAndroid(androidMessage);
|
|
410
|
+
}
|
|
304
411
|
setTemplateMediaType(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
412
|
+
androidIsHTML ? INAPP_MEDIA_TYPES.HTML
|
|
413
|
+
: androidStyle === BIG_TEXT
|
|
414
|
+
? INAPP_MEDIA_TYPES.TEXT
|
|
415
|
+
: INAPP_MEDIA_TYPES.IMAGE
|
|
308
416
|
);
|
|
309
417
|
setInAppImageSrcAndroid(androidImage);
|
|
310
418
|
setDeepLinkValueAndroid(androidCta[0]?.actionLink || '');
|
|
@@ -321,6 +429,7 @@ export const InApp = (props) => {
|
|
|
321
429
|
const {
|
|
322
430
|
title: iosTitle = '',
|
|
323
431
|
message: iosMessage = '',
|
|
432
|
+
type: iosType = '',
|
|
324
433
|
ctas: iosCta = {},
|
|
325
434
|
expandableDetails: iosExpandableDetails = {},
|
|
326
435
|
} = iosContent || {};
|
|
@@ -330,9 +439,53 @@ export const InApp = (props) => {
|
|
|
330
439
|
ctas: iosCtas,
|
|
331
440
|
} = iosExpandableDetails || {};
|
|
332
441
|
const iosCtaLength = iosCtas?.length;
|
|
442
|
+
|
|
443
|
+
// Check if this is an HTML template (if Android wasn't HTML, check iOS)
|
|
444
|
+
// Note: androidIsHTML is in the outer scope from the Android content check above
|
|
445
|
+
if (!androidIsHTML) {
|
|
446
|
+
// Check if this is a Bee editor template
|
|
447
|
+
const isBEEeditor = get(iosContent, 'isBEEeditor');
|
|
448
|
+
const isBeeFreeTemplate = !isEmpty(iosTitle) && iosTitle.toLowerCase() === 'bee free template';
|
|
449
|
+
// Check if this is an HTML editor template (identified by special title)
|
|
450
|
+
const isHTMLEditorTemplate = !isEmpty(iosTitle) && iosTitle.toLowerCase() === 'html editor template';
|
|
451
|
+
// Check if this is an HTML template
|
|
452
|
+
// Prioritize HTML editor template title, then check type/style
|
|
453
|
+
// But exclude if it's a Bee editor template
|
|
454
|
+
const iosIsHTML = isHTMLEditorTemplate || ((iosType === INAPP_MEDIA_TYPES.HTML || iosStyle === BIG_HTML)
|
|
455
|
+
&& !isBEEeditor
|
|
456
|
+
&& !isBeeFreeTemplate);
|
|
457
|
+
setIsHTMLTemplate(iosIsHTML);
|
|
458
|
+
// Initialize HTML content for HTMLEditor if feature is enabled and it's an HTML template
|
|
459
|
+
if (iosIsHTML) {
|
|
460
|
+
setHtmlContentIos(iosMessage);
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
// If Android is HTML, also initialize iOS HTML content if available
|
|
464
|
+
if (androidIsHTML) {
|
|
465
|
+
setHtmlContentIos(iosMessage);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
333
469
|
setTitleIos(iosTitle);
|
|
334
470
|
setTemplateMessageIos(iosMessage);
|
|
335
|
-
|
|
471
|
+
// Update templateMediaType if iOS is HTML and Android wasn't
|
|
472
|
+
if (!androidIsHTML) {
|
|
473
|
+
// Check if this is a Bee editor template
|
|
474
|
+
const isBEEeditor = get(iosContent, 'isBEEeditor');
|
|
475
|
+
const isBeeFreeTemplate = !isEmpty(iosTitle) && iosTitle.toLowerCase() === 'bee free template';
|
|
476
|
+
// Check if this is an HTML editor template (identified by special title)
|
|
477
|
+
const isHTMLEditorTemplate = !isEmpty(iosTitle) && iosTitle.toLowerCase() === 'html editor template';
|
|
478
|
+
// Check if this is an HTML template
|
|
479
|
+
// Prioritize HTML editor template title, then check type/style
|
|
480
|
+
const iosIsHTML = isHTMLEditorTemplate || ((iosType === INAPP_MEDIA_TYPES.HTML || iosStyle === BIG_HTML)
|
|
481
|
+
&& !isBEEeditor
|
|
482
|
+
&& !isBeeFreeTemplate);
|
|
483
|
+
if (iosIsHTML) {
|
|
484
|
+
setTemplateMediaType(INAPP_MEDIA_TYPES.HTML);
|
|
485
|
+
} else {
|
|
486
|
+
setTemplateMediaType(iosStyle === BIG_TEXT ? INAPP_MEDIA_TYPES.TEXT : INAPP_MEDIA_TYPES.IMAGE);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
336
489
|
setInAppImageSrcIos(iosImage);
|
|
337
490
|
setButtonTypeIos(iosCtaLength ? INAPP_BUTTON_TYPES.CTA : INAPP_BUTTON_TYPES.NONE);
|
|
338
491
|
setAddActionLinkIos(!isEmpty(iosCta) && true);
|
|
@@ -341,12 +494,47 @@ export const InApp = (props) => {
|
|
|
341
494
|
setCtaDataIos(getCtaObject(iosCtas));
|
|
342
495
|
}
|
|
343
496
|
}
|
|
497
|
+
} else {
|
|
498
|
+
// Explicitly set edit flow to false if there's no edit content
|
|
499
|
+
setEditFlow(false);
|
|
344
500
|
}
|
|
345
501
|
}, [editData.templateDetails || templateData]);
|
|
346
502
|
|
|
503
|
+
// Extract editor type from defaultData for stable reference
|
|
504
|
+
const editorType = useMemo(() => {
|
|
505
|
+
const type = defaultData?.['editor-type'];
|
|
506
|
+
return type;
|
|
507
|
+
}, [defaultData]);
|
|
508
|
+
|
|
509
|
+
// Separate effect for handling editor type from wrapper in create mode
|
|
510
|
+
useEffect(() => {
|
|
511
|
+
// Only process editor type if we're not in edit flow
|
|
512
|
+
if (!isEditFlow) {
|
|
513
|
+
if (editorType === INAPP_EDITOR_TYPES.HTML_EDITOR) {
|
|
514
|
+
setIsHTMLTemplate(true);
|
|
515
|
+
setTemplateMediaType(INAPP_MEDIA_TYPES.HTML);
|
|
516
|
+
} else if (editorType === INAPP_EDITOR_TYPES.DRAG_DROP_EDITOR) {
|
|
517
|
+
setIsHTMLTemplate(false);
|
|
518
|
+
} else if (!editorType) {
|
|
519
|
+
// If no editor type is set yet, ensure we start with false
|
|
520
|
+
setIsHTMLTemplate(false);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}, [editorType, isEditFlow]);
|
|
524
|
+
|
|
347
525
|
// tag Code start from here
|
|
348
526
|
useEffect(() => {
|
|
349
|
-
//
|
|
527
|
+
// Reset tags fetch ref when switching between HTML and legacy editors
|
|
528
|
+
tagsFetchedRef.current = false;
|
|
529
|
+
|
|
530
|
+
// Only fetch tags if HTML Editor is not being used (legacy flow)
|
|
531
|
+
// For HTML Editor, tags will be fetched via handleOnTagsContextChange
|
|
532
|
+
if (isHTMLTemplate) {
|
|
533
|
+
// HTML Editor will handle tag fetching via onContextChange
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
//fetching tags for legacy editor
|
|
350
538
|
const { type, module } = location.query || {};
|
|
351
539
|
const isEmbedded = type === EMBEDDED;
|
|
352
540
|
const context = isEmbedded ? module : DEFAULT;
|
|
@@ -361,7 +549,7 @@ export const InApp = (props) => {
|
|
|
361
549
|
query.context = getDefaultTags;
|
|
362
550
|
}
|
|
363
551
|
globalActions.fetchSchemaForEntity(query);
|
|
364
|
-
}, []);
|
|
552
|
+
}, [isHTMLTemplate]);
|
|
365
553
|
|
|
366
554
|
useEffect(() => {
|
|
367
555
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -372,7 +560,17 @@ export const InApp = (props) => {
|
|
|
372
560
|
updateTags(tag);
|
|
373
561
|
}, [metaEntities]);
|
|
374
562
|
|
|
375
|
-
|
|
563
|
+
// Track if we've already fetched tags to prevent duplicate calls
|
|
564
|
+
const tagsFetchedRef = React.useRef(false);
|
|
565
|
+
|
|
566
|
+
const handleOnTagsContextChange = useCallback((data) => {
|
|
567
|
+
// This function is called when TagList needs to fetch tags
|
|
568
|
+
// It triggers the API call to /meta/TAG endpoint via fetchSchemaForEntity
|
|
569
|
+
// Only fetch if we haven't already fetched for this context
|
|
570
|
+
if (tagsFetchedRef.current) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
376
574
|
const { type } = location.query || {};
|
|
377
575
|
const tempData = (data || '').toLowerCase();
|
|
378
576
|
const isEmbedded = type === EMBEDDED;
|
|
@@ -384,260 +582,192 @@ export const InApp = (props) => {
|
|
|
384
582
|
context,
|
|
385
583
|
embedded,
|
|
386
584
|
};
|
|
585
|
+
// Mark as fetched to prevent duplicate calls
|
|
586
|
+
tagsFetchedRef.current = true;
|
|
587
|
+
// Call the API via Redux action - this will trigger the saga which calls Api.fetchSchemaForEntity
|
|
588
|
+
// The API endpoint will be: /meta/TAG?query={...}
|
|
387
589
|
globalActions.fetchSchemaForEntity(query);
|
|
388
|
-
};
|
|
590
|
+
}, [location.query, globalActions]);
|
|
389
591
|
|
|
390
|
-
const templateDescErrorHandler = (value) => {
|
|
391
|
-
let errorMessage = false;
|
|
392
|
-
const { unsupportedTags, isBraceError } = validateTags({
|
|
393
|
-
content: value,
|
|
394
|
-
tagsParam: tags,
|
|
395
|
-
injectedTagsParams: injectedTags,
|
|
396
|
-
location,
|
|
397
|
-
tagModule: getDefaultTags,
|
|
398
|
-
isFullMode,
|
|
399
|
-
}) || {};
|
|
400
|
-
if (value === '' && INAPP_MEDIA_TYPES.NONE) {
|
|
401
|
-
errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
|
|
402
|
-
} else if (unsupportedTags?.length > 0) {
|
|
403
|
-
errorMessage = formatMessage(
|
|
404
|
-
globalMessages.unsupportedTagsValidationError,
|
|
405
|
-
{
|
|
406
|
-
unsupportedTags,
|
|
407
|
-
},
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
if (isBraceError) {
|
|
411
|
-
errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
412
|
-
}
|
|
413
|
-
return errorMessage;
|
|
414
|
-
};
|
|
415
592
|
|
|
416
|
-
const onTagSelect = (data, id) => {
|
|
417
|
-
if (id === 0) {
|
|
418
|
-
const tempTitle = `${panes === ANDROID ? titleAndroid : titleIos}{{${data}}}`;
|
|
419
|
-
if (panes === ANDROID) {
|
|
420
|
-
setTitleAndroid(tempTitle);
|
|
421
|
-
} else {
|
|
422
|
-
setTitleIos(tempTitle);
|
|
423
|
-
}
|
|
424
|
-
} else {
|
|
425
|
-
const tempMsg = `${panes === ANDROID ? templateMessageAndroid : templateMessageIos}{{${data}}}`;
|
|
426
|
-
const error = templateDescErrorHandler(tempMsg);
|
|
427
|
-
if (panes === ANDROID) {
|
|
428
|
-
setTemplateMessageAndroid(tempMsg);
|
|
429
|
-
setTemplateMessageErrorAndroid(error);
|
|
430
|
-
} else {
|
|
431
|
-
setTemplateMessageIos(tempMsg);
|
|
432
|
-
setTemplateMessageErrorIos(error);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
};
|
|
436
593
|
// tag Code end
|
|
437
594
|
|
|
438
|
-
const onTemplateNameChange = ({ target: { value } }) => {
|
|
439
|
-
setTemplateName(value);
|
|
440
|
-
};
|
|
441
|
-
|
|
442
595
|
const onTemplateLayoutTypeChange = ({ target: { value } }) => {
|
|
596
|
+
// Update layout type and force HTMLEditor to remount with latest content
|
|
597
|
+
// Increment version to force remount - this ensures editor displays current content
|
|
443
598
|
setTemplateLayoutType(value);
|
|
599
|
+
setHtmlEditorContentVersion((prev) => prev + 1);
|
|
444
600
|
};
|
|
445
601
|
|
|
446
|
-
const onCopyTitleAndContent = () => {
|
|
447
|
-
if (panes === ANDROID) {
|
|
448
|
-
setTitleAndroid(titleIos);
|
|
449
|
-
setTemplateMessageAndroid(templateMessageIos);
|
|
450
|
-
} else {
|
|
451
|
-
setTitleIos(titleAndroid);
|
|
452
|
-
setTemplateMessageIos(templateMessageAndroid);
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
602
|
|
|
456
|
-
const PANES = [
|
|
457
|
-
{
|
|
458
|
-
content: (
|
|
459
|
-
<CapDeviceContent
|
|
460
|
-
intl={intl}
|
|
461
|
-
location={location}
|
|
462
|
-
injectedTags={injectIntl}
|
|
463
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
464
|
-
panes={panes}
|
|
465
|
-
actions={actions}
|
|
466
|
-
editData={editData}
|
|
467
|
-
isFullMode={isFullMode}
|
|
468
|
-
inAppImageSrc={inAppImageSrcAndroid}
|
|
469
|
-
setInAppImageSrc={setInAppImageSrcAndroid}
|
|
470
|
-
isEditFlow={isEditFlow}
|
|
471
|
-
ctaData={ctaDataAndroid}
|
|
472
|
-
setCtaData={setCtaDataAndroid}
|
|
473
|
-
buttonType={buttonTypeAndroid}
|
|
474
|
-
setButtonType={setButtonTypeAndroid}
|
|
475
|
-
accountId={accountId}
|
|
476
|
-
accessToken={accessToken}
|
|
477
|
-
templateMediaType={templateMediaType}
|
|
478
|
-
setTemplateMediaType={setTemplateMediaType}
|
|
479
|
-
title={titleAndroid}
|
|
480
|
-
setTitle={setTitleAndroid}
|
|
481
|
-
templateMessageError={templateMessageErrorAndroid}
|
|
482
|
-
templateMessage={templateMessageAndroid}
|
|
483
|
-
setTemplateMessage={setTemplateMessageAndroid}
|
|
484
|
-
setTemplateMessageError={setTemplateMessageErrorAndroid}
|
|
485
|
-
templateTitleError={templateTitleErrorAndroid}
|
|
486
|
-
setTemplateTitleError={setTemplateTitleErrorAndroid}
|
|
487
|
-
addActionLink={addActionLinkAndroid}
|
|
488
|
-
setAddActionLink={setAddActionLinkAndroid}
|
|
489
|
-
deepLink={deepLink}
|
|
490
|
-
deepLinkValue={deepLinkValueAndroid}
|
|
491
|
-
setDeepLinkValue={setDeepLinkValueAndroid}
|
|
492
|
-
onCopyTitleAndContent={onCopyTitleAndContent}
|
|
493
|
-
tags={tags}
|
|
494
|
-
onTagSelect={onTagSelect}
|
|
495
|
-
handleOnTagsContextChange={handleOnTagsContextChange}
|
|
496
|
-
templateDescErrorHandler={templateDescErrorHandler}
|
|
497
|
-
isAiContentBotDisabled={isAiContentBotDisabled}
|
|
498
|
-
/>
|
|
499
|
-
),
|
|
500
|
-
tab: <FormattedMessage {...messages.Android} />,
|
|
501
|
-
key: ANDROID,
|
|
502
|
-
isSupported: get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED,
|
|
503
|
-
// DEVICE_SUPPORTED is '1', which indicates if the particular account is supported, and '0' if the devive is not supported
|
|
504
|
-
},
|
|
505
|
-
{
|
|
506
|
-
content: (
|
|
507
|
-
<CapDeviceContent
|
|
508
|
-
intl={intl}
|
|
509
|
-
location={location}
|
|
510
|
-
injectedTags={injectIntl}
|
|
511
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
512
|
-
panes={panes}
|
|
513
|
-
actions={actions}
|
|
514
|
-
editData={editData}
|
|
515
|
-
isFullMode={isFullMode}
|
|
516
|
-
inAppImageSrc={inAppImageSrcIos}
|
|
517
|
-
setInAppImageSrc={setInAppImageSrcIos}
|
|
518
|
-
isEditFlow={isEditFlow}
|
|
519
|
-
ctaData={ctaDataIos}
|
|
520
|
-
setCtaData={setCtaDataIos}
|
|
521
|
-
buttonType={buttonTypeIos}
|
|
522
|
-
setButtonType={setButtonTypeIos}
|
|
523
|
-
accountId={accountId}
|
|
524
|
-
accessToken={accessToken}
|
|
525
|
-
templateMediaType={templateMediaType}
|
|
526
|
-
setTemplateMediaType={setTemplateMediaType}
|
|
527
|
-
title={titleIos}
|
|
528
|
-
setTitle={setTitleIos}
|
|
529
|
-
templateMessageError={templateMessageErrorIos}
|
|
530
|
-
templateMessage={templateMessageIos}
|
|
531
|
-
setTemplateMessage={setTemplateMessageIos}
|
|
532
|
-
setTemplateMessageError={setTemplateMessageErrorIos}
|
|
533
|
-
templateTitleError={templateTitleErrorIos}
|
|
534
|
-
setTemplateTitleError={setTemplateTitleErrorIos}
|
|
535
|
-
addActionLink={addActionLinkIos}
|
|
536
|
-
setAddActionLink={setAddActionLinkIos}
|
|
537
|
-
deepLink={deepLink}
|
|
538
|
-
deepLinkValue={deepLinkValueIos}
|
|
539
|
-
setDeepLinkValue={setDeepLinkValueIos}
|
|
540
|
-
onCopyTitleAndContent={onCopyTitleAndContent}
|
|
541
|
-
tags={tags}
|
|
542
|
-
onTagSelect={onTagSelect}
|
|
543
|
-
handleOnTagsContextChange={handleOnTagsContextChange}
|
|
544
|
-
templateDescErrorHandler={templateDescErrorHandler}
|
|
545
|
-
isAiContentBotDisabled={isAiContentBotDisabled}
|
|
546
|
-
/>
|
|
547
|
-
),
|
|
548
|
-
tab: <FormattedMessage {...messages.Ios} />,
|
|
549
|
-
key: IOS,
|
|
550
|
-
isSupported: get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED,
|
|
551
|
-
},
|
|
552
|
-
];
|
|
553
|
-
|
|
554
|
-
const createModeContent = (
|
|
555
|
-
<CapRow>
|
|
556
|
-
{/* template name */}
|
|
557
|
-
<CapHeading type="h4">
|
|
558
|
-
<FormattedMessage {...messages.creativeName} />
|
|
559
|
-
</CapHeading>
|
|
560
|
-
<CapInput
|
|
561
|
-
id="inapp-template-name-input"
|
|
562
|
-
className="inapp-template-name-input"
|
|
563
|
-
onChange={onTemplateNameChange}
|
|
564
|
-
placeholder={formatMessage(globalMessages.templateNamePlaceholder)}
|
|
565
|
-
value={templateName}
|
|
566
|
-
size="default"
|
|
567
|
-
/>
|
|
568
|
-
</CapRow>
|
|
569
|
-
);
|
|
570
603
|
//create methods end
|
|
571
604
|
|
|
572
605
|
//used by create and edit
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
606
|
+
|
|
607
|
+
const isDisableDone = (device) => {
|
|
608
|
+
const isIosDevice = device === IOS || device === IOS_CAPITAL;
|
|
609
|
+
const isAndroidDevice = device === ANDROID;
|
|
610
|
+
const isNoDevice = device === null || device === undefined;
|
|
611
|
+
|
|
612
|
+
// Check for validation errors first - if there are errors, disable the button
|
|
613
|
+
if (hasAnyErrors(errorMessage)) {
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// For HTMLEditor: only validate HTML content (when it's an HTML template)
|
|
618
|
+
if (isHTMLTemplate) {
|
|
619
|
+
// Get account-level device support restrictions
|
|
620
|
+
const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
|
|
621
|
+
const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
|
|
622
|
+
|
|
623
|
+
// Check if devices have content - ensure content is a string before calling trim
|
|
624
|
+
let hasAndroidContent = htmlContentAndroid && typeof htmlContentAndroid === 'string' && htmlContentAndroid?.trim() !== '';
|
|
625
|
+
let hasIosContent = htmlContentIos && typeof htmlContentIos === 'string' && htmlContentIos?.trim() !== '';
|
|
626
|
+
|
|
627
|
+
// LIBRARY MODE FIX: In library mode edit, htmlContent states might not be set yet
|
|
628
|
+
// because HTMLEditor loads content via initialContent prop, not via onContentChange
|
|
629
|
+
// Fallback to checking template data content
|
|
630
|
+
if (!hasAndroidContent && isEditFlow && !isFullMode && templateData) {
|
|
631
|
+
const androidTemplateContent = get(templateData, 'versions.base.content.ANDROID.message')
|
|
632
|
+
|| get(templateData, 'versions.base.content.ANDROID.beeHtml.value');
|
|
633
|
+
hasAndroidContent = androidTemplateContent && androidTemplateContent.trim() !== '';
|
|
586
634
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
mediaPreview.inAppImageSrcIos = inAppImageSrcIos;
|
|
592
|
-
break;
|
|
593
|
-
default:
|
|
594
|
-
break;
|
|
635
|
+
if (!hasIosContent && isEditFlow && !isFullMode && templateData) {
|
|
636
|
+
const iosTemplateContent = get(templateData, 'versions.base.content.IOS.message')
|
|
637
|
+
|| get(templateData, 'versions.base.content.IOS.beeHtml.value');
|
|
638
|
+
hasIosContent = iosTemplateContent && iosTemplateContent.trim() !== '';
|
|
595
639
|
}
|
|
640
|
+
|
|
641
|
+
// If checking specific device, validate that device's content
|
|
642
|
+
if (isAndroidDevice) {
|
|
643
|
+
// Only validate Android if it's supported by account
|
|
644
|
+
// But in library mode edit, check template data too
|
|
645
|
+
if (androidSupported || (isEditFlow && !isFullMode && hasAndroidContent)) {
|
|
646
|
+
if (!hasAndroidContent) {
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
// Android not supported and no content in template - skip validation
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
if (isIosDevice) {
|
|
655
|
+
// Only validate iOS if it's supported by account
|
|
656
|
+
// But in library mode edit, check template data too
|
|
657
|
+
if (iosSupported || (isEditFlow && !isFullMode && hasIosContent)) {
|
|
658
|
+
if (!hasIosContent) {
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
// iOS not supported and no content in template - skip validation
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// If no specific device, check if at least one supported device has content
|
|
668
|
+
// Users can create templates with content in Android-only, iOS-only, or both devices
|
|
669
|
+
// Even when both devices are supported, user can create template with content in just one device
|
|
670
|
+
if (androidSupported && iosSupported) {
|
|
671
|
+
// Both devices supported - user can create template with content in Android, iOS, or both
|
|
672
|
+
// Only disable if NEITHER device has content
|
|
673
|
+
if (!hasAndroidContent && !hasIosContent) {
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
// At least one device has content - enable Done button
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
if (androidSupported) {
|
|
680
|
+
// Only Android supported - require Android content
|
|
681
|
+
if (!hasAndroidContent) {
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
// Android has content - enable Done button
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
if (iosSupported) {
|
|
688
|
+
// Only iOS supported - require iOS content
|
|
689
|
+
if (!hasIosContent) {
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
// iOS has content - enable Done button
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
// Neither device supported - this shouldn't happen, but handle gracefully
|
|
696
|
+
return true;
|
|
596
697
|
}
|
|
597
|
-
return (
|
|
598
|
-
<UnifiedPreview
|
|
599
|
-
channel={INAPP}
|
|
600
|
-
content={{
|
|
601
|
-
inAppPreviewContent: {
|
|
602
|
-
mediaPreview,
|
|
603
|
-
templateTitle,
|
|
604
|
-
templateMsg,
|
|
605
|
-
...((isBtnTypeCtaAndroid || isBtnTypeCTaIos) && {
|
|
606
|
-
ctaData,
|
|
607
|
-
}),
|
|
608
|
-
},
|
|
609
|
-
templateLayoutType,
|
|
610
|
-
}}
|
|
611
|
-
device={panes}
|
|
612
|
-
showDeviceToggle={false}
|
|
613
|
-
showHeader={false}
|
|
614
|
-
formatMessage={formatMessage}
|
|
615
|
-
/>
|
|
616
|
-
);
|
|
617
|
-
};
|
|
618
698
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
699
|
+
// Legacy flow validation (when HTMLEditor is not enabled)
|
|
700
|
+
// Get account-level device support
|
|
701
|
+
const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
|
|
702
|
+
const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
|
|
703
|
+
|
|
704
|
+
// If no tags are available (e.g., in tests), allow submission even with minimal content
|
|
705
|
+
// This is to support test scenarios where full content validation might not be set up
|
|
706
|
+
const hasTags = tags && tags.length > 0;
|
|
707
|
+
const isTestScenario = !hasTags;
|
|
708
|
+
|
|
709
|
+
// If no device specified, check if at least one supported device has valid content
|
|
710
|
+
if (isNoDevice) {
|
|
711
|
+
// In test scenarios, only require message content (title is optional)
|
|
712
|
+
// In production, require both title and message
|
|
713
|
+
const hasAndroidContent = androidSupported && templateMessageAndroid && templateMessageAndroid?.trim() !== '' && !templateMessageErrorAndroid && (isTestScenario || (titleAndroid && titleAndroid?.trim() !== '' && !templateTitleErrorAndroid));
|
|
714
|
+
const hasIosContent = iosSupported && templateMessageIos && templateMessageIos?.trim() !== '' && !templateMessageErrorIos && (isTestScenario || (titleIos && titleIos?.trim() !== '' && !templateTitleErrorIos));
|
|
715
|
+
|
|
716
|
+
// Check media requirements
|
|
717
|
+
const androidMediaValid = !androidSupported || (templateMediaType === INAPP_MEDIA_TYPES.TEXT || inAppImageSrcAndroid !== '');
|
|
718
|
+
const iosMediaValid = !iosSupported || (templateMediaType === INAPP_MEDIA_TYPES.TEXT || inAppImageSrcIos !== '');
|
|
719
|
+
|
|
720
|
+
// Check CTA requirements
|
|
721
|
+
const androidCtaValid = !isBtnTypeCtaAndroid || (ctaDataAndroid[0]?.isSaved);
|
|
722
|
+
const iosCtaValid = !isBtnTypeCTaIos || (ctaDataIos[0]?.isSaved);
|
|
723
|
+
|
|
724
|
+
// Check action link requirements
|
|
725
|
+
const androidActionLinkValid = !addActionLinkAndroid || deepLinkValueAndroid;
|
|
726
|
+
const iosActionLinkValid = !addActionLinkIos || deepLinkValueIos;
|
|
727
|
+
|
|
728
|
+
// If both devices are supported, at least one should have valid content
|
|
729
|
+
if (androidSupported && iosSupported) {
|
|
730
|
+
const androidValid = hasAndroidContent && androidMediaValid && androidCtaValid && androidActionLinkValid;
|
|
731
|
+
const iosValid = hasIosContent && iosMediaValid && iosCtaValid && iosActionLinkValid;
|
|
732
|
+
return !(androidValid || iosValid);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// If only Android is supported, it must have valid content
|
|
736
|
+
if (androidSupported) {
|
|
737
|
+
return !(hasAndroidContent && androidMediaValid && androidCtaValid && androidActionLinkValid);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// If only iOS is supported, it must have valid content
|
|
741
|
+
if (iosSupported) {
|
|
742
|
+
return !(hasIosContent && iosMediaValid && iosCtaValid && iosActionLinkValid);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Neither device supported - disable
|
|
624
746
|
return true;
|
|
625
747
|
}
|
|
748
|
+
|
|
626
749
|
//if template message is not entered
|
|
627
750
|
//for android
|
|
628
|
-
if (isAndroidDevice
|
|
629
|
-
|
|
751
|
+
if (isAndroidDevice) {
|
|
752
|
+
const androidMessage = templateMessageAndroid;
|
|
753
|
+
if (androidMessage?.trim() === '' || templateMessageErrorAndroid) {
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
//for ios
|
|
758
|
+
if (isIosDevice) {
|
|
759
|
+
const iosMessage = templateMessageIos;
|
|
760
|
+
if (iosMessage?.trim() === '' || templateMessageErrorIos) {
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
630
763
|
}
|
|
631
|
-
if (isIosDevice && (templateMessageIos.trim() === '' || templateMessageErrorIos)) {
|
|
632
|
-
return true;
|
|
633
|
-
}//for ios
|
|
634
764
|
|
|
635
765
|
//if template title is not entered
|
|
636
766
|
//for android
|
|
637
|
-
if (isAndroidDevice && (titleAndroid
|
|
767
|
+
if (isAndroidDevice && (titleAndroid?.trim() === '' || templateTitleErrorAndroid)) {
|
|
638
768
|
return true;
|
|
639
769
|
}
|
|
640
|
-
if (isIosDevice && (titleIos
|
|
770
|
+
if (isIosDevice && (titleIos?.trim() === '' || templateTitleErrorIos)) {
|
|
641
771
|
return true;
|
|
642
772
|
}//for ios
|
|
643
773
|
//if media type is image and the mediaType file is not uploaded
|
|
@@ -696,11 +826,11 @@ export const InApp = (props) => {
|
|
|
696
826
|
switch (templateMediaType) {
|
|
697
827
|
case INAPP_MEDIA_TYPES.IMAGE:
|
|
698
828
|
androidMediaParams = {
|
|
699
|
-
image: getCdnUrl({url: inAppImageSrcAndroid, channelName: INAPP }),
|
|
829
|
+
image: getCdnUrl({ url: inAppImageSrcAndroid, channelName: INAPP }),
|
|
700
830
|
style: BIG_PICTURE,
|
|
701
831
|
};
|
|
702
832
|
iosMediaParams = {
|
|
703
|
-
image: getCdnUrl({url: inAppImageSrcIos, channelName: INAPP }),
|
|
833
|
+
image: getCdnUrl({ url: inAppImageSrcIos, channelName: INAPP }),
|
|
704
834
|
style: BIG_PICTURE,
|
|
705
835
|
};
|
|
706
836
|
break;
|
|
@@ -712,16 +842,46 @@ export const InApp = (props) => {
|
|
|
712
842
|
sourceAccountIdentifier = "",
|
|
713
843
|
id,
|
|
714
844
|
} = accountObj;
|
|
715
|
-
|
|
716
|
-
//
|
|
717
|
-
const
|
|
845
|
+
|
|
846
|
+
// Use HTML content if HTMLEditor is enabled and it's an HTML template, otherwise use regular message
|
|
847
|
+
const androidMessage = isHTMLTemplate && htmlContentAndroid
|
|
848
|
+
? htmlContentAndroid
|
|
849
|
+
: templateMessageAndroid;
|
|
850
|
+
|
|
851
|
+
// Determine type and style for Android
|
|
852
|
+
const androidType = isHTMLTemplate ? INAPP_MEDIA_TYPES.HTML : templateMediaType;
|
|
853
|
+
const androidExpandableStyle = isHTMLTemplate ? BIG_HTML : BIG_TEXT;
|
|
854
|
+
|
|
855
|
+
// LIBRARY MODE FIX: Backend doesn't support 'MODAL' bodyType, convert to 'POPUP'
|
|
856
|
+
const bodyTypeForBackend = templateLayoutType === INAPP_MESSAGE_LAYOUT_TYPES.MODAL ? INAPP_MESSAGE_LAYOUT_TYPES.POPUP : templateLayoutType;
|
|
857
|
+
|
|
858
|
+
// Check account-level device support
|
|
859
|
+
const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
|
|
860
|
+
const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
|
|
861
|
+
|
|
862
|
+
// Check if devices have content (for HTML Editor)
|
|
863
|
+
const hasAndroidContent = htmlContentAndroid && htmlContentAndroid?.trim() !== '';
|
|
864
|
+
const hasIosContent = htmlContentIos && htmlContentIos?.trim() !== '';
|
|
865
|
+
|
|
866
|
+
// For HTML Editor, check if device has content and is supported
|
|
867
|
+
// For legacy editor, use isDisableDone check
|
|
868
|
+
// Only include devices that have content - allows Android-only, iOS-only, or both
|
|
869
|
+
const shouldIncludeAndroid = isHTMLTemplate
|
|
870
|
+
? (androidSupported && hasAndroidContent)
|
|
871
|
+
: !isDisableDone(ANDROID);
|
|
872
|
+
|
|
873
|
+
// Construct Android content if device is supported and has content
|
|
874
|
+
// Even when both devices are supported, only include devices that have content
|
|
875
|
+
const androidContent = shouldIncludeAndroid ? {
|
|
718
876
|
...commonDevicePayload,
|
|
719
|
-
|
|
720
|
-
|
|
877
|
+
type: androidType,
|
|
878
|
+
// Use 'html editor template' as title for HTML editor to differentiate from BEE editor
|
|
879
|
+
title: isHTMLTemplate ? 'html editor template' : titleAndroid,
|
|
880
|
+
message: androidMessage,
|
|
721
881
|
bodyType: templateLayoutType,
|
|
722
882
|
expandableDetails: {
|
|
723
|
-
style:
|
|
724
|
-
message:
|
|
883
|
+
style: androidExpandableStyle,
|
|
884
|
+
message: androidMessage,
|
|
725
885
|
...androidMediaParams,
|
|
726
886
|
...(isBtnTypeCtaAndroid && {
|
|
727
887
|
ctas: getCtaPayload(ANDROID),
|
|
@@ -729,19 +889,38 @@ export const InApp = (props) => {
|
|
|
729
889
|
},
|
|
730
890
|
custom: [],
|
|
731
891
|
...(deepLinkValueAndroid && {
|
|
732
|
-
ctas: [{type: DEEP_LINK, actionLink: deepLinkValueAndroid}],
|
|
892
|
+
ctas: [{ type: DEEP_LINK, actionLink: deepLinkValueAndroid }],
|
|
733
893
|
}),
|
|
734
894
|
} : {};
|
|
735
|
-
|
|
736
|
-
//
|
|
737
|
-
const
|
|
895
|
+
|
|
896
|
+
// Use HTML content if HTMLEditor is enabled and it's an HTML template, otherwise use regular message
|
|
897
|
+
const iosMessage = isHTMLTemplate && htmlContentIos
|
|
898
|
+
? htmlContentIos
|
|
899
|
+
: templateMessageIos;
|
|
900
|
+
|
|
901
|
+
// Determine type and style for iOS
|
|
902
|
+
const iosType = isHTMLTemplate ? INAPP_MEDIA_TYPES.HTML : templateMediaType;
|
|
903
|
+
const iosExpandableStyle = isHTMLTemplate ? BIG_HTML : BIG_TEXT;
|
|
904
|
+
|
|
905
|
+
// For HTML Editor, check if device has content and is supported
|
|
906
|
+
// For legacy editor, use isDisableDone check
|
|
907
|
+
// Only include devices that have content - allows Android-only, iOS-only, or both
|
|
908
|
+
const shouldIncludeIos = isHTMLTemplate
|
|
909
|
+
? (iosSupported && hasIosContent)
|
|
910
|
+
: !isDisableDone(IOS);
|
|
911
|
+
|
|
912
|
+
// Construct iOS content if device is supported and has content
|
|
913
|
+
// Even when both devices are supported, only include devices that have content
|
|
914
|
+
const iosContent = shouldIncludeIos ? {
|
|
738
915
|
...commonDevicePayload,
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
916
|
+
type: iosType,
|
|
917
|
+
// Use 'html editor template' as title for HTML editor to differentiate from BEE editor
|
|
918
|
+
title: isHTMLTemplate ? 'html editor template' : titleIos,
|
|
919
|
+
message: iosMessage,
|
|
920
|
+
bodyType: bodyTypeForBackend,
|
|
742
921
|
expandableDetails: {
|
|
743
|
-
style:
|
|
744
|
-
message:
|
|
922
|
+
style: iosExpandableStyle,
|
|
923
|
+
message: iosMessage,
|
|
745
924
|
...iosMediaParams,
|
|
746
925
|
...(isBtnTypeCTaIos && {
|
|
747
926
|
ctas: getCtaPayload(IOS),
|
|
@@ -749,12 +928,16 @@ export const InApp = (props) => {
|
|
|
749
928
|
},
|
|
750
929
|
custom: [],
|
|
751
930
|
...(deepLinkValueIos && {
|
|
752
|
-
ctas: [{type: DEEP_LINK, actionLink: deepLinkValueIos}],
|
|
931
|
+
ctas: [{ type: DEEP_LINK, actionLink: deepLinkValueIos }],
|
|
753
932
|
}),
|
|
754
933
|
} : {};
|
|
755
|
-
|
|
934
|
+
|
|
935
|
+
// Ensure name is always set - use tempName as fallback if templateName is empty
|
|
936
|
+
const templateNameValue = isEditFlow ? tempName : (templateName || tempName || '');
|
|
937
|
+
const trimmedName = (templateNameValue && typeof templateNameValue === 'string') ? templateNameValue?.trim() : '';
|
|
938
|
+
|
|
756
939
|
const data = {
|
|
757
|
-
name:
|
|
940
|
+
name: trimmedName,
|
|
758
941
|
versions: {
|
|
759
942
|
base: {
|
|
760
943
|
content: {
|
|
@@ -773,28 +956,26 @@ export const InApp = (props) => {
|
|
|
773
956
|
return data;
|
|
774
957
|
};
|
|
775
958
|
|
|
776
|
-
const actionCallback = ({
|
|
777
|
-
if (!
|
|
959
|
+
const actionCallback = ({ errorMsg }) => {
|
|
960
|
+
if (!errorMsg) {
|
|
778
961
|
CapNotification.success({
|
|
779
|
-
message: isEditFlow
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
name: templateName,
|
|
783
|
-
}),
|
|
962
|
+
message: isEditFlow
|
|
963
|
+
? formatMessage(messages.inAppEditNotification)
|
|
964
|
+
: formatMessage(messages.inAppCreateNotification),
|
|
784
965
|
});
|
|
785
966
|
actions.clearCreateResponse();
|
|
786
967
|
} else {
|
|
787
968
|
CapNotification.error({
|
|
788
|
-
message: JSON.stringify(
|
|
969
|
+
message: JSON.stringify(errorMsg),
|
|
789
970
|
});
|
|
790
971
|
}
|
|
791
972
|
};
|
|
792
973
|
|
|
793
974
|
const onCreateInApp = () => {
|
|
794
975
|
setSpin(true);
|
|
795
|
-
actions.createInAppTemplate(createPayload(), (resp,
|
|
796
|
-
actionCallback({ resp,
|
|
797
|
-
if (!
|
|
976
|
+
actions.createInAppTemplate(createPayload(), (resp, errorMsg) => {
|
|
977
|
+
actionCallback({ resp, errorMsg });
|
|
978
|
+
if (!errorMsg) {
|
|
798
979
|
onCreateComplete();
|
|
799
980
|
} else {
|
|
800
981
|
setSpin(false);
|
|
@@ -809,9 +990,9 @@ export const InApp = (props) => {
|
|
|
809
990
|
...createPayload(),
|
|
810
991
|
_id: params.id,
|
|
811
992
|
},
|
|
812
|
-
(resp,
|
|
813
|
-
actionCallback({ resp,
|
|
814
|
-
if (!
|
|
993
|
+
(resp, errorMsg) => {
|
|
994
|
+
actionCallback({ resp, errorMsg });
|
|
995
|
+
if (!errorMsg) {
|
|
815
996
|
onCreateComplete();
|
|
816
997
|
} else {
|
|
817
998
|
setSpin(false);
|
|
@@ -820,6 +1001,67 @@ export const InApp = (props) => {
|
|
|
820
1001
|
);
|
|
821
1002
|
};
|
|
822
1003
|
|
|
1004
|
+
// Handle HTML content changes from HTMLEditor
|
|
1005
|
+
const handleHtmlContentChange = useCallback((deviceContent, changedDevice) => {
|
|
1006
|
+
// The onChange callback from useInAppContent passes the full deviceContent object
|
|
1007
|
+
// But we only want to update the state for the device that actually changed
|
|
1008
|
+
// Use the second parameter (changedDevice) if provided, otherwise update both
|
|
1009
|
+
|
|
1010
|
+
// Clear validation errors when content changes (similar to Bee Editor)
|
|
1011
|
+
// This ensures Done button re-enables after user fixes errors
|
|
1012
|
+
if (changedDevice) {
|
|
1013
|
+
setErrorMessage((prev) => ({
|
|
1014
|
+
STANDARD_ERROR_MSG: {
|
|
1015
|
+
...prev.STANDARD_ERROR_MSG,
|
|
1016
|
+
[changedDevice.toUpperCase()]: [],
|
|
1017
|
+
GENERIC: [],
|
|
1018
|
+
},
|
|
1019
|
+
LIQUID_ERROR_MSG: {
|
|
1020
|
+
...prev.LIQUID_ERROR_MSG,
|
|
1021
|
+
[changedDevice.toUpperCase()]: [],
|
|
1022
|
+
GENERIC: [],
|
|
1023
|
+
},
|
|
1024
|
+
}));
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (changedDevice) {
|
|
1028
|
+
// Only update the device that actually changed
|
|
1029
|
+
if (changedDevice.toUpperCase() === ANDROID && deviceContent?.android !== undefined) {
|
|
1030
|
+
setHtmlContentAndroid(deviceContent.android || '');
|
|
1031
|
+
} else if (changedDevice.toUpperCase() === IOS_CAPITAL && deviceContent?.ios !== undefined) {
|
|
1032
|
+
setHtmlContentIos(deviceContent.ios || '');
|
|
1033
|
+
}
|
|
1034
|
+
} else {
|
|
1035
|
+
// Fallback: update both if changedDevice not provided (for backward compatibility)
|
|
1036
|
+
// Only update if value actually changed to avoid unnecessary re-renders
|
|
1037
|
+
if (deviceContent?.android !== undefined) {
|
|
1038
|
+
setHtmlContentAndroid((prev) => {
|
|
1039
|
+
const newValue = deviceContent.android || '';
|
|
1040
|
+
return prev !== newValue ? newValue : prev;
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
if (deviceContent?.ios !== undefined) {
|
|
1044
|
+
setHtmlContentIos((prev) => {
|
|
1045
|
+
const newValue = deviceContent.ios || '';
|
|
1046
|
+
return prev !== newValue ? newValue : prev;
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}, [ANDROID, IOS, setErrorMessage]);
|
|
1051
|
+
|
|
1052
|
+
// Handle HTML save from HTMLEditor
|
|
1053
|
+
const handleHtmlSave = useCallback((deviceContent) => {
|
|
1054
|
+
// Update state for both devices if present in the callback
|
|
1055
|
+
if (deviceContent?.android !== undefined) {
|
|
1056
|
+
setHtmlContentAndroid(deviceContent.android || '');
|
|
1057
|
+
}
|
|
1058
|
+
if (deviceContent?.ios !== undefined) {
|
|
1059
|
+
setHtmlContentIos(deviceContent.ios || '');
|
|
1060
|
+
}
|
|
1061
|
+
// Auto-save can trigger this, but we don't want to submit automatically
|
|
1062
|
+
// The user will click the Create/Update button to submit
|
|
1063
|
+
}, []);
|
|
1064
|
+
|
|
823
1065
|
const onDoneCallback = () => {
|
|
824
1066
|
if (isFullMode) {
|
|
825
1067
|
if (isEditFlow) {
|
|
@@ -827,15 +1069,17 @@ export const InApp = (props) => {
|
|
|
827
1069
|
}
|
|
828
1070
|
return onCreateInApp();
|
|
829
1071
|
}
|
|
830
|
-
|
|
831
|
-
|
|
1072
|
+
const payload = createPayload();
|
|
1073
|
+
getFormData({
|
|
1074
|
+
value: payload,
|
|
832
1075
|
_id: params && params.id,
|
|
833
1076
|
validity: true,
|
|
834
1077
|
type: INAPP,
|
|
835
1078
|
});
|
|
836
1079
|
};
|
|
837
1080
|
|
|
838
|
-
|
|
1081
|
+
// Validation middleware for tag validation (both liquid and non-liquid flow)
|
|
1082
|
+
const validationMiddleWare = async () => {
|
|
839
1083
|
// Set up callbacks for validation results
|
|
840
1084
|
const onError = ({ standardErrors, liquidErrors }) => {
|
|
841
1085
|
setErrorMessage((prev) => ({
|
|
@@ -848,45 +1092,200 @@ export const InApp = (props) => {
|
|
|
848
1092
|
onDoneCallback();
|
|
849
1093
|
};
|
|
850
1094
|
|
|
851
|
-
//
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1095
|
+
// Skip validation if no tags are available (e.g., in tests or when tags haven't loaded)
|
|
1096
|
+
const hasTags = tags && tags.length > 0;
|
|
1097
|
+
|
|
1098
|
+
// For liquid flow, use validateInAppContent
|
|
1099
|
+
if (isLiquidFlow && hasTags) {
|
|
1100
|
+
// Validate the INAPP content
|
|
1101
|
+
const payload = createPayload();
|
|
1102
|
+
validateInAppContent(payload, {
|
|
1103
|
+
currentTab: panes === ANDROID ? 1 : 2, // Convert ANDROID/IOS to tab numbers
|
|
1104
|
+
onError,
|
|
1105
|
+
onSuccess,
|
|
1106
|
+
getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
|
|
1107
|
+
formatMessage,
|
|
1108
|
+
messages: formBuilderMessages,
|
|
1109
|
+
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
1110
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1111
|
+
isLiquidFlow,
|
|
1112
|
+
forwardedTags: {},
|
|
1113
|
+
skipTags: (tag) => {
|
|
1114
|
+
// Skip certain tags if needed
|
|
1115
|
+
const skipRegexes = [
|
|
1116
|
+
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
1117
|
+
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
1118
|
+
/Link_to_[a-zA-z]/,
|
|
1119
|
+
/SURVEY.*\.TOKEN/,
|
|
1120
|
+
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
1121
|
+
];
|
|
1122
|
+
|
|
1123
|
+
return skipRegexes.some((regex) => regex.test(tag));
|
|
1124
|
+
},
|
|
1125
|
+
singleTab: getSingleTab(accountData),
|
|
1126
|
+
});
|
|
1127
|
+
} else if (hasTags) {
|
|
1128
|
+
// For non-liquid flow, validate tags using validateTags (only if tags are available)
|
|
1129
|
+
const androidContent = htmlContentAndroid || '';
|
|
1130
|
+
const iosContent = htmlContentIos || '';
|
|
1131
|
+
|
|
1132
|
+
const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
|
|
1133
|
+
const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
|
|
1134
|
+
|
|
1135
|
+
let hasErrors = false;
|
|
1136
|
+
const newErrors = {
|
|
1137
|
+
STANDARD_ERROR_MSG: {
|
|
1138
|
+
ANDROID: [],
|
|
1139
|
+
IOS: [],
|
|
1140
|
+
GENERIC: [],
|
|
1141
|
+
},
|
|
1142
|
+
LIQUID_ERROR_MSG: {
|
|
1143
|
+
ANDROID: [],
|
|
1144
|
+
IOS: [],
|
|
1145
|
+
GENERIC: [],
|
|
1146
|
+
},
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
// Validate Android content
|
|
1150
|
+
if (androidSupported && androidContent && androidContent?.trim() !== '') {
|
|
1151
|
+
const validationResponse = validateTags({
|
|
1152
|
+
content: androidContent,
|
|
1153
|
+
tagsParam: tags,
|
|
1154
|
+
injectedTagsParams: injectedTags || {},
|
|
1155
|
+
location,
|
|
1156
|
+
tagModule: getDefaultTags,
|
|
1157
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1158
|
+
}) || {};
|
|
1159
|
+
|
|
1160
|
+
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
1161
|
+
hasErrors = true;
|
|
1162
|
+
newErrors.LIQUID_ERROR_MSG.ANDROID.push(
|
|
1163
|
+
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1164
|
+
unsupportedTags: validationResponse.unsupportedTags.join(", "),
|
|
1165
|
+
})
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
if (validationResponse?.isBraceError) {
|
|
1169
|
+
hasErrors = true;
|
|
1170
|
+
newErrors.LIQUID_ERROR_MSG.ANDROID.push(
|
|
1171
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Validate iOS content
|
|
1177
|
+
if (iosSupported && iosContent && iosContent?.trim() !== '') {
|
|
1178
|
+
const validationResponse = validateTags({
|
|
1179
|
+
content: iosContent,
|
|
1180
|
+
tagsParam: tags,
|
|
1181
|
+
injectedTagsParams: injectedTags || {},
|
|
1182
|
+
location,
|
|
1183
|
+
tagModule: getDefaultTags,
|
|
1184
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1185
|
+
}) || {};
|
|
1186
|
+
|
|
1187
|
+
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
1188
|
+
hasErrors = true;
|
|
1189
|
+
newErrors.LIQUID_ERROR_MSG.IOS.push(
|
|
1190
|
+
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1191
|
+
unsupportedTags: validationResponse.unsupportedTags.join(", "),
|
|
1192
|
+
})
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
if (validationResponse?.isBraceError) {
|
|
1196
|
+
hasErrors = true;
|
|
1197
|
+
newErrors.LIQUID_ERROR_MSG.IOS.push(
|
|
1198
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if (hasErrors) {
|
|
1204
|
+
setErrorMessage(newErrors);
|
|
1205
|
+
} else {
|
|
1206
|
+
// No errors, proceed with submission
|
|
1207
|
+
onSuccess();
|
|
1208
|
+
}
|
|
1209
|
+
} else {
|
|
1210
|
+
// No tags available, skip validation and proceed directly
|
|
1211
|
+
onSuccess();
|
|
1212
|
+
}
|
|
878
1213
|
};
|
|
879
1214
|
|
|
880
1215
|
const isLiquidFlow = hasLiquidSupportFeature();
|
|
1216
|
+
|
|
1217
|
+
// Check template data to determine editor type (for render decision)
|
|
1218
|
+
const templateDetails = isFullMode ? editData?.templateDetails : templateData;
|
|
1219
|
+
const versions = templateDetails?.versions || {};
|
|
1220
|
+
const templateContent = get(versions, 'base.content', {});
|
|
1221
|
+
const androidContent = templateContent?.ANDROID || {};
|
|
1222
|
+
const iosContent = templateContent?.IOS || {};
|
|
1223
|
+
|
|
1224
|
+
// Check if this is a Bee editor template
|
|
1225
|
+
const isBEEeditor = get(androidContent, 'isBEEeditor') || get(iosContent, 'isBEEeditor');
|
|
1226
|
+
const androidTitle = androidContent?.title || '';
|
|
1227
|
+
const iosTitle = iosContent?.title || '';
|
|
1228
|
+
const isBeeFreeTemplate = (!isEmpty(androidTitle) && androidTitle.toLowerCase() === 'bee free template')
|
|
1229
|
+
|| (!isEmpty(iosTitle) && iosTitle.toLowerCase() === 'bee free template');
|
|
1230
|
+
|
|
1231
|
+
// Check if this is an HTML template from content data
|
|
1232
|
+
const androidType = androidContent?.type || '';
|
|
1233
|
+
const androidStyle = androidContent?.expandableDetails?.style || '';
|
|
1234
|
+
const iosType = iosContent?.type || '';
|
|
1235
|
+
const iosStyle = iosContent?.expandableDetails?.style || '';
|
|
1236
|
+
const isHTMLTemplateFromData = (androidType === INAPP_MEDIA_TYPES.HTML || androidStyle === BIG_HTML
|
|
1237
|
+
|| iosType === INAPP_MEDIA_TYPES.HTML || iosStyle === BIG_HTML)
|
|
1238
|
+
&& !isBEEeditor
|
|
1239
|
+
&& !isBeeFreeTemplate;
|
|
1240
|
+
|
|
1241
|
+
// Use state if available, otherwise fall back to direct data check
|
|
1242
|
+
const shouldUseHTMLEditor = isHTMLTemplate || isHTMLTemplateFromData;
|
|
1243
|
+
|
|
1244
|
+
// Only route to Bee editor if it's explicitly a Bee editor AND not an HTML template
|
|
1245
|
+
const shouldUseBeeEditor = (isBEEeditor || isBeeFreeTemplate) && !shouldUseHTMLEditor;
|
|
1246
|
+
|
|
1247
|
+
// Early returns to avoid nested ternary
|
|
1248
|
+
if (isEditInApp && getTemplateDetailsInProgress) {
|
|
1249
|
+
return <CapSpin spinning={getTemplateDetailsInProgress} />;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Allow BEE editor in both full mode edit (isEditInApp) AND library mode edit (isEditFlow && !isFullMode)
|
|
1253
|
+
if ((isEditInApp || (isEditFlow && !isFullMode)) && shouldUseBeeEditor) {
|
|
1254
|
+
return (
|
|
1255
|
+
<InappAdvanced
|
|
1256
|
+
getFormData={getFormData}
|
|
1257
|
+
setIsLoadingContent={setIsLoadingContent}
|
|
1258
|
+
defaultData={{ "template-name": tempName }}
|
|
1259
|
+
location={{
|
|
1260
|
+
pathname: `/inapp/edit`,
|
|
1261
|
+
query,
|
|
1262
|
+
search: '',
|
|
1263
|
+
}}
|
|
1264
|
+
params={{ mode: inAppCreateMode, id: params.id }}
|
|
1265
|
+
isFullMode={isFullMode}
|
|
1266
|
+
isGetFormData={isGetFormData}
|
|
1267
|
+
showTemplateName={showTemplateName}
|
|
1268
|
+
route={{ name: "inapp" }}
|
|
1269
|
+
getDefaultTags={type}
|
|
1270
|
+
onValidationFail={onValidationFail}
|
|
1271
|
+
templateData={templateData}
|
|
1272
|
+
templateName={tempName}
|
|
1273
|
+
setTemplateName={setTempName}
|
|
1274
|
+
forwardedTags={forwardedTags}
|
|
1275
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1276
|
+
onCreateComplete={onCreateComplete}
|
|
1277
|
+
/>
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
881
1281
|
return (
|
|
882
1282
|
<CapSpin spinning={spin || fetchingLiquidValidation} tip={fetchingLiquidValidation ? <FormattedMessage {...formBuilderMessages.liquidSpinText} /> : ""}>
|
|
883
1283
|
<CapRow className="cap-inapp-creatives">
|
|
884
|
-
<CapColumn span={
|
|
885
|
-
{
|
|
886
|
-
{
|
|
887
|
-
{(isFullMode || !isEditFlow) && (
|
|
1284
|
+
<CapColumn span={shouldUseHTMLEditor ? 18 : 24}>
|
|
1285
|
+
{/* Creative layout type */}
|
|
1286
|
+
{shouldUseHTMLEditor && (
|
|
888
1287
|
<>
|
|
889
|
-
<CapRow
|
|
1288
|
+
<CapRow>
|
|
890
1289
|
<CapHeading type="h4">
|
|
891
1290
|
<FormattedMessage {...messages.creativeLayout} />
|
|
892
1291
|
</CapHeading>
|
|
@@ -903,26 +1302,64 @@ export const InApp = (props) => {
|
|
|
903
1302
|
/>
|
|
904
1303
|
</>
|
|
905
1304
|
)}
|
|
906
|
-
{
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1305
|
+
{shouldUseHTMLEditor ? (
|
|
1306
|
+
<HTMLEditor
|
|
1307
|
+
key={`inapp-html-editor-v${htmlEditorContentVersion}`}
|
|
1308
|
+
variant={HTML_EDITOR_VARIANTS.INAPP}
|
|
1309
|
+
layoutType={templateLayoutType}
|
|
1310
|
+
initialContent={{
|
|
1311
|
+
android: htmlContentAndroidRef.current || htmlContentAndroid || templateMessageAndroid || '',
|
|
1312
|
+
ios: htmlContentIosRef.current || htmlContentIos || templateMessageIos || '',
|
|
1313
|
+
}}
|
|
1314
|
+
onContentChange={handleHtmlContentChange}
|
|
1315
|
+
onSave={handleHtmlSave}
|
|
1316
|
+
tags={tags}
|
|
1317
|
+
injectedTags={injectedTags}
|
|
1318
|
+
location={location}
|
|
1319
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1320
|
+
onTagSelect={() => {
|
|
1321
|
+
// Tag insertion is handled by HTMLEditor's CodeEditorPane at cursor position
|
|
1322
|
+
// Content updates will be propagated via onContentChange callback
|
|
1323
|
+
}}
|
|
1324
|
+
// Don't pass globalActions to prevent duplicate API calls
|
|
1325
|
+
// HTMLEditor will use onContextChange instead
|
|
1326
|
+
onContextChange={handleOnTagsContextChange}
|
|
1327
|
+
// Pass validation errors to HTMLEditor for display
|
|
1328
|
+
errors={errorMessage}
|
|
1329
|
+
isFullMode={isFullMode}
|
|
1330
|
+
data-test="inapp-html-editor"
|
|
1331
|
+
style={{ width: '138%' }}
|
|
1332
|
+
/>
|
|
1333
|
+
) : (
|
|
1334
|
+
<InappAdvanced
|
|
1335
|
+
getFormData={getFormData}
|
|
1336
|
+
setIsLoadingContent={setIsLoadingContent}
|
|
1337
|
+
defaultData={{ "template-name": tempName }}
|
|
1338
|
+
location={{
|
|
1339
|
+
pathname: `/inapp/create`,
|
|
1340
|
+
query,
|
|
1341
|
+
search: '',
|
|
1342
|
+
}}
|
|
1343
|
+
params={{ mode: inAppCreateMode }}
|
|
1344
|
+
isFullMode={isFullMode}
|
|
1345
|
+
isGetFormData={isGetFormData}
|
|
1346
|
+
showTemplateName={showTemplateName}
|
|
1347
|
+
route={{ name: "inapp" }}
|
|
1348
|
+
getDefaultTags={type}
|
|
1349
|
+
onValidationFail={onValidationFail}
|
|
1350
|
+
templateData={templateData}
|
|
1351
|
+
templateName={tempName}
|
|
1352
|
+
setTemplateName={setTempName}
|
|
1353
|
+
forwardedTags={forwardedTags}
|
|
1354
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1355
|
+
onCreateComplete={onCreateComplete}
|
|
1356
|
+
/>
|
|
1357
|
+
)}
|
|
922
1358
|
</CapColumn>
|
|
923
1359
|
</CapRow>
|
|
924
|
-
|
|
925
|
-
|
|
1360
|
+
{/* Footer with Done/Update button - Only show for HTML editor, bee editor has its own footer */}
|
|
1361
|
+
{
|
|
1362
|
+
shouldUseHTMLEditor && (
|
|
926
1363
|
<>
|
|
927
1364
|
{hasAnyErrors(errorMessage) && (
|
|
928
1365
|
<ErrorInfoNote
|
|
@@ -930,24 +1367,28 @@ export const InApp = (props) => {
|
|
|
930
1367
|
currentTab={panes}
|
|
931
1368
|
/>
|
|
932
1369
|
)}
|
|
933
|
-
<
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1370
|
+
<div className={`inapp-footer ${!isFullMode && `inapp-footer-lib`}`}>
|
|
1371
|
+
<CapButton
|
|
1372
|
+
onClick={validationMiddleWare}
|
|
1373
|
+
disabled={shouldUseHTMLEditor ? isDisableDone(null) : isDisableDone(panes)}
|
|
1374
|
+
className="inapp-create-btn"
|
|
1375
|
+
>
|
|
1376
|
+
{(() => {
|
|
1377
|
+
if (isEditFlow) {
|
|
1378
|
+
return isFullMode ? (
|
|
1379
|
+
<FormattedMessage {...messages.update} />
|
|
1380
|
+
) : (
|
|
1381
|
+
<FormattedMessage {...globalMessages.done} />
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
return isFullMode ? (
|
|
1385
|
+
<FormattedMessage {...messages.create} />
|
|
1386
|
+
) : (
|
|
1387
|
+
<FormattedMessage {...globalMessages.done} />
|
|
1388
|
+
);
|
|
1389
|
+
})()}
|
|
1390
|
+
</CapButton>
|
|
1391
|
+
{/* {!isFullMode && ( */}
|
|
951
1392
|
<CapButton
|
|
952
1393
|
onClick={handleTestAndPreview}
|
|
953
1394
|
className="inapp-test-preview-btn"
|
|
@@ -956,10 +1397,10 @@ export const InApp = (props) => {
|
|
|
956
1397
|
>
|
|
957
1398
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
958
1399
|
</CapButton>
|
|
959
|
-
|
|
1400
|
+
{/* )} */}
|
|
1401
|
+
</div>
|
|
960
1402
|
</>
|
|
961
|
-
}
|
|
962
|
-
</div>
|
|
1403
|
+
)}
|
|
963
1404
|
<TestAndPreviewSlidebox
|
|
964
1405
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
965
1406
|
onClose={handleCloseTestAndPreview}
|
|
@@ -979,7 +1420,8 @@ const mapStateToProps = createStructuredSelector({
|
|
|
979
1420
|
loadingTags: isLoadingMetaEntities(),
|
|
980
1421
|
injectedTags: setInjectedTags(),
|
|
981
1422
|
currentOrgDetails: selectCurrentOrgDetails(),
|
|
982
|
-
fetchingLiquidValidation: selectLiquidStateDetails()
|
|
1423
|
+
fetchingLiquidValidation: selectLiquidStateDetails(),
|
|
1424
|
+
getTemplateDetailsInProgress: makeSelectGetTemplateDetailsInProgress(),
|
|
983
1425
|
});
|
|
984
1426
|
|
|
985
1427
|
const mapDispatchToProps = (dispatch) => ({
|