@capillarytech/creatives-library 8.0.125 → 8.0.127-alpha.0
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/config/app.js +6 -0
- package/containers/App/constants.js +1 -0
- package/index.html +3 -1
- package/initialReducer.js +2 -0
- package/package.json +1 -1
- package/services/api.js +94 -1
- package/services/tests/api.test.js +191 -0
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
- package/tests/integration/TemplateCreation/api-response.js +5 -0
- package/tests/integration/TemplateCreation/msw-handler.js +42 -63
- package/utils/common.js +7 -0
- package/utils/commonUtils.js +2 -6
- package/utils/createPayload.js +272 -0
- package/utils/tests/createPayload.test.js +761 -0
- package/v2Components/CapImageUpload/index.js +59 -46
- package/v2Components/CapInAppCTA/index.js +1 -0
- package/v2Components/CapMpushCTA/constants.js +25 -0
- package/v2Components/CapMpushCTA/index.js +332 -0
- package/v2Components/CapMpushCTA/index.scss +95 -0
- package/v2Components/CapMpushCTA/messages.js +89 -0
- package/v2Components/CapTagList/index.js +177 -120
- package/v2Components/CapVideoUpload/constants.js +3 -0
- package/v2Components/CapVideoUpload/index.js +167 -110
- package/v2Components/CapVideoUpload/messages.js +16 -0
- package/v2Components/Carousel/index.js +15 -13
- package/v2Components/CustomerSearchSection/_customerSearch.scss +309 -0
- package/v2Components/CustomerSearchSection/constants.js +5 -0
- package/v2Components/CustomerSearchSection/index.js +367 -0
- package/v2Components/CustomerSearchSection/messages.js +20 -0
- package/v2Components/CustomerSearchSection/tests/utils.test.js +334 -0
- package/v2Components/CustomerSearchSection/utils.js +49 -0
- package/v2Components/ErrorInfoNote/style.scss +1 -0
- package/v2Components/MobilePushPreviewV2/index.js +37 -5
- package/v2Components/TemplatePreview/_templatePreview.scss +114 -72
- package/v2Components/TemplatePreview/assets/images/Android _ With date and time.svg +29 -0
- package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
- package/v2Components/TemplatePreview/assets/images/iOS _ With date and time.svg +26 -0
- package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
- package/v2Components/TemplatePreview/index.js +178 -50
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +169 -0
- package/v2Components/TestAndPreviewSlidebox/LeftPanelContent.js +95 -0
- package/v2Components/TestAndPreviewSlidebox/PreviewSection.js +69 -0
- package/v2Components/TestAndPreviewSlidebox/SendTestMessage.js +68 -0
- package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +543 -0
- package/v2Components/TestAndPreviewSlidebox/actions.js +67 -0
- package/v2Components/TestAndPreviewSlidebox/constants.js +67 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +592 -0
- package/v2Components/TestAndPreviewSlidebox/messages.js +147 -0
- package/v2Components/TestAndPreviewSlidebox/reducer.js +233 -0
- package/v2Components/TestAndPreviewSlidebox/sagas.js +258 -0
- package/v2Components/TestAndPreviewSlidebox/selectors.js +142 -0
- package/v2Components/TestAndPreviewSlidebox/tests/CustomValuesEditor.test.js +425 -0
- package/v2Components/TestAndPreviewSlidebox/tests/LeftPanelContent.test.js +400 -0
- package/v2Components/TestAndPreviewSlidebox/tests/SendTestMessage.test.js +448 -0
- package/v2Components/TestAndPreviewSlidebox/tests/actions.test.js +80 -0
- package/v2Components/TestAndPreviewSlidebox/tests/reducer.test.js +367 -0
- package/v2Components/TestAndPreviewSlidebox/tests/saga.rtl.test.js +192 -0
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +652 -0
- package/v2Components/TestAndPreviewSlidebox/tests/selector.test.js +182 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +22 -10
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +23 -2
- package/v2Containers/CreativesContainer/index.js +216 -136
- package/v2Containers/CreativesContainer/messages.js +4 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +21 -0
- package/v2Containers/Email/index.js +27 -2
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +10 -0
- package/v2Containers/EmailWrapper/index.js +6 -0
- package/v2Containers/InApp/constants.js +1 -0
- package/v2Containers/InApp/index.js +13 -13
- 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/MobilePush/Create/index.js +1 -0
- package/v2Containers/MobilePush/commonMethods.js +7 -14
- package/v2Containers/MobilePushNew/actions.js +116 -0
- package/v2Containers/MobilePushNew/components/CtaButtons.js +170 -0
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +754 -0
- package/v2Containers/MobilePushNew/components/PlatformContentFields.js +279 -0
- package/v2Containers/MobilePushNew/components/index.js +5 -0
- package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +779 -0
- package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +2114 -0
- package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +343 -0
- package/v2Containers/MobilePushNew/constants.js +115 -0
- package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1299 -0
- package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1223 -0
- package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +246 -0
- package/v2Containers/MobilePushNew/hooks/useUpload.js +726 -0
- package/v2Containers/MobilePushNew/index.js +2280 -0
- package/v2Containers/MobilePushNew/index.scss +308 -0
- package/v2Containers/MobilePushNew/messages.js +226 -0
- package/v2Containers/MobilePushNew/reducer.js +160 -0
- package/v2Containers/MobilePushNew/sagas.js +198 -0
- package/v2Containers/MobilePushNew/selectors.js +55 -0
- package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
- package/v2Containers/MobilePushNew/tests/sagas.test.js +863 -0
- package/v2Containers/MobilePushNew/tests/selectors.test.js +425 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +322 -0
- package/v2Containers/MobilePushNew/utils.js +33 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +23 -5
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
- package/v2Containers/TagList/index.js +56 -10
- package/v2Containers/Templates/_templates.scss +101 -1
- package/v2Containers/Templates/index.js +147 -35
- package/v2Containers/Templates/messages.js +8 -0
- package/v2Containers/Templates/sagas.js +2 -0
- package/v2Containers/Whatsapp/constants.js +1 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +35 -0
- package/v2Containers/Email/tests/index.test.js +0 -35
|
@@ -0,0 +1,2280 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback, useEffect, useMemo, useState, useRef,
|
|
3
|
+
} from "react";
|
|
4
|
+
import PropTypes from "prop-types";
|
|
5
|
+
import { createStructuredSelector, createSelector } from "reselect";
|
|
6
|
+
import { bindActionCreators } from "redux";
|
|
7
|
+
import {
|
|
8
|
+
injectReducer,
|
|
9
|
+
injectSaga,
|
|
10
|
+
} from "@capillarytech/vulcan-react-sdk/utils";
|
|
11
|
+
import { cloneDeep, get, isEmpty } from "lodash";
|
|
12
|
+
import CapCheckbox from "@capillarytech/cap-ui-library/CapCheckbox";
|
|
13
|
+
import CapInput from "@capillarytech/cap-ui-library/CapInput";
|
|
14
|
+
import CapButton from "@capillarytech/cap-ui-library/CapButton";
|
|
15
|
+
import CapRow from "@capillarytech/cap-ui-library/CapRow";
|
|
16
|
+
import CapSpin from "@capillarytech/cap-ui-library/CapSpin";
|
|
17
|
+
import CapTab from "@capillarytech/cap-ui-library/CapTab";
|
|
18
|
+
import CapIcon from "@capillarytech/cap-ui-library/CapIcon";
|
|
19
|
+
import CapColumn from "@capillarytech/cap-ui-library/CapColumn";
|
|
20
|
+
import { intlShape } from "react-intl";
|
|
21
|
+
import "./index.scss";
|
|
22
|
+
import { GA } from "@capillarytech/cap-ui-utils";
|
|
23
|
+
import CapNotification from "@capillarytech/cap-ui-library/CapNotification";
|
|
24
|
+
import { DAEMON } from "@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes";
|
|
25
|
+
import globalMessages from "../Cap/messages";
|
|
26
|
+
import * as actions from "./actions";
|
|
27
|
+
import {
|
|
28
|
+
MEDIA_TYPES_OPTIONS,
|
|
29
|
+
ANDROID,
|
|
30
|
+
IOS,
|
|
31
|
+
MOBILE_PUSH_CHANNEL,
|
|
32
|
+
INITIAL_CONTENT,
|
|
33
|
+
BIG_PICTURE,
|
|
34
|
+
EXTERNAL_LINK,
|
|
35
|
+
GIF,
|
|
36
|
+
CAROUSEL,
|
|
37
|
+
PRIMARY,
|
|
38
|
+
SECONDARY,
|
|
39
|
+
MANUAL_CAROUSEL,
|
|
40
|
+
TEXT,
|
|
41
|
+
} from "./constants";
|
|
42
|
+
import TemplatePreview from "../../v2Components/TemplatePreview";
|
|
43
|
+
import {
|
|
44
|
+
EMBEDDED,
|
|
45
|
+
IMAGE,
|
|
46
|
+
NONE,
|
|
47
|
+
VIDEO,
|
|
48
|
+
DEFAULT,
|
|
49
|
+
FULL,
|
|
50
|
+
TAG,
|
|
51
|
+
LIBRARY,
|
|
52
|
+
ALL,
|
|
53
|
+
} from "../Whatsapp/constants";
|
|
54
|
+
import * as globalActions from "../Cap/actions";
|
|
55
|
+
import {
|
|
56
|
+
isLoadingMetaEntities,
|
|
57
|
+
makeSelectMetaEntities,
|
|
58
|
+
selectCurrentOrgDetails,
|
|
59
|
+
selectLiquidStateDetails,
|
|
60
|
+
setInjectedTags,
|
|
61
|
+
} from "../Cap/selectors";
|
|
62
|
+
import {
|
|
63
|
+
makeSelectMobilePushNew,
|
|
64
|
+
makeSelectUploadedAssetData,
|
|
65
|
+
makeSelectUploadedAssetData0,
|
|
66
|
+
makeSelectUploadedAssetData1,
|
|
67
|
+
makeSelectUploadAssetSuccess,
|
|
68
|
+
makeSelectCreateError,
|
|
69
|
+
makeSelectGetTemplateDetailsInProgress,
|
|
70
|
+
makeSelectAssetUploading,
|
|
71
|
+
} from "./selectors";
|
|
72
|
+
import withCreatives from "../../hoc/withCreatives";
|
|
73
|
+
import { BIG_TEXT, DEEP_LINK, DEVICE_SUPPORTED } from "../InApp/constants";
|
|
74
|
+
import { v2MobilePushSagas } from "./sagas";
|
|
75
|
+
import { getContent } from "../MobilePush/commonMethods";
|
|
76
|
+
import { getMessageObject } from "../../utils/messageUtils";
|
|
77
|
+
import { gtmPush } from "../../utils/gtmTrackers";
|
|
78
|
+
import { createPayload } from "../../utils/createPayload";
|
|
79
|
+
import mobilePushReducer from "./reducer";
|
|
80
|
+
import { hasLiquidSupportFeature } from "../../utils/common";
|
|
81
|
+
import formBuilderMessages from "../../v2Components/FormBuilder/messages";
|
|
82
|
+
import { validateMobilePushContent } from "../../utils/commonUtils";
|
|
83
|
+
import { getSingleTab } from "../InApp/utils";
|
|
84
|
+
import ErrorInfoNote from "../../v2Components/ErrorInfoNote";
|
|
85
|
+
import usePlatformSync from "./hooks/usePlatformSync";
|
|
86
|
+
import useUpload from "./hooks/useUpload";
|
|
87
|
+
import { validateTags } from "../../utils/tagValidations";
|
|
88
|
+
import { PlatformContentFields } from "./components";
|
|
89
|
+
import { CREATE, EDIT, TRACK_CREATE_MPUSH } from "../App/constants";
|
|
90
|
+
import { validateExternalLink, validateDeepLink } from "./utils";
|
|
91
|
+
import messages from "./messages";
|
|
92
|
+
import { EXTERNAL_URL } from "../CreativesContainer/constants";
|
|
93
|
+
|
|
94
|
+
const MobilePushNew = ({
|
|
95
|
+
isFullMode,
|
|
96
|
+
intl,
|
|
97
|
+
onEnterTemplateName,
|
|
98
|
+
onRemoveTemplateName,
|
|
99
|
+
location,
|
|
100
|
+
selectedOfferDetails,
|
|
101
|
+
getDefaultTags,
|
|
102
|
+
injectedTags,
|
|
103
|
+
params,
|
|
104
|
+
templateData = {},
|
|
105
|
+
accountData,
|
|
106
|
+
editData = {},
|
|
107
|
+
mobilePushActions,
|
|
108
|
+
onValidationFail,
|
|
109
|
+
getFormLibraryData,
|
|
110
|
+
uploadedAssetData,
|
|
111
|
+
uploadedAssetData0,
|
|
112
|
+
uploadedAssetData1,
|
|
113
|
+
uploadAssetSuccess,
|
|
114
|
+
metaEntities,
|
|
115
|
+
supportedTags = [],
|
|
116
|
+
globalActions: globalActionsProps,
|
|
117
|
+
handleClose,
|
|
118
|
+
fetchingLiquidValidation,
|
|
119
|
+
createTemplateError,
|
|
120
|
+
isGetFormData,
|
|
121
|
+
getTemplateDetailsInProgress,
|
|
122
|
+
onCreateComplete,
|
|
123
|
+
}) => {
|
|
124
|
+
const { formatMessage } = intl;
|
|
125
|
+
|
|
126
|
+
// Improved edit mode detection for both full and library modes
|
|
127
|
+
const templateId = params?.id || templateData?._id || templateData?.id;
|
|
128
|
+
const isEditMode = !!templateId;
|
|
129
|
+
const computedCreativesMode = isEditMode || templateData?.type === 'MOBILEPUSH' ? 'edit' : 'create';
|
|
130
|
+
|
|
131
|
+
const [sameContent, setSameContent] = useState(false);
|
|
132
|
+
const [templateName, setTemplateName] = useState("");
|
|
133
|
+
const [spin, setSpin] = useState(false);
|
|
134
|
+
const [activeTab, setActiveTab] = useState(ANDROID);
|
|
135
|
+
const [templateNameError, setTemplateNameError] = useState(false);
|
|
136
|
+
// Replace ctaData with platform-specific state
|
|
137
|
+
const [ctaDataAndroid, setCtaDataAndroid] = useState([]);
|
|
138
|
+
const [ctaDataIos, setCtaDataIos] = useState([]);
|
|
139
|
+
const ctaData = activeTab === ANDROID ? ctaDataAndroid : ctaDataIos;
|
|
140
|
+
const [deepLink, setDeepLink] = useState([]);
|
|
141
|
+
const [primaryButtonAndroid, setPrimaryButtonAndroid] = useState(false);
|
|
142
|
+
const [secondaryButtonAndroid, setSecondaryButtonAndroid] = useState(false);
|
|
143
|
+
const [primaryButtonIos, setPrimaryButtonIos] = useState(false);
|
|
144
|
+
const [secondaryButtonIos, setSecondaryButtonIos] = useState(false);
|
|
145
|
+
const [carouselActiveTabIndex, setCarouselActiveTabIndex] = useState(0);
|
|
146
|
+
const [errorMessage, setErrorMessage] = useState({
|
|
147
|
+
STANDARD_ERROR_MSG: {},
|
|
148
|
+
LIQUID_ERROR_MSG: {},
|
|
149
|
+
});
|
|
150
|
+
const [androidContent, setAndroidContent] = useState(INITIAL_CONTENT);
|
|
151
|
+
|
|
152
|
+
const [iosContent, setIosContent] = useState(INITIAL_CONTENT);
|
|
153
|
+
// Refs to track object references for debugging
|
|
154
|
+
const prevEditDataRef = useRef(null);
|
|
155
|
+
const prevUploadedAssetDataRef = useRef(null);
|
|
156
|
+
const prevVideoDataRef = useRef(null);
|
|
157
|
+
|
|
158
|
+
const videoData = useMemo(
|
|
159
|
+
() => {
|
|
160
|
+
const videoDataId = `vd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
161
|
+
const newVideoData = {
|
|
162
|
+
...editData,
|
|
163
|
+
uploadedAssetData0,
|
|
164
|
+
uploadedAssetData1,
|
|
165
|
+
_videoDataId: videoDataId, // Add unique ID for tracking
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Store references for next comparison
|
|
169
|
+
prevEditDataRef.current = editData;
|
|
170
|
+
prevUploadedAssetDataRef.current = uploadedAssetData;
|
|
171
|
+
prevVideoDataRef.current = newVideoData;
|
|
172
|
+
|
|
173
|
+
return newVideoData;
|
|
174
|
+
},
|
|
175
|
+
[editData, uploadedAssetData0, uploadedAssetData1]
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const imageData = useMemo(
|
|
179
|
+
() => {
|
|
180
|
+
// For carousel media type, don't include carousel images in Redux structure
|
|
181
|
+
// since carousel images are stored in component state, not Redux
|
|
182
|
+
const currentContent = activeTab === ANDROID ? androidContent : iosContent;
|
|
183
|
+
|
|
184
|
+
if (currentContent?.mediaType === CAROUSEL) {
|
|
185
|
+
// Return only the base editData and uploadedAssetData for non-carousel assets
|
|
186
|
+
return {
|
|
187
|
+
...editData,
|
|
188
|
+
uploadedAssetData0,
|
|
189
|
+
uploadedAssetData1,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
...editData,
|
|
195
|
+
uploadedAssetData0,
|
|
196
|
+
uploadedAssetData1,
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
[editData, uploadedAssetData0, uploadedAssetData1, activeTab, androidContent, iosContent]
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
const {
|
|
204
|
+
// States
|
|
205
|
+
androidAssetList,
|
|
206
|
+
iosAssetList,
|
|
207
|
+
mpushVideoSrcAndPreview,
|
|
208
|
+
imageSrc,
|
|
209
|
+
videoState,
|
|
210
|
+
// Upload functions
|
|
211
|
+
uploadMpushAsset,
|
|
212
|
+
setUpdateMpushImageSrc,
|
|
213
|
+
setUpdateMpushVideoSrc,
|
|
214
|
+
updateOnMpushImageReUpload,
|
|
215
|
+
updateOnMpushVideoReUpload,
|
|
216
|
+
// Clear functions
|
|
217
|
+
clearImageDataByMediaType,
|
|
218
|
+
resetUploadStates,
|
|
219
|
+
} = useUpload(
|
|
220
|
+
mobilePushActions,
|
|
221
|
+
editData,
|
|
222
|
+
uploadedAssetData0,
|
|
223
|
+
uploadedAssetData1,
|
|
224
|
+
uploadAssetSuccess,
|
|
225
|
+
sameContent,
|
|
226
|
+
setAndroidContent,
|
|
227
|
+
setIosContent,
|
|
228
|
+
activeTab,
|
|
229
|
+
androidContent,
|
|
230
|
+
iosContent
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const [androidTitleError, setAndroidTitleError] = useState("");
|
|
234
|
+
const [androidMessageError, setAndroidMessageError] = useState("");
|
|
235
|
+
const [iosTitleError, setIosTitleError] = useState("");
|
|
236
|
+
const [iosMessageError, setIosMessageError] = useState("");
|
|
237
|
+
|
|
238
|
+
const [tags, updateTags] = useState([]);
|
|
239
|
+
|
|
240
|
+
// Ref to track if schema has been fetched to prevent duplicate calls
|
|
241
|
+
const schemaFetched = useRef(false);
|
|
242
|
+
|
|
243
|
+
// Ref to track previous mode to detect mode changes
|
|
244
|
+
const prevModeRef = useRef({ id: params?.id, isFullMode });
|
|
245
|
+
|
|
246
|
+
// Add URL validation state
|
|
247
|
+
const [androidExternalLinkError, setAndroidExternalLinkError] = useState("");
|
|
248
|
+
const [iosExternalLinkError, setIosExternalLinkError] = useState("");
|
|
249
|
+
const [androidDeepLinkError, setAndroidDeepLinkError] = useState("");
|
|
250
|
+
const [iosDeepLinkError, setIosDeepLinkError] = useState("");
|
|
251
|
+
const [carouselLinkErrors, setCarouselLinkErrors] = useState({});
|
|
252
|
+
|
|
253
|
+
// Ref for create timeout fallback
|
|
254
|
+
const createTimeoutRef = useRef(null);
|
|
255
|
+
|
|
256
|
+
// Function to validate carousel data completeness
|
|
257
|
+
const validateCarouselData = useCallback((carouselData = []) => {
|
|
258
|
+
if (!carouselData || carouselData.length === 0) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return carouselData.every((card) => {
|
|
263
|
+
// Check if image is uploaded (currently only image media type is supported)
|
|
264
|
+
if (card.mediaType === 'image' && !card.imageUrl) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Check if buttons have proper link data when actionOnClick is true
|
|
269
|
+
if (card.buttons && card.buttons.length > 0) {
|
|
270
|
+
return card.buttons.every((button) => {
|
|
271
|
+
if (button.actionOnClick) {
|
|
272
|
+
// Check if link is provided based on link type
|
|
273
|
+
if (button.linkType === DEEP_LINK && !button.deepLinkValue) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
if (button.linkType === EXTERNAL_LINK && !button.externalLinkValue) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return true;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return true;
|
|
285
|
+
});
|
|
286
|
+
}, []);
|
|
287
|
+
|
|
288
|
+
// Function to check if carousel data is valid for both platforms
|
|
289
|
+
const isCarouselDataValid = useCallback(() => {
|
|
290
|
+
// Check Android carousel data
|
|
291
|
+
const androidCarouselValid = androidContent?.mediaType === CAROUSEL
|
|
292
|
+
? validateCarouselData(androidContent.carouselData)
|
|
293
|
+
: true;
|
|
294
|
+
|
|
295
|
+
// Check iOS carousel data (only if not using same content)
|
|
296
|
+
let iosCarouselValid = true;
|
|
297
|
+
if (sameContent) {
|
|
298
|
+
iosCarouselValid = androidCarouselValid; // If same content, iOS uses same data as Android
|
|
299
|
+
} else if (iosContent?.mediaType === CAROUSEL) {
|
|
300
|
+
iosCarouselValid = validateCarouselData(iosContent.carouselData);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return androidCarouselValid && iosCarouselValid;
|
|
304
|
+
}, [androidContent?.mediaType, androidContent?.carouselData, iosContent?.mediaType, iosContent?.carouselData, sameContent, validateCarouselData]);
|
|
305
|
+
|
|
306
|
+
// Define resetFormData early to avoid initialization errors
|
|
307
|
+
const resetFormData = useCallback(() => {
|
|
308
|
+
setTemplateName("");
|
|
309
|
+
setAndroidContent({
|
|
310
|
+
title: "",
|
|
311
|
+
message: "",
|
|
312
|
+
mediaType: NONE,
|
|
313
|
+
buttons: [],
|
|
314
|
+
actionOnClick: false,
|
|
315
|
+
linkType: DEEP_LINK,
|
|
316
|
+
deepLinkValue: "",
|
|
317
|
+
externalLinkValue: "",
|
|
318
|
+
carouselData: [],
|
|
319
|
+
});
|
|
320
|
+
setIosContent({
|
|
321
|
+
title: "",
|
|
322
|
+
message: "",
|
|
323
|
+
mediaType: NONE,
|
|
324
|
+
buttons: [],
|
|
325
|
+
actionOnClick: false,
|
|
326
|
+
linkType: DEEP_LINK,
|
|
327
|
+
deepLinkValue: "",
|
|
328
|
+
externalLinkValue: "",
|
|
329
|
+
carouselData: [],
|
|
330
|
+
});
|
|
331
|
+
// Update resetFormData to reset both ctaDataAndroid and ctaDataIos
|
|
332
|
+
setCtaDataAndroid([]);
|
|
333
|
+
setCtaDataIos([]);
|
|
334
|
+
setPrimaryButtonAndroid(false);
|
|
335
|
+
setSecondaryButtonAndroid(false);
|
|
336
|
+
setPrimaryButtonIos(false);
|
|
337
|
+
setSecondaryButtonIos(false);
|
|
338
|
+
setSameContent(false);
|
|
339
|
+
setTemplateNameError(false);
|
|
340
|
+
setAndroidTitleError("");
|
|
341
|
+
setAndroidMessageError("");
|
|
342
|
+
setIosTitleError("");
|
|
343
|
+
setIosMessageError("");
|
|
344
|
+
setAndroidExternalLinkError("");
|
|
345
|
+
setIosExternalLinkError("");
|
|
346
|
+
setAndroidDeepLinkError("");
|
|
347
|
+
setIosDeepLinkError("");
|
|
348
|
+
setCarouselLinkErrors({});
|
|
349
|
+
resetUploadStates();
|
|
350
|
+
}, [resetUploadStates]);
|
|
351
|
+
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
if (createTemplateError) {
|
|
354
|
+
CapNotification.error({
|
|
355
|
+
message: createTemplateError,
|
|
356
|
+
key: "createMpushError",
|
|
357
|
+
});
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Prevent duplicate schema fetching
|
|
362
|
+
if (schemaFetched.current) return;
|
|
363
|
+
|
|
364
|
+
const { type, module } = location?.query || {};
|
|
365
|
+
const isEmbedded = type === EMBEDDED;
|
|
366
|
+
const context = isEmbedded ? module : DEFAULT;
|
|
367
|
+
const embedded = isEmbedded ? type : FULL;
|
|
368
|
+
const query = {
|
|
369
|
+
layout: 'mobilepush',
|
|
370
|
+
type: TAG,
|
|
371
|
+
context,
|
|
372
|
+
embedded,
|
|
373
|
+
};
|
|
374
|
+
if (getDefaultTags) {
|
|
375
|
+
query.context = getDefaultTags;
|
|
376
|
+
}
|
|
377
|
+
globalActionsProps.fetchSchemaForEntity(query);
|
|
378
|
+
schemaFetched.current = true;
|
|
379
|
+
}, [createTemplateError, location?.query?.type, location?.query?.module, getDefaultTags]);
|
|
380
|
+
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
let tag = get(metaEntities, `tags.standard`, []);
|
|
383
|
+
const { type, module } = location?.query || {};
|
|
384
|
+
if (type === EMBEDDED && module === LIBRARY && !getDefaultTags) {
|
|
385
|
+
tag = supportedTags;
|
|
386
|
+
}
|
|
387
|
+
updateTags(tag);
|
|
388
|
+
}, [metaEntities, location, getDefaultTags, supportedTags]);
|
|
389
|
+
|
|
390
|
+
const templateDescErrorHandler = useCallback(
|
|
391
|
+
(value) => {
|
|
392
|
+
let errorTemplateDescMessage = "";
|
|
393
|
+
|
|
394
|
+
const { unsupportedTags, isBraceError } = validateTags({
|
|
395
|
+
content: value,
|
|
396
|
+
tagsParam: tags,
|
|
397
|
+
injectedTagsParams: injectedTags,
|
|
398
|
+
location,
|
|
399
|
+
tagModule: getDefaultTags,
|
|
400
|
+
}) || {};
|
|
401
|
+
if (value === "" && MEDIA_TYPES_OPTIONS[0].value) {
|
|
402
|
+
errorTemplateDescMessage = formatMessage(
|
|
403
|
+
messages.emptyTemplateDescErrorMessage
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (unsupportedTags?.length > 0) {
|
|
407
|
+
errorTemplateDescMessage = formatMessage(
|
|
408
|
+
globalMessages.unsupportedTagsValidationError,
|
|
409
|
+
{
|
|
410
|
+
unsupportedTags,
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (isBraceError) {
|
|
415
|
+
errorTemplateDescMessage = formatMessage(
|
|
416
|
+
globalMessages.unbalanacedCurlyBraces
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
return errorTemplateDescMessage;
|
|
420
|
+
},
|
|
421
|
+
[tags, injectedTags, location, getDefaultTags, formatMessage]
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
const validateTitle = useCallback(
|
|
425
|
+
(value) => {
|
|
426
|
+
let error = templateDescErrorHandler(value);
|
|
427
|
+
if (!value || value.trim() === "") {
|
|
428
|
+
error = formatMessage(messages.emptyTemplateDescErrorMessage);
|
|
429
|
+
}
|
|
430
|
+
return error || "";
|
|
431
|
+
},
|
|
432
|
+
[templateDescErrorHandler, formatMessage]
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
const validateMessage = useCallback(
|
|
436
|
+
(value) => {
|
|
437
|
+
let error = templateDescErrorHandler(value);
|
|
438
|
+
if (!value || value.trim() === "") {
|
|
439
|
+
error = formatMessage(messages.emptyTemplateDescErrorMessage);
|
|
440
|
+
}
|
|
441
|
+
return error || "";
|
|
442
|
+
},
|
|
443
|
+
[templateDescErrorHandler, formatMessage]
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
const handleOnTagsContextChange = useCallback(
|
|
447
|
+
(data) => {
|
|
448
|
+
const { type } = location?.query || {};
|
|
449
|
+
const tempData = (data || "").toLowerCase();
|
|
450
|
+
const isEmbedded = type === EMBEDDED;
|
|
451
|
+
const embedded = isEmbedded ? type : FULL;
|
|
452
|
+
const context = tempData === ALL ? DEFAULT : tempData;
|
|
453
|
+
const query = {
|
|
454
|
+
layout: 'mobilepush',
|
|
455
|
+
type: TAG,
|
|
456
|
+
context,
|
|
457
|
+
embedded,
|
|
458
|
+
};
|
|
459
|
+
globalActionsProps.fetchSchemaForEntity(query);
|
|
460
|
+
},
|
|
461
|
+
[globalActionsProps, location]
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
// accountData IS the selectedWeChatAccount object based on mapStateToProps
|
|
466
|
+
const accountObj = accountData || {};
|
|
467
|
+
const deepLinkObj = accountObj?.configs?.deeplink || "";
|
|
468
|
+
|
|
469
|
+
if (!isEmpty(accountObj)) {
|
|
470
|
+
const { configs = {} } = accountObj;
|
|
471
|
+
const isAndroidSupported = configs?.android === DEVICE_SUPPORTED;
|
|
472
|
+
|
|
473
|
+
try {
|
|
474
|
+
const parsedDeepLinks = JSON.parse(deepLinkObj || "{}");
|
|
475
|
+
const deepLinkKeys = Object.values(parsedDeepLinks);
|
|
476
|
+
const keys = deepLinkKeys?.map((link) => ({
|
|
477
|
+
label: link?.name,
|
|
478
|
+
value: link?.link,
|
|
479
|
+
title: link?.link,
|
|
480
|
+
}));
|
|
481
|
+
|
|
482
|
+
setActiveTab(isAndroidSupported ? ANDROID : IOS);
|
|
483
|
+
setDeepLink(keys);
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.error("[MobilePushNew] Error parsing deeplinks:", error, { deepLinkObj });
|
|
486
|
+
setDeepLink([]);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}, [accountData?.configs?.deeplink, accountData?.configs?.android]);
|
|
490
|
+
|
|
491
|
+
// Add debug logs for template switching and data population
|
|
492
|
+
useEffect(() => {
|
|
493
|
+
resetFormData();
|
|
494
|
+
if (params?.id) {
|
|
495
|
+
setSpin(true);
|
|
496
|
+
mobilePushActions.getTemplateDetails(params.id);
|
|
497
|
+
}
|
|
498
|
+
// No need to resetStore here, as we want to keep the new template's details
|
|
499
|
+
}, [params?.id, resetFormData, mobilePushActions]);
|
|
500
|
+
|
|
501
|
+
// Add a useEffect to reset form data only when switching from create mode to edit mode
|
|
502
|
+
useEffect(() => {
|
|
503
|
+
const currentMode = { id: params?.id, isFullMode };
|
|
504
|
+
const prevMode = prevModeRef.current;
|
|
505
|
+
const isEditModeNow = !!(params?.id && isFullMode);
|
|
506
|
+
const wasCreateMode = !(prevMode.id && prevMode.isFullMode);
|
|
507
|
+
const isModeChange = prevMode.id !== currentMode.id || prevMode.isFullMode !== currentMode.isFullMode;
|
|
508
|
+
|
|
509
|
+
// Only reset if we're switching from create mode to edit mode
|
|
510
|
+
if (isModeChange && isEditModeNow && wasCreateMode) {
|
|
511
|
+
resetFormData();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
prevModeRef.current = currentMode;
|
|
515
|
+
}, [params?.id, isFullMode, resetFormData]);
|
|
516
|
+
|
|
517
|
+
// Add a useEffect to reset dataPopulated when switching templates
|
|
518
|
+
useEffect(() => {
|
|
519
|
+
// setDataPopulated(false); // Removed
|
|
520
|
+
}, [params?.id]);
|
|
521
|
+
|
|
522
|
+
// Data population useEffect - ONLY for edit mode
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
if (!isEditMode) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Do NOT reset form state here; only populate data
|
|
529
|
+
// resetFormData();
|
|
530
|
+
|
|
531
|
+
const { name = "", versions = {} } = editData?.templateDetails || {};
|
|
532
|
+
const editContent = versions?.base || {};
|
|
533
|
+
|
|
534
|
+
// If templateDetails exist but content is empty/invalid, hide spinner
|
|
535
|
+
if (editData?.templateDetails && isEmpty(editContent)) {
|
|
536
|
+
setSpin(false);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (editContent && !isEmpty(editContent)) {
|
|
541
|
+
// Populate data immediately
|
|
542
|
+
setTemplateName(name);
|
|
543
|
+
// Process Android content
|
|
544
|
+
const androidContentType = editContent?.ANDROID;
|
|
545
|
+
if (!isEmpty(androidContentType)) {
|
|
546
|
+
const {
|
|
547
|
+
title: androidTitle = "",
|
|
548
|
+
message: androidMessage = "",
|
|
549
|
+
ctas: androidCta = [],
|
|
550
|
+
expandableDetails: androidExpandableDetails = {},
|
|
551
|
+
image: androidImage = "",
|
|
552
|
+
cta: androidMainCta = null, // This is the main notification body CTA
|
|
553
|
+
} = androidContentType || {};
|
|
554
|
+
|
|
555
|
+
// Only destructure androidExpandableDetails here
|
|
556
|
+
const { style: androidStyle, ctas: androidCtas = [], image: androidExpandableImage = "" } = androidExpandableDetails || {};
|
|
557
|
+
// In iOS content extraction, use unique names:
|
|
558
|
+
|
|
559
|
+
// Determine media type based on style and available content
|
|
560
|
+
let androidMediaType = NONE;
|
|
561
|
+
let androidImageSrc = "";
|
|
562
|
+
let androidVideoSrc = "";
|
|
563
|
+
let androidVideoPreview = "";
|
|
564
|
+
let carouselDataFromEdit = [];
|
|
565
|
+
|
|
566
|
+
if (androidStyle === BIG_PICTURE) {
|
|
567
|
+
androidMediaType = IMAGE;
|
|
568
|
+
androidImageSrc = androidExpandableImage || androidImage;
|
|
569
|
+
} else if (androidStyle === BIG_TEXT) {
|
|
570
|
+
androidMediaType = NONE;
|
|
571
|
+
} else if (androidStyle === MANUAL_CAROUSEL) {
|
|
572
|
+
androidMediaType = CAROUSEL;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Handle video/GIF content from expandableDetails.media array (new format)
|
|
576
|
+
if (androidExpandableDetails?.media && Array.isArray(androidExpandableDetails.media) && androidExpandableDetails.media.length > 0) {
|
|
577
|
+
const mediaItem = androidExpandableDetails.media[0];
|
|
578
|
+
if (mediaItem.type === VIDEO) {
|
|
579
|
+
// Distinguish between actual video and GIF based on URL extension
|
|
580
|
+
if (mediaItem.url && mediaItem.url.toLowerCase().includes('.gif')) {
|
|
581
|
+
androidMediaType = GIF;
|
|
582
|
+
} else {
|
|
583
|
+
androidMediaType = VIDEO;
|
|
584
|
+
}
|
|
585
|
+
androidVideoSrc = mediaItem.url;
|
|
586
|
+
androidVideoPreview = mediaItem.url;
|
|
587
|
+
} else if (mediaItem.type === GIF) {
|
|
588
|
+
androidMediaType = GIF;
|
|
589
|
+
androidVideoSrc = mediaItem.url;
|
|
590
|
+
androidVideoPreview = mediaItem.url;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// Handle video content if present in image field (legacy format)
|
|
594
|
+
else if (androidImage && (androidImage.includes('.mp4') || androidImage.includes('.mov'))) {
|
|
595
|
+
androidMediaType = VIDEO;
|
|
596
|
+
androidVideoSrc = androidImage;
|
|
597
|
+
androidVideoPreview = androidImage; // Use same for preview
|
|
598
|
+
}
|
|
599
|
+
// Handle GIF content if present in image field (legacy format)
|
|
600
|
+
else if (androidImage && androidImage.toLowerCase().includes('.gif')) {
|
|
601
|
+
androidMediaType = GIF;
|
|
602
|
+
androidVideoSrc = androidImage;
|
|
603
|
+
androidVideoPreview = androidImage; // Use same for preview
|
|
604
|
+
}
|
|
605
|
+
// Handle carousel content from expandableDetails.carouselData
|
|
606
|
+
else if ((androidExpandableDetails?.style === MANUAL_CAROUSEL || androidContentType?.type === CAROUSEL) && androidExpandableDetails?.carouselData) {
|
|
607
|
+
androidMediaType = CAROUSEL;
|
|
608
|
+
// Process carousel data from editData
|
|
609
|
+
carouselDataFromEdit = androidExpandableDetails.carouselData.map((card) => ({
|
|
610
|
+
mediaType: card.mediaType || IMAGE.toLowerCase(),
|
|
611
|
+
imageUrl: card.imageUrl || '',
|
|
612
|
+
videoSrc: card.videoSrc || '',
|
|
613
|
+
buttons: card.buttons || [],
|
|
614
|
+
}));
|
|
615
|
+
|
|
616
|
+
// Initialize carousel images in Redux state for proper display in uploaders
|
|
617
|
+
if (carouselDataFromEdit.length > 0) {
|
|
618
|
+
const firstCard = carouselDataFromEdit[0];
|
|
619
|
+
if (firstCard.mediaType === 'image' && firstCard.imageUrl) {
|
|
620
|
+
// Set the first carousel image in Redux state for the uploader to recognize
|
|
621
|
+
setUpdateMpushImageSrc(firstCard.imageUrl, 0, CAROUSEL);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
let androidButtonsValue = [];
|
|
627
|
+
if (Array.isArray(androidCtas)) {
|
|
628
|
+
androidButtonsValue = androidCtas;
|
|
629
|
+
} else if (Array.isArray(androidCta)) {
|
|
630
|
+
androidButtonsValue = androidCta;
|
|
631
|
+
}
|
|
632
|
+
const androidContentData = {
|
|
633
|
+
title: androidTitle,
|
|
634
|
+
message: androidMessage,
|
|
635
|
+
mediaType: androidMediaType,
|
|
636
|
+
buttons: androidButtonsValue,
|
|
637
|
+
actionOnClick: false,
|
|
638
|
+
linkType: DEEP_LINK,
|
|
639
|
+
deepLinkValue: "",
|
|
640
|
+
externalLinkValue: "",
|
|
641
|
+
imageSrc: androidImageSrc,
|
|
642
|
+
videoSrc: androidVideoSrc,
|
|
643
|
+
videoPreview: androidVideoPreview,
|
|
644
|
+
carouselData: carouselDataFromEdit, // Initialize carousel data
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
setAndroidContent(androidContentData);
|
|
648
|
+
// Set image/video sources for upload state with platform isolation
|
|
649
|
+
if (androidImageSrc) {
|
|
650
|
+
setUpdateMpushImageSrc(androidImageSrc, 0, IMAGE);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Apply exact same simple pattern as images for videos/GIFs
|
|
654
|
+
if (androidVideoSrc) {
|
|
655
|
+
setUpdateMpushVideoSrc(0, {
|
|
656
|
+
videoSrc: androidVideoSrc,
|
|
657
|
+
previewUrl: androidVideoPreview,
|
|
658
|
+
}, true); // isInitialization = true
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Check for main notification body CTA (actionOnClick functionality)
|
|
662
|
+
if (androidMainCta?.actionLink) {
|
|
663
|
+
androidContentData.actionOnClick = true;
|
|
664
|
+
|
|
665
|
+
// Determine link type based on saved CTA type (prioritize saved type over URL pattern)
|
|
666
|
+
if (androidMainCta.type === EXTERNAL_URL) {
|
|
667
|
+
androidContentData.linkType = EXTERNAL_LINK;
|
|
668
|
+
androidContentData.externalLinkValue = androidMainCta.actionLink;
|
|
669
|
+
androidContentData.deepLinkValue = "";
|
|
670
|
+
} else if (androidMainCta.type === DEEP_LINK) {
|
|
671
|
+
androidContentData.linkType = DEEP_LINK;
|
|
672
|
+
androidContentData.deepLinkValue = androidMainCta.actionLink;
|
|
673
|
+
androidContentData.externalLinkValue = "";
|
|
674
|
+
} else if (androidMainCta.actionLink.startsWith('http://') || androidMainCta.actionLink.startsWith('https://')) {
|
|
675
|
+
androidContentData.linkType = EXTERNAL_LINK;
|
|
676
|
+
androidContentData.externalLinkValue = androidMainCta.actionLink;
|
|
677
|
+
androidContentData.deepLinkValue = "";
|
|
678
|
+
} else {
|
|
679
|
+
androidContentData.linkType = DEEP_LINK;
|
|
680
|
+
androidContentData.deepLinkValue = androidMainCta.actionLink;
|
|
681
|
+
androidContentData.externalLinkValue = "";
|
|
682
|
+
}
|
|
683
|
+
} else {
|
|
684
|
+
// If no CTA data, ensure both link values are empty
|
|
685
|
+
androidContentData.deepLinkValue = "";
|
|
686
|
+
androidContentData.externalLinkValue = "";
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Set CTA data for buttons (from expandableDetails.ctas)
|
|
690
|
+
const androidButtons = androidExpandableDetails?.ctas || [];
|
|
691
|
+
if (androidButtons.length > 0) {
|
|
692
|
+
const ctaDataFromAndroid = androidButtons.map((button, index) => ({
|
|
693
|
+
text: button.actionText || button.label || "",
|
|
694
|
+
index,
|
|
695
|
+
url: button.actionLink || "",
|
|
696
|
+
urlType: button.type || DEEP_LINK,
|
|
697
|
+
ctaType: index === 0 ? PRIMARY : SECONDARY,
|
|
698
|
+
isSaved: true, // Mark as saved since it's loaded from backend
|
|
699
|
+
}));
|
|
700
|
+
setCtaDataAndroid(ctaDataFromAndroid);
|
|
701
|
+
setPrimaryButtonAndroid(androidButtons.length >= 1);
|
|
702
|
+
setSecondaryButtonAndroid(androidButtons.length >= 2);
|
|
703
|
+
} else {
|
|
704
|
+
setCtaDataAndroid([]);
|
|
705
|
+
setPrimaryButtonAndroid(false);
|
|
706
|
+
setSecondaryButtonAndroid(false);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Process iOS content
|
|
711
|
+
const iosContentType = editContent?.IOS;
|
|
712
|
+
if (!isEmpty(iosContentType)) {
|
|
713
|
+
const {
|
|
714
|
+
title: iosTitle = "",
|
|
715
|
+
message: iosMessage = "",
|
|
716
|
+
ctas = [],
|
|
717
|
+
expandableDetails: iosExpandableDetails = {},
|
|
718
|
+
image: iosImage = "",
|
|
719
|
+
// Removed unused iosCta and iosMainCta variables
|
|
720
|
+
} = iosContentType || {};
|
|
721
|
+
|
|
722
|
+
// Only destructure iosExpandableDetails here
|
|
723
|
+
const { style: iosStyle, image: iosExpandableImage = "" } = iosExpandableDetails || {};
|
|
724
|
+
// In iOS content extraction, use unique names:
|
|
725
|
+
|
|
726
|
+
// Determine media type based on style and available content
|
|
727
|
+
let iosMediaType = NONE;
|
|
728
|
+
let iosImageSrc = "";
|
|
729
|
+
let iosVideoSrc = "";
|
|
730
|
+
let iosVideoPreview = "";
|
|
731
|
+
let carouselDataFromEdit = [];
|
|
732
|
+
|
|
733
|
+
if (iosStyle === BIG_PICTURE) {
|
|
734
|
+
iosMediaType = IMAGE;
|
|
735
|
+
iosImageSrc = iosExpandableImage || iosImage;
|
|
736
|
+
} else if (iosStyle === BIG_TEXT) {
|
|
737
|
+
iosMediaType = NONE;
|
|
738
|
+
} else if (iosStyle === MANUAL_CAROUSEL) {
|
|
739
|
+
iosMediaType = CAROUSEL;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Handle video/GIF content from expandableDetails.media array (new format)
|
|
743
|
+
if (iosExpandableDetails?.media && Array.isArray(iosExpandableDetails.media) && iosExpandableDetails.media.length > 0) {
|
|
744
|
+
const mediaItem = iosExpandableDetails.media[0];
|
|
745
|
+
if (mediaItem.type === VIDEO) {
|
|
746
|
+
// Distinguish between actual video and GIF based on URL extension
|
|
747
|
+
if (mediaItem.url && mediaItem.url.toLowerCase().includes('.gif')) {
|
|
748
|
+
iosMediaType = GIF;
|
|
749
|
+
} else {
|
|
750
|
+
iosMediaType = VIDEO;
|
|
751
|
+
}
|
|
752
|
+
iosVideoSrc = mediaItem.url;
|
|
753
|
+
iosVideoPreview = mediaItem.url;
|
|
754
|
+
} else if (mediaItem.type === GIF) {
|
|
755
|
+
iosMediaType = GIF;
|
|
756
|
+
iosVideoSrc = mediaItem.url;
|
|
757
|
+
iosVideoPreview = mediaItem.url;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
// Handle video content if present in image field (legacy format)
|
|
761
|
+
else if (iosImage && (iosImage.includes('.mp4') || iosImage.includes('.mov'))) {
|
|
762
|
+
iosMediaType = VIDEO;
|
|
763
|
+
iosVideoSrc = iosImage;
|
|
764
|
+
iosVideoPreview = iosImage; // Use same for preview
|
|
765
|
+
}
|
|
766
|
+
// Handle GIF content if present in image field (legacy format)
|
|
767
|
+
else if (iosImage && iosImage.toLowerCase().includes('.gif')) {
|
|
768
|
+
iosMediaType = GIF;
|
|
769
|
+
iosVideoSrc = iosImage;
|
|
770
|
+
iosVideoPreview = iosImage; // Use same for preview
|
|
771
|
+
}
|
|
772
|
+
// Handle carousel content from expandableDetails.carouselData
|
|
773
|
+
else if ((iosExpandableDetails?.style === MANUAL_CAROUSEL || iosContentType?.type === CAROUSEL) && iosExpandableDetails?.carouselData) {
|
|
774
|
+
iosMediaType = CAROUSEL;
|
|
775
|
+
// Process carousel data from editData
|
|
776
|
+
carouselDataFromEdit = iosExpandableDetails.carouselData.map((card) => ({
|
|
777
|
+
mediaType: card.mediaType || IMAGE.toLowerCase(),
|
|
778
|
+
imageUrl: card.imageUrl || '',
|
|
779
|
+
videoSrc: card.videoSrc || '',
|
|
780
|
+
buttons: card.buttons || [],
|
|
781
|
+
}));
|
|
782
|
+
|
|
783
|
+
// Initialize carousel images in Redux state for proper display in uploaders
|
|
784
|
+
if (carouselDataFromEdit.length > 0) {
|
|
785
|
+
const firstCard = carouselDataFromEdit[0];
|
|
786
|
+
if (firstCard.mediaType === 'image' && firstCard.imageUrl) {
|
|
787
|
+
// Set the first carousel image in Redux state for the uploader to recognize
|
|
788
|
+
setUpdateMpushImageSrc(firstCard.imageUrl, 1, CAROUSEL);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
let iosButtonsValue = [];
|
|
794
|
+
if (Array.isArray(ctas)) {
|
|
795
|
+
iosButtonsValue = ctas;
|
|
796
|
+
}
|
|
797
|
+
const iosContentData = {
|
|
798
|
+
title: iosTitle,
|
|
799
|
+
message: iosMessage,
|
|
800
|
+
mediaType: iosMediaType,
|
|
801
|
+
buttons: iosButtonsValue,
|
|
802
|
+
actionOnClick: false,
|
|
803
|
+
linkType: DEEP_LINK,
|
|
804
|
+
deepLinkValue: "",
|
|
805
|
+
externalLinkValue: "",
|
|
806
|
+
imageSrc: iosImageSrc,
|
|
807
|
+
videoSrc: iosVideoSrc,
|
|
808
|
+
videoPreview: iosVideoPreview,
|
|
809
|
+
carouselData: carouselDataFromEdit, // Initialize carousel data
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
setIosContent(iosContentData);
|
|
813
|
+
// Set image/video sources for upload state with platform isolation
|
|
814
|
+
if (iosImageSrc) {
|
|
815
|
+
setUpdateMpushImageSrc(iosImageSrc, 1, IMAGE);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Apply exact same simple pattern as images for videos/GIFs
|
|
819
|
+
if (iosVideoSrc) {
|
|
820
|
+
setUpdateMpushVideoSrc(1, {
|
|
821
|
+
videoSrc: iosVideoSrc,
|
|
822
|
+
previewUrl: iosVideoPreview,
|
|
823
|
+
}, true); // isInitialization = true
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Check for main notification body CTA (actionOnClick functionality)
|
|
827
|
+
// Removed all iosMainCta references (was not defined or used)
|
|
828
|
+
|
|
829
|
+
// Set CTA data for buttons (from expandableDetails.ctas)
|
|
830
|
+
const iosButtons = iosExpandableDetails?.ctas || [];
|
|
831
|
+
if (iosButtons.length > 0) {
|
|
832
|
+
const ctaDataFromIos = iosButtons.map((button, index) => ({
|
|
833
|
+
text: button.actionText || button.label || "",
|
|
834
|
+
index,
|
|
835
|
+
url: button.actionLink || "",
|
|
836
|
+
urlType: button.type || DEEP_LINK,
|
|
837
|
+
ctaType: index === 0 ? PRIMARY : SECONDARY,
|
|
838
|
+
isSaved: true, // Mark as saved since it's loaded from backend
|
|
839
|
+
}));
|
|
840
|
+
setCtaDataIos(ctaDataFromIos);
|
|
841
|
+
setPrimaryButtonIos(iosButtons.length >= 1);
|
|
842
|
+
setSecondaryButtonIos(iosButtons.length >= 2);
|
|
843
|
+
} else {
|
|
844
|
+
setCtaDataIos([]);
|
|
845
|
+
setPrimaryButtonIos(false);
|
|
846
|
+
setSecondaryButtonIos(false);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Mark initial load as complete after data population and hide spinner
|
|
851
|
+
// setIsInitialLoad(false); // Removed
|
|
852
|
+
// setDataPopulated(true); // Removed
|
|
853
|
+
setSpin(false); // Hide spinner only after all state is populated
|
|
854
|
+
}
|
|
855
|
+
}, [editData?.templateDetails, isEditMode, params?.id]);
|
|
856
|
+
|
|
857
|
+
// Data population useEffect - for library mode (not full mode) using templateData
|
|
858
|
+
useEffect(() => {
|
|
859
|
+
if (isFullMode || !templateData || isEmpty(templateData)) {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Do NOT reset form state here; only populate data
|
|
864
|
+
// resetFormData();
|
|
865
|
+
|
|
866
|
+
// templateData is expected to have a similar structure as editData.templateDetails
|
|
867
|
+
const { name = "", versions = {} } = templateData || {};
|
|
868
|
+
const templateContent = versions?.base || {};
|
|
869
|
+
|
|
870
|
+
if (isEmpty(templateContent)) {
|
|
871
|
+
setSpin(false);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
setTemplateName(name);
|
|
876
|
+
// Process Android content
|
|
877
|
+
const androidContentType = templateContent?.ANDROID;
|
|
878
|
+
if (!isEmpty(androidContentType)) {
|
|
879
|
+
const {
|
|
880
|
+
title: androidTitle = "",
|
|
881
|
+
message: androidMessage = "",
|
|
882
|
+
ctas: androidCta = [],
|
|
883
|
+
expandableDetails: androidExpandableDetails = {},
|
|
884
|
+
image: androidImage = "",
|
|
885
|
+
cta: androidMainCta = null,
|
|
886
|
+
} = androidContentType || {};
|
|
887
|
+
const { style: androidStyle, ctas: androidCtas = [], image: androidExpandableImage = "" } = androidExpandableDetails || {};
|
|
888
|
+
let androidMediaType = NONE;
|
|
889
|
+
let androidImageSrc = "";
|
|
890
|
+
let androidVideoSrc = "";
|
|
891
|
+
let androidVideoPreview = "";
|
|
892
|
+
let carouselDataFromTemplate = [];
|
|
893
|
+
if (androidStyle === BIG_PICTURE) {
|
|
894
|
+
androidMediaType = IMAGE;
|
|
895
|
+
androidImageSrc = androidExpandableImage || androidImage;
|
|
896
|
+
} else if (androidStyle === BIG_TEXT) {
|
|
897
|
+
androidMediaType = NONE;
|
|
898
|
+
} else if (androidStyle === MANUAL_CAROUSEL) {
|
|
899
|
+
androidMediaType = CAROUSEL;
|
|
900
|
+
}
|
|
901
|
+
if (androidExpandableDetails?.media && Array.isArray(androidExpandableDetails.media) && androidExpandableDetails.media.length > 0) {
|
|
902
|
+
const mediaItem = androidExpandableDetails.media[0];
|
|
903
|
+
if (mediaItem.type === VIDEO) {
|
|
904
|
+
if (mediaItem.url && mediaItem.url.toLowerCase().includes('.gif')) {
|
|
905
|
+
androidMediaType = GIF;
|
|
906
|
+
} else {
|
|
907
|
+
androidMediaType = VIDEO;
|
|
908
|
+
}
|
|
909
|
+
androidVideoSrc = mediaItem.url;
|
|
910
|
+
androidVideoPreview = mediaItem.url;
|
|
911
|
+
} else if (mediaItem.type === GIF) {
|
|
912
|
+
androidMediaType = GIF;
|
|
913
|
+
androidVideoSrc = mediaItem.url;
|
|
914
|
+
androidVideoPreview = mediaItem.url;
|
|
915
|
+
}
|
|
916
|
+
} else if (androidImage && (androidImage.includes('.mp4') || androidImage.includes('.mov'))) {
|
|
917
|
+
androidMediaType = VIDEO;
|
|
918
|
+
androidVideoSrc = androidImage;
|
|
919
|
+
androidVideoPreview = androidImage;
|
|
920
|
+
} else if (androidImage && androidImage.toLowerCase().includes('.gif')) {
|
|
921
|
+
androidMediaType = GIF;
|
|
922
|
+
androidVideoSrc = androidImage;
|
|
923
|
+
androidVideoPreview = androidImage;
|
|
924
|
+
} else if ((androidExpandableDetails?.style === MANUAL_CAROUSEL || androidContentType?.type === CAROUSEL) && androidExpandableDetails?.carouselData) {
|
|
925
|
+
androidMediaType = CAROUSEL;
|
|
926
|
+
carouselDataFromTemplate = androidExpandableDetails.carouselData.map((card) => ({
|
|
927
|
+
mediaType: card.mediaType || IMAGE.toLowerCase(),
|
|
928
|
+
imageUrl: card.imageUrl || '',
|
|
929
|
+
videoSrc: card.videoSrc || '',
|
|
930
|
+
buttons: card.buttons || [],
|
|
931
|
+
}));
|
|
932
|
+
|
|
933
|
+
// Initialize carousel images in Redux state for proper display in uploaders
|
|
934
|
+
if (carouselDataFromTemplate.length > 0) {
|
|
935
|
+
const firstCard = carouselDataFromTemplate[0];
|
|
936
|
+
if (firstCard.mediaType === 'image' && firstCard.imageUrl) {
|
|
937
|
+
// Set the first carousel image in Redux state for the uploader to recognize
|
|
938
|
+
setUpdateMpushImageSrc(firstCard.imageUrl, 0, CAROUSEL);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
let androidButtonsValue = [];
|
|
943
|
+
if (Array.isArray(androidCtas)) {
|
|
944
|
+
androidButtonsValue = androidCtas;
|
|
945
|
+
} else if (Array.isArray(androidCta)) {
|
|
946
|
+
androidButtonsValue = androidCta;
|
|
947
|
+
}
|
|
948
|
+
const androidContentData = {
|
|
949
|
+
title: androidTitle,
|
|
950
|
+
message: androidMessage,
|
|
951
|
+
mediaType: androidMediaType,
|
|
952
|
+
buttons: androidButtonsValue,
|
|
953
|
+
actionOnClick: false,
|
|
954
|
+
linkType: DEEP_LINK,
|
|
955
|
+
deepLinkValue: "",
|
|
956
|
+
externalLinkValue: "",
|
|
957
|
+
imageSrc: androidImageSrc,
|
|
958
|
+
videoSrc: androidVideoSrc,
|
|
959
|
+
videoPreview: androidVideoPreview,
|
|
960
|
+
carouselData: carouselDataFromTemplate,
|
|
961
|
+
};
|
|
962
|
+
setAndroidContent(androidContentData);
|
|
963
|
+
if (androidImageSrc) {
|
|
964
|
+
setUpdateMpushImageSrc(androidImageSrc, 0, IMAGE);
|
|
965
|
+
}
|
|
966
|
+
if (androidVideoSrc) {
|
|
967
|
+
setUpdateMpushVideoSrc(0, {
|
|
968
|
+
videoSrc: androidVideoSrc,
|
|
969
|
+
previewUrl: androidVideoPreview,
|
|
970
|
+
}, true);
|
|
971
|
+
}
|
|
972
|
+
if (androidMainCta?.actionLink) {
|
|
973
|
+
androidContentData.actionOnClick = true;
|
|
974
|
+
if (androidMainCta.type === EXTERNAL_URL) {
|
|
975
|
+
androidContentData.linkType = EXTERNAL_LINK;
|
|
976
|
+
androidContentData.externalLinkValue = androidMainCta.actionLink;
|
|
977
|
+
androidContentData.deepLinkValue = "";
|
|
978
|
+
} else if (androidMainCta.type === DEEP_LINK) {
|
|
979
|
+
androidContentData.linkType = DEEP_LINK;
|
|
980
|
+
androidContentData.deepLinkValue = androidMainCta.actionLink;
|
|
981
|
+
androidContentData.externalLinkValue = "";
|
|
982
|
+
} else if (androidMainCta.actionLink.startsWith('http://') || androidMainCta.actionLink.startsWith('https://')) {
|
|
983
|
+
androidContentData.linkType = EXTERNAL_LINK;
|
|
984
|
+
androidContentData.externalLinkValue = androidMainCta.actionLink;
|
|
985
|
+
androidContentData.deepLinkValue = "";
|
|
986
|
+
} else {
|
|
987
|
+
androidContentData.linkType = DEEP_LINK;
|
|
988
|
+
androidContentData.deepLinkValue = androidMainCta.actionLink;
|
|
989
|
+
androidContentData.externalLinkValue = "";
|
|
990
|
+
}
|
|
991
|
+
} else {
|
|
992
|
+
androidContentData.deepLinkValue = "";
|
|
993
|
+
androidContentData.externalLinkValue = "";
|
|
994
|
+
}
|
|
995
|
+
const androidButtons = androidExpandableDetails?.ctas || [];
|
|
996
|
+
if (androidButtons.length > 0) {
|
|
997
|
+
const ctaDataFromAndroid = androidButtons.map((button, index) => ({
|
|
998
|
+
text: button.actionText || button.label || "",
|
|
999
|
+
index,
|
|
1000
|
+
url: button.actionLink || "",
|
|
1001
|
+
urlType: button.type || DEEP_LINK,
|
|
1002
|
+
ctaType: index === 0 ? PRIMARY : SECONDARY,
|
|
1003
|
+
isSaved: true,
|
|
1004
|
+
}));
|
|
1005
|
+
setCtaDataAndroid(ctaDataFromAndroid);
|
|
1006
|
+
setPrimaryButtonAndroid(androidButtons.length >= 1);
|
|
1007
|
+
setSecondaryButtonAndroid(androidButtons.length >= 2);
|
|
1008
|
+
} else {
|
|
1009
|
+
setCtaDataAndroid([]);
|
|
1010
|
+
setPrimaryButtonAndroid(false);
|
|
1011
|
+
setSecondaryButtonAndroid(false);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
// Process iOS content
|
|
1015
|
+
const iosContentType = templateContent?.IOS;
|
|
1016
|
+
if (!isEmpty(iosContentType)) {
|
|
1017
|
+
const {
|
|
1018
|
+
title: iosTitle = "",
|
|
1019
|
+
message: iosMessage = "",
|
|
1020
|
+
ctas = [],
|
|
1021
|
+
expandableDetails: iosExpandableDetails = {},
|
|
1022
|
+
image: iosImage = "",
|
|
1023
|
+
} = iosContentType || {};
|
|
1024
|
+
const { style: iosStyle, image: iosExpandableImage = "" } = iosExpandableDetails || {};
|
|
1025
|
+
let iosMediaType = NONE;
|
|
1026
|
+
let iosImageSrc = "";
|
|
1027
|
+
let iosVideoSrc = "";
|
|
1028
|
+
let iosVideoPreview = "";
|
|
1029
|
+
let carouselDataFromTemplate = [];
|
|
1030
|
+
if (iosStyle === BIG_PICTURE) {
|
|
1031
|
+
iosMediaType = IMAGE;
|
|
1032
|
+
iosImageSrc = iosExpandableImage || iosImage;
|
|
1033
|
+
} else if (iosStyle === BIG_TEXT) {
|
|
1034
|
+
iosMediaType = NONE;
|
|
1035
|
+
} else if (iosStyle === MANUAL_CAROUSEL) {
|
|
1036
|
+
iosMediaType = CAROUSEL;
|
|
1037
|
+
}
|
|
1038
|
+
if (iosExpandableDetails?.media && Array.isArray(iosExpandableDetails.media) && iosExpandableDetails.media.length > 0) {
|
|
1039
|
+
const mediaItem = iosExpandableDetails.media[0];
|
|
1040
|
+
if (mediaItem.type === VIDEO) {
|
|
1041
|
+
if (mediaItem.url && mediaItem.url.toLowerCase().includes('.gif')) {
|
|
1042
|
+
iosMediaType = GIF;
|
|
1043
|
+
} else {
|
|
1044
|
+
iosMediaType = VIDEO;
|
|
1045
|
+
}
|
|
1046
|
+
iosVideoSrc = mediaItem.url;
|
|
1047
|
+
iosVideoPreview = mediaItem.url;
|
|
1048
|
+
} else if (mediaItem.type === GIF) {
|
|
1049
|
+
iosMediaType = GIF;
|
|
1050
|
+
iosVideoSrc = mediaItem.url;
|
|
1051
|
+
iosVideoPreview = mediaItem.url;
|
|
1052
|
+
}
|
|
1053
|
+
} else if (iosImage && (iosImage.includes('.mp4') || iosImage.includes('.mov'))) {
|
|
1054
|
+
iosMediaType = VIDEO;
|
|
1055
|
+
iosVideoSrc = iosImage;
|
|
1056
|
+
iosVideoPreview = iosImage;
|
|
1057
|
+
} else if (iosImage && iosImage.toLowerCase().includes('.gif')) {
|
|
1058
|
+
iosMediaType = GIF;
|
|
1059
|
+
iosVideoSrc = iosImage;
|
|
1060
|
+
iosVideoPreview = iosImage;
|
|
1061
|
+
} else if ((iosExpandableDetails?.style === MANUAL_CAROUSEL || iosContentType?.type === CAROUSEL) && iosExpandableDetails?.carouselData) {
|
|
1062
|
+
iosMediaType = CAROUSEL;
|
|
1063
|
+
carouselDataFromTemplate = iosExpandableDetails.carouselData.map((card) => ({
|
|
1064
|
+
mediaType: card.mediaType || IMAGE.toLowerCase(),
|
|
1065
|
+
imageUrl: card.imageUrl || '',
|
|
1066
|
+
videoSrc: card.videoSrc || '',
|
|
1067
|
+
buttons: card.buttons || [],
|
|
1068
|
+
}));
|
|
1069
|
+
|
|
1070
|
+
// Initialize carousel images in Redux state for proper display in uploaders
|
|
1071
|
+
if (carouselDataFromTemplate.length > 0) {
|
|
1072
|
+
const firstCard = carouselDataFromTemplate[0];
|
|
1073
|
+
if (firstCard.mediaType === 'image' && firstCard.imageUrl) {
|
|
1074
|
+
// Set the first carousel image in Redux state for the uploader to recognize
|
|
1075
|
+
setUpdateMpushImageSrc(firstCard.imageUrl, 1, CAROUSEL);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
let iosButtonsValue = [];
|
|
1080
|
+
if (Array.isArray(ctas)) {
|
|
1081
|
+
iosButtonsValue = ctas;
|
|
1082
|
+
}
|
|
1083
|
+
const iosContentData = {
|
|
1084
|
+
title: iosTitle,
|
|
1085
|
+
message: iosMessage,
|
|
1086
|
+
mediaType: iosMediaType,
|
|
1087
|
+
buttons: iosButtonsValue,
|
|
1088
|
+
actionOnClick: false,
|
|
1089
|
+
linkType: DEEP_LINK,
|
|
1090
|
+
deepLinkValue: "",
|
|
1091
|
+
externalLinkValue: "",
|
|
1092
|
+
imageSrc: iosImageSrc,
|
|
1093
|
+
videoSrc: iosVideoSrc,
|
|
1094
|
+
videoPreview: iosVideoPreview,
|
|
1095
|
+
carouselData: carouselDataFromTemplate,
|
|
1096
|
+
};
|
|
1097
|
+
setIosContent(iosContentData);
|
|
1098
|
+
if (iosImageSrc) {
|
|
1099
|
+
setUpdateMpushImageSrc(iosImageSrc, 1, IMAGE);
|
|
1100
|
+
}
|
|
1101
|
+
if (iosVideoSrc) {
|
|
1102
|
+
setUpdateMpushVideoSrc(1, {
|
|
1103
|
+
videoSrc: iosVideoSrc,
|
|
1104
|
+
previewUrl: iosVideoPreview,
|
|
1105
|
+
}, true);
|
|
1106
|
+
}
|
|
1107
|
+
const iosButtons = iosExpandableDetails?.ctas || [];
|
|
1108
|
+
if (iosButtons.length > 0) {
|
|
1109
|
+
const ctaDataFromIos = iosButtons.map((button, index) => ({
|
|
1110
|
+
text: button.actionText || button.label || "",
|
|
1111
|
+
index,
|
|
1112
|
+
url: button.actionLink || "",
|
|
1113
|
+
urlType: button.type || DEEP_LINK,
|
|
1114
|
+
ctaType: index === 0 ? PRIMARY : SECONDARY,
|
|
1115
|
+
isSaved: true,
|
|
1116
|
+
}));
|
|
1117
|
+
setCtaDataIos(ctaDataFromIos);
|
|
1118
|
+
setPrimaryButtonIos(iosButtons.length >= 1);
|
|
1119
|
+
setSecondaryButtonIos(iosButtons.length >= 2);
|
|
1120
|
+
} else {
|
|
1121
|
+
setCtaDataIos([]);
|
|
1122
|
+
setPrimaryButtonIos(false);
|
|
1123
|
+
setSecondaryButtonIos(false);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
setSpin(false);
|
|
1127
|
+
}, [isFullMode, templateData]);
|
|
1128
|
+
|
|
1129
|
+
// Determine platform support from accountData
|
|
1130
|
+
const isAndroidSupported = accountData?.configs?.android === '1';
|
|
1131
|
+
const isIosSupported = accountData?.configs?.ios === '1';
|
|
1132
|
+
|
|
1133
|
+
// Validation logic for template creation/update
|
|
1134
|
+
const isAndroidFieldsMissing = isAndroidSupported && (!androidContent?.title?.trim() || !androidContent?.message?.trim());
|
|
1135
|
+
const isIosFieldsMissing = isIosSupported && (!iosContent?.title?.trim() || !iosContent?.message?.trim());
|
|
1136
|
+
|
|
1137
|
+
// Add changeSourceRef for debounced sync
|
|
1138
|
+
const changeSourceRef = useRef(null);
|
|
1139
|
+
|
|
1140
|
+
const {
|
|
1141
|
+
handleTitleChange,
|
|
1142
|
+
handleMessageChange,
|
|
1143
|
+
handleTagSelect,
|
|
1144
|
+
handleMediaTypeChange,
|
|
1145
|
+
handleActionOnClickChange,
|
|
1146
|
+
handleLinkTypeChange,
|
|
1147
|
+
} = usePlatformSync({
|
|
1148
|
+
setAndroidContent,
|
|
1149
|
+
setIosContent,
|
|
1150
|
+
validateTitle,
|
|
1151
|
+
validateMessage,
|
|
1152
|
+
setAndroidTitleError,
|
|
1153
|
+
setAndroidMessageError,
|
|
1154
|
+
setIosTitleError,
|
|
1155
|
+
setIosMessageError,
|
|
1156
|
+
androidContent,
|
|
1157
|
+
iosContent,
|
|
1158
|
+
updateOnMpushImageReUpload,
|
|
1159
|
+
updateOnMpushVideoReUpload,
|
|
1160
|
+
sameContent,
|
|
1161
|
+
changeSourceRef,
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
// Platform-specific deeplink and external link handlers
|
|
1165
|
+
const handleDeepLinkChange = useCallback((platform, deepLinkValue) => {
|
|
1166
|
+
// Validate deep link
|
|
1167
|
+
const deepLinkError = validateDeepLink(deepLinkValue, formatMessage, messages);
|
|
1168
|
+
|
|
1169
|
+
if (platform === ANDROID) {
|
|
1170
|
+
setAndroidContent((prev) => {
|
|
1171
|
+
const updated = {
|
|
1172
|
+
...prev,
|
|
1173
|
+
deepLinkValue,
|
|
1174
|
+
// Clear external link value when deep link is selected
|
|
1175
|
+
externalLinkValue: "",
|
|
1176
|
+
};
|
|
1177
|
+
return updated;
|
|
1178
|
+
});
|
|
1179
|
+
setAndroidDeepLinkError(deepLinkError || "");
|
|
1180
|
+
} else {
|
|
1181
|
+
setIosContent((prev) => {
|
|
1182
|
+
const updated = {
|
|
1183
|
+
...prev,
|
|
1184
|
+
deepLinkValue,
|
|
1185
|
+
// Clear external link value when deep link is selected
|
|
1186
|
+
externalLinkValue: "",
|
|
1187
|
+
};
|
|
1188
|
+
return updated;
|
|
1189
|
+
});
|
|
1190
|
+
setIosDeepLinkError(deepLinkError || "");
|
|
1191
|
+
}
|
|
1192
|
+
}, [validateDeepLink]);
|
|
1193
|
+
|
|
1194
|
+
const handleExternalLinkChange = useCallback((platform, externalLinkValue) => {
|
|
1195
|
+
// Validate URL
|
|
1196
|
+
const urlError = validateExternalLink(externalLinkValue, formatMessage, messages);
|
|
1197
|
+
|
|
1198
|
+
if (platform === ANDROID) {
|
|
1199
|
+
setAndroidContent((prev) => {
|
|
1200
|
+
const updated = {
|
|
1201
|
+
...prev,
|
|
1202
|
+
externalLinkValue,
|
|
1203
|
+
// Clear deep link value when external link is entered
|
|
1204
|
+
deepLinkValue: "",
|
|
1205
|
+
};
|
|
1206
|
+
return updated;
|
|
1207
|
+
});
|
|
1208
|
+
setAndroidExternalLinkError(urlError || "");
|
|
1209
|
+
} else {
|
|
1210
|
+
setIosContent((prev) => {
|
|
1211
|
+
const updated = {
|
|
1212
|
+
...prev,
|
|
1213
|
+
externalLinkValue,
|
|
1214
|
+
// Clear deep link value when external link is entered
|
|
1215
|
+
deepLinkValue: "",
|
|
1216
|
+
};
|
|
1217
|
+
return updated;
|
|
1218
|
+
});
|
|
1219
|
+
setIosExternalLinkError(urlError || "");
|
|
1220
|
+
}
|
|
1221
|
+
}, [validateExternalLink]);
|
|
1222
|
+
|
|
1223
|
+
// Carousel data handler for platform-specific carousel management
|
|
1224
|
+
const updateCarouselLinkError = useCallback((cardIndex, field, error) => {
|
|
1225
|
+
setCarouselLinkErrors((prev) => ({
|
|
1226
|
+
...prev,
|
|
1227
|
+
[`${cardIndex}-${field}`]: error,
|
|
1228
|
+
}));
|
|
1229
|
+
}, []);
|
|
1230
|
+
|
|
1231
|
+
const handleCarouselDataChange = useCallback((platform, carouselData) => {
|
|
1232
|
+
if (platform === ANDROID) {
|
|
1233
|
+
setAndroidContent((prev) => {
|
|
1234
|
+
const updated = { ...prev, carouselData };
|
|
1235
|
+
return updated;
|
|
1236
|
+
});
|
|
1237
|
+
} else {
|
|
1238
|
+
setIosContent((prev) => {
|
|
1239
|
+
const updated = { ...prev, carouselData };
|
|
1240
|
+
return updated;
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}, []);
|
|
1244
|
+
|
|
1245
|
+
// Update updateHandler to sync button state for both platforms if sameContent is true
|
|
1246
|
+
const updateHandler = useCallback(
|
|
1247
|
+
(ctaDataParam, index) => {
|
|
1248
|
+
if (sameContent) {
|
|
1249
|
+
setCtaDataAndroid((prevState) => {
|
|
1250
|
+
const clonedCta = cloneDeep(prevState);
|
|
1251
|
+
clonedCta[index] = ctaDataParam;
|
|
1252
|
+
return clonedCta;
|
|
1253
|
+
});
|
|
1254
|
+
setCtaDataIos((prevState) => {
|
|
1255
|
+
const clonedCta = cloneDeep(prevState);
|
|
1256
|
+
clonedCta[index] = ctaDataParam;
|
|
1257
|
+
return clonedCta;
|
|
1258
|
+
});
|
|
1259
|
+
// Update button state for both platforms
|
|
1260
|
+
if (index === 0) {
|
|
1261
|
+
setPrimaryButtonAndroid(true);
|
|
1262
|
+
setPrimaryButtonIos(true);
|
|
1263
|
+
} else if (index === 1) {
|
|
1264
|
+
setSecondaryButtonAndroid(true);
|
|
1265
|
+
setSecondaryButtonIos(true);
|
|
1266
|
+
}
|
|
1267
|
+
} else if (activeTab === ANDROID) {
|
|
1268
|
+
setCtaDataAndroid((prevState) => {
|
|
1269
|
+
const clonedCta = cloneDeep(prevState);
|
|
1270
|
+
clonedCta[index] = ctaDataParam;
|
|
1271
|
+
return clonedCta;
|
|
1272
|
+
});
|
|
1273
|
+
if (index === 0) setPrimaryButtonAndroid(true);
|
|
1274
|
+
if (index === 1) setSecondaryButtonAndroid(true);
|
|
1275
|
+
} else {
|
|
1276
|
+
setCtaDataIos((prevState) => {
|
|
1277
|
+
const clonedCta = cloneDeep(prevState);
|
|
1278
|
+
clonedCta[index] = ctaDataParam;
|
|
1279
|
+
return clonedCta;
|
|
1280
|
+
});
|
|
1281
|
+
if (index === 0) setPrimaryButtonIos(true);
|
|
1282
|
+
if (index === 1) setSecondaryButtonIos(true);
|
|
1283
|
+
}
|
|
1284
|
+
},
|
|
1285
|
+
[activeTab, sameContent]
|
|
1286
|
+
);
|
|
1287
|
+
|
|
1288
|
+
// Update deleteHandler to sync button state for both platforms if sameContent is true
|
|
1289
|
+
const deleteHandler = useCallback((index) => {
|
|
1290
|
+
if (sameContent) {
|
|
1291
|
+
setCtaDataAndroid((prevState) => {
|
|
1292
|
+
const clonedCta = cloneDeep(prevState);
|
|
1293
|
+
const filteredCta = clonedCta.filter((cta) => cta.index !== index);
|
|
1294
|
+
return filteredCta;
|
|
1295
|
+
});
|
|
1296
|
+
setCtaDataIos((prevState) => {
|
|
1297
|
+
const clonedCta = cloneDeep(prevState);
|
|
1298
|
+
const filteredCta = clonedCta.filter((cta) => cta.index !== index);
|
|
1299
|
+
return filteredCta;
|
|
1300
|
+
});
|
|
1301
|
+
// Update button state for both platforms
|
|
1302
|
+
if (index === 0) {
|
|
1303
|
+
setPrimaryButtonAndroid(false);
|
|
1304
|
+
setPrimaryButtonIos(false);
|
|
1305
|
+
} else if (index === 1) {
|
|
1306
|
+
setSecondaryButtonAndroid(false);
|
|
1307
|
+
setSecondaryButtonIos(false);
|
|
1308
|
+
}
|
|
1309
|
+
} else if (activeTab === ANDROID) {
|
|
1310
|
+
setCtaDataAndroid((prevState) => {
|
|
1311
|
+
const clonedCta = cloneDeep(prevState);
|
|
1312
|
+
const filteredCta = clonedCta.filter((cta) => cta.index !== index);
|
|
1313
|
+
return filteredCta;
|
|
1314
|
+
});
|
|
1315
|
+
if (index === 0) setPrimaryButtonAndroid(false);
|
|
1316
|
+
if (index === 1) setSecondaryButtonAndroid(false);
|
|
1317
|
+
} else {
|
|
1318
|
+
setCtaDataIos((prevState) => {
|
|
1319
|
+
const clonedCta = cloneDeep(prevState);
|
|
1320
|
+
const filteredCta = clonedCta.filter((cta) => cta.index !== index);
|
|
1321
|
+
return filteredCta;
|
|
1322
|
+
});
|
|
1323
|
+
if (index === 0) setPrimaryButtonIos(false);
|
|
1324
|
+
if (index === 1) setSecondaryButtonIos(false);
|
|
1325
|
+
}
|
|
1326
|
+
}, [activeTab, sameContent]);
|
|
1327
|
+
|
|
1328
|
+
const onTagSelect = useCallback(
|
|
1329
|
+
(data, id) => {
|
|
1330
|
+
const platform = activeTab === ANDROID ? ANDROID : IOS;
|
|
1331
|
+
handleTagSelect(platform, data, id === 0);
|
|
1332
|
+
},
|
|
1333
|
+
[activeTab, handleTagSelect]
|
|
1334
|
+
);
|
|
1335
|
+
|
|
1336
|
+
const renderContentFields = useCallback(
|
|
1337
|
+
(platform) => {
|
|
1338
|
+
const isAndroid = platform === ANDROID;
|
|
1339
|
+
const currentContent = isAndroid ? androidContent : iosContent;
|
|
1340
|
+
// Only show error if the platform is enabled
|
|
1341
|
+
let titleError = "";
|
|
1342
|
+
let messageError = "";
|
|
1343
|
+
let externalLinkError = "";
|
|
1344
|
+
let deepLinkError = "";
|
|
1345
|
+
if (isAndroid) {
|
|
1346
|
+
titleError = isAndroidSupported ? androidTitleError : "";
|
|
1347
|
+
messageError = isAndroidSupported ? androidMessageError : "";
|
|
1348
|
+
externalLinkError = isAndroidSupported ? androidExternalLinkError : "";
|
|
1349
|
+
deepLinkError = isAndroidSupported ? androidDeepLinkError : "";
|
|
1350
|
+
} else {
|
|
1351
|
+
titleError = isIosSupported ? iosTitleError : "";
|
|
1352
|
+
messageError = isIosSupported ? iosMessageError : "";
|
|
1353
|
+
externalLinkError = isIosSupported ? iosExternalLinkError : "";
|
|
1354
|
+
deepLinkError = isIosSupported ? iosDeepLinkError : "";
|
|
1355
|
+
}
|
|
1356
|
+
const primaryButton = isAndroid ? primaryButtonAndroid : primaryButtonIos;
|
|
1357
|
+
const secondaryButton = isAndroid ? secondaryButtonAndroid : secondaryButtonIos;
|
|
1358
|
+
const setPrimaryButton = isAndroid ? setPrimaryButtonAndroid : setPrimaryButtonIos;
|
|
1359
|
+
const setSecondaryButton = isAndroid ? setSecondaryButtonAndroid : setSecondaryButtonIos;
|
|
1360
|
+
|
|
1361
|
+
const handlers = {
|
|
1362
|
+
handleTitleChange,
|
|
1363
|
+
handleMessageChange,
|
|
1364
|
+
handleMediaTypeChange,
|
|
1365
|
+
handleActionOnClickChange,
|
|
1366
|
+
handleLinkTypeChange,
|
|
1367
|
+
handleDeepLinkChange: (value) => handleDeepLinkChange(platform, value),
|
|
1368
|
+
handleExternalLinkChange: (value) => handleExternalLinkChange(platform, value),
|
|
1369
|
+
onTagSelect,
|
|
1370
|
+
handleOnTagsContextChange,
|
|
1371
|
+
};
|
|
1372
|
+
|
|
1373
|
+
const tagListProps = {
|
|
1374
|
+
moduleFilterEnabled: location?.query?.type !== EMBEDDED,
|
|
1375
|
+
label: formatMessage(messages.addLabels),
|
|
1376
|
+
location,
|
|
1377
|
+
tags: tags || [],
|
|
1378
|
+
injectedTags: injectedTags || {},
|
|
1379
|
+
selectedOfferDetails,
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
// Fix nested ternary for videoAssetList and gifAssetList
|
|
1383
|
+
let videoAssetList = {};
|
|
1384
|
+
let gifAssetList = {};
|
|
1385
|
+
if (currentContent?.mediaType === VIDEO) {
|
|
1386
|
+
videoAssetList = platform === ANDROID ? androidAssetList : iosAssetList;
|
|
1387
|
+
}
|
|
1388
|
+
if (currentContent?.mediaType === GIF) {
|
|
1389
|
+
gifAssetList = platform === ANDROID ? androidAssetList : iosAssetList;
|
|
1390
|
+
}
|
|
1391
|
+
const mediaUploaderProps = {
|
|
1392
|
+
mediaType: currentContent?.mediaType,
|
|
1393
|
+
activeTab: platform,
|
|
1394
|
+
imageSrc,
|
|
1395
|
+
uploadMpushAsset,
|
|
1396
|
+
isFullMode,
|
|
1397
|
+
setUpdateMpushImageSrc,
|
|
1398
|
+
updateOnMpushImageReUpload,
|
|
1399
|
+
imageData,
|
|
1400
|
+
videoAssetList,
|
|
1401
|
+
gifAssetList,
|
|
1402
|
+
setUpdateMpushVideoSrc,
|
|
1403
|
+
// Add video content state for fallback when Redux is empty (same pattern as imageSrc)
|
|
1404
|
+
videoSrc: {
|
|
1405
|
+
androidVideoSrc: videoState?.androidVideoSrc || '',
|
|
1406
|
+
iosVideoSrc: videoState?.iosVideoSrc || '',
|
|
1407
|
+
androidVideoPreview: videoState?.androidVideoPreview || '',
|
|
1408
|
+
iosVideoPreview: videoState?.iosVideoPreview || '',
|
|
1409
|
+
},
|
|
1410
|
+
// Create truly platform-specific video data with no cross-platform information
|
|
1411
|
+
videoDataForVideo: currentContent?.mediaType === VIDEO ? {
|
|
1412
|
+
// Keep BOTH platforms available like images do - let MediaUploaders choose which to show
|
|
1413
|
+
uploadedAssetData0: uploadedAssetData0 || {},
|
|
1414
|
+
uploadedAssetData1: uploadedAssetData1 || {},
|
|
1415
|
+
} : {},
|
|
1416
|
+
videoDataForGif: currentContent?.mediaType === GIF ? {
|
|
1417
|
+
// Keep BOTH platforms available like images do - let MediaUploaders choose which to show
|
|
1418
|
+
uploadedAssetData0: uploadedAssetData0 || {},
|
|
1419
|
+
uploadedAssetData1: uploadedAssetData1 || {},
|
|
1420
|
+
} : {},
|
|
1421
|
+
videoData,
|
|
1422
|
+
clearImageDataByMediaType,
|
|
1423
|
+
carouselData: currentContent?.carouselData || [],
|
|
1424
|
+
onCarouselDataChange: handleCarouselDataChange,
|
|
1425
|
+
mobilePushActions,
|
|
1426
|
+
carouselActiveTabIndex,
|
|
1427
|
+
setCarouselActiveTabIndex,
|
|
1428
|
+
carouselLinkErrors,
|
|
1429
|
+
updateCarouselLinkError,
|
|
1430
|
+
linkProps: { deepLink },
|
|
1431
|
+
videoPreviewKey: videoState?.videoPreviewKey,
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
const ctaButtonProps = {
|
|
1435
|
+
primaryButton,
|
|
1436
|
+
secondaryButton,
|
|
1437
|
+
setPrimaryButton,
|
|
1438
|
+
setSecondaryButton,
|
|
1439
|
+
ctaData,
|
|
1440
|
+
updateHandler,
|
|
1441
|
+
deleteHandler,
|
|
1442
|
+
deepLink,
|
|
1443
|
+
};
|
|
1444
|
+
|
|
1445
|
+
const linkProps = {
|
|
1446
|
+
deepLink,
|
|
1447
|
+
deepLinkValue: currentContent?.deepLinkValue || "",
|
|
1448
|
+
externalLinkValue: currentContent?.externalLinkValue || "",
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
return (
|
|
1452
|
+
<PlatformContentFields
|
|
1453
|
+
key={`platform-fields-${platform}`}
|
|
1454
|
+
deviceType={platform}
|
|
1455
|
+
content={currentContent}
|
|
1456
|
+
errors={{
|
|
1457
|
+
title: titleError,
|
|
1458
|
+
message: messageError,
|
|
1459
|
+
externalLink: externalLinkError,
|
|
1460
|
+
deepLink: deepLinkError,
|
|
1461
|
+
}}
|
|
1462
|
+
handlers={handlers}
|
|
1463
|
+
tagListProps={tagListProps}
|
|
1464
|
+
mediaUploaderProps={mediaUploaderProps}
|
|
1465
|
+
ctaButtonProps={ctaButtonProps}
|
|
1466
|
+
linkProps={linkProps}
|
|
1467
|
+
sameContent={sameContent}
|
|
1468
|
+
formatMessage={formatMessage}
|
|
1469
|
+
/>
|
|
1470
|
+
);
|
|
1471
|
+
}, [androidContent, iosContent, androidTitleError, iosTitleError, androidMessageError, iosMessageError, androidExternalLinkError, iosExternalLinkError, androidDeepLinkError, iosDeepLinkError, formatMessage, activeTab, imageSrc, isFullMode, imageData, androidAssetList, iosAssetList, videoState, videoData, location, tags, injectedTags, selectedOfferDetails, primaryButtonAndroid, secondaryButtonAndroid, primaryButtonIos, secondaryButtonIos, ctaData, deepLink, mobilePushActions, carouselActiveTabIndex, carouselLinkErrors, handleTitleChange, handleMessageChange, handleMediaTypeChange, handleActionOnClickChange, handleLinkTypeChange, handleDeepLinkChange, handleExternalLinkChange, onTagSelect, handleOnTagsContextChange, setUpdateMpushImageSrc, updateOnMpushImageReUpload, setUpdateMpushVideoSrc, updateOnMpushVideoReUpload, clearImageDataByMediaType, handleCarouselDataChange, updateCarouselLinkError, sameContent, updateHandler, deleteHandler]
|
|
1472
|
+
);
|
|
1473
|
+
|
|
1474
|
+
// PANES: Only render enabled platforms
|
|
1475
|
+
const PANES = useMemo(() => {
|
|
1476
|
+
const panes = [];
|
|
1477
|
+
if (isAndroidSupported) {
|
|
1478
|
+
panes.push({
|
|
1479
|
+
key: ANDROID,
|
|
1480
|
+
tab: (
|
|
1481
|
+
<CapRow>
|
|
1482
|
+
<CapIcon type="android" />
|
|
1483
|
+
{formatMessage(messages.android)}
|
|
1484
|
+
</CapRow>
|
|
1485
|
+
),
|
|
1486
|
+
content: renderContentFields(ANDROID),
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
if (isIosSupported) {
|
|
1490
|
+
panes.push({
|
|
1491
|
+
key: IOS,
|
|
1492
|
+
tab: (
|
|
1493
|
+
<CapRow>
|
|
1494
|
+
<CapIcon type="ios" />
|
|
1495
|
+
{formatMessage(messages.ios)}
|
|
1496
|
+
</CapRow>
|
|
1497
|
+
),
|
|
1498
|
+
content: renderContentFields(IOS),
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
return panes;
|
|
1502
|
+
}, [isAndroidSupported, isIosSupported, renderContentFields, formatMessage]);
|
|
1503
|
+
|
|
1504
|
+
// Save button disabled logic: only check enabled platforms
|
|
1505
|
+
const isSaveDisabled = (
|
|
1506
|
+
(isAndroidSupported && (!androidContent?.title?.trim() || !androidContent?.message?.trim()))
|
|
1507
|
+
|| (isIosSupported && (!iosContent?.title?.trim() || !iosContent?.message?.trim()))
|
|
1508
|
+
|| templateNameError
|
|
1509
|
+
|| Object.values(carouselLinkErrors).some((error) => error !== null && error !== "")
|
|
1510
|
+
|| !isCarouselDataValid()
|
|
1511
|
+
);
|
|
1512
|
+
|
|
1513
|
+
// Validation in handleSave: only show errors for enabled platforms
|
|
1514
|
+
const handleSave = useCallback(() => {
|
|
1515
|
+
if (isAndroidSupported && (!androidContent?.title?.trim() || !androidContent?.message?.trim())) {
|
|
1516
|
+
CapNotification.error({
|
|
1517
|
+
message: formatMessage(messages.androidValidationError),
|
|
1518
|
+
});
|
|
1519
|
+
if (onValidationFail) onValidationFail();
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
if (isIosSupported && (!iosContent?.title?.trim() || !iosContent?.message?.trim())) {
|
|
1523
|
+
CapNotification.error({
|
|
1524
|
+
message: formatMessage(messages.iosValidationError),
|
|
1525
|
+
});
|
|
1526
|
+
if (onValidationFail) onValidationFail();
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
if (templateNameError) {
|
|
1530
|
+
CapNotification.error({
|
|
1531
|
+
message: formatMessage(messages.emptyTemplateErrorMessage),
|
|
1532
|
+
});
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
// Only require templateName in full mode
|
|
1536
|
+
if (isFullMode && !templateName?.trim()) {
|
|
1537
|
+
CapNotification.error({
|
|
1538
|
+
message: formatMessage(messages.emptyTemplateErrorMessage),
|
|
1539
|
+
});
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// In library mode, set templateName programmatically if empty
|
|
1544
|
+
let finalTemplateName = templateName;
|
|
1545
|
+
if (!isFullMode && (!finalTemplateName || !finalTemplateName.trim())) {
|
|
1546
|
+
if (androidContent?.title && androidContent.title.trim()) {
|
|
1547
|
+
finalTemplateName = androidContent.title.trim();
|
|
1548
|
+
} else if (iosContent?.title && iosContent.title.trim()) {
|
|
1549
|
+
finalTemplateName = iosContent.title.trim();
|
|
1550
|
+
} else {
|
|
1551
|
+
finalTemplateName = '';
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// Validate external links
|
|
1556
|
+
const androidUrlError = validateExternalLink(androidContent.externalLinkValue, formatMessage, messages);
|
|
1557
|
+
const iosUrlError = validateExternalLink(iosContent.externalLinkValue, formatMessage, messages);
|
|
1558
|
+
|
|
1559
|
+
setAndroidExternalLinkError(androidUrlError || "");
|
|
1560
|
+
setIosExternalLinkError(iosUrlError || "");
|
|
1561
|
+
|
|
1562
|
+
// Validate deep links
|
|
1563
|
+
const androidDeepLinkUrlError = validateDeepLink(androidContent.deepLinkValue, formatMessage, messages);
|
|
1564
|
+
const iosDeepLinkUrlError = validateDeepLink(iosContent.deepLinkValue, formatMessage, messages);
|
|
1565
|
+
|
|
1566
|
+
setAndroidDeepLinkError(androidDeepLinkUrlError || "");
|
|
1567
|
+
setIosDeepLinkError(iosDeepLinkUrlError || "");
|
|
1568
|
+
|
|
1569
|
+
// Check for carousel link errors
|
|
1570
|
+
const hasCarouselErrors = Object.values(carouselLinkErrors).some((error) => error !== null && error !== "");
|
|
1571
|
+
|
|
1572
|
+
if (androidUrlError || iosUrlError || androidDeepLinkUrlError || iosDeepLinkUrlError || hasCarouselErrors) {
|
|
1573
|
+
CapNotification.error({
|
|
1574
|
+
message: formatMessage(messages.invalidUrl),
|
|
1575
|
+
});
|
|
1576
|
+
if (onValidationFail) {
|
|
1577
|
+
onValidationFail();
|
|
1578
|
+
}
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
try {
|
|
1583
|
+
// Convert ctaData to backend format and add to content
|
|
1584
|
+
const processedAndroidContent = { ...androidContent };
|
|
1585
|
+
const processedIosContent = { ...iosContent };
|
|
1586
|
+
|
|
1587
|
+
// Process button CTA data if it exists
|
|
1588
|
+
if (ctaDataAndroid && ctaDataAndroid.length > 0) {
|
|
1589
|
+
const savedCtas = ctaDataAndroid.filter((cta) => cta.isSaved);
|
|
1590
|
+
if (savedCtas.length > 0) {
|
|
1591
|
+
processedAndroidContent.expandableDetails = {
|
|
1592
|
+
...processedAndroidContent.expandableDetails,
|
|
1593
|
+
ctas: savedCtas.map((cta) => ({
|
|
1594
|
+
actionText: cta.text,
|
|
1595
|
+
type: cta.urlType || DEEP_LINK,
|
|
1596
|
+
actionLink: cta.url,
|
|
1597
|
+
})),
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
if (ctaDataIos && ctaDataIos.length > 0) {
|
|
1602
|
+
const savedCtas = ctaDataIos.filter((cta) => cta.isSaved);
|
|
1603
|
+
if (savedCtas.length > 0) {
|
|
1604
|
+
processedIosContent.expandableDetails = {
|
|
1605
|
+
...processedIosContent.expandableDetails,
|
|
1606
|
+
ctas: savedCtas.map((cta) => ({
|
|
1607
|
+
actionText: cta.text,
|
|
1608
|
+
type: cta.urlType || DEEP_LINK,
|
|
1609
|
+
actionLink: cta.url,
|
|
1610
|
+
})),
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// Fix: Only include enabled platform content in payload
|
|
1616
|
+
const payload = createPayload({
|
|
1617
|
+
templateName: finalTemplateName,
|
|
1618
|
+
androidContent: isAndroidSupported ? processedAndroidContent : undefined,
|
|
1619
|
+
iosContent: isIosSupported ? processedIosContent : undefined,
|
|
1620
|
+
imageSrc,
|
|
1621
|
+
mpushVideoSrcAndPreview,
|
|
1622
|
+
accountData,
|
|
1623
|
+
sameContent,
|
|
1624
|
+
options: {
|
|
1625
|
+
mode: params?.mode,
|
|
1626
|
+
},
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
// Add template ID for edit mode
|
|
1630
|
+
if (isEditMode && templateId) {
|
|
1631
|
+
payload._id = templateId;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
const content = getContent(payload);
|
|
1635
|
+
const { definition: { mode: definitionMode } = {} } = payload || {};
|
|
1636
|
+
const label = definitionMode === IMAGE.toLowerCase() ? IMAGE.toLowerCase() : TEXT.toLowerCase();
|
|
1637
|
+
|
|
1638
|
+
const handleSuccess = (response) => {
|
|
1639
|
+
const timeTaken = GA.timeTracker.stopTimer(TRACK_CREATE_MPUSH, {
|
|
1640
|
+
category: "Creatives",
|
|
1641
|
+
action: TRACK_CREATE_MPUSH,
|
|
1642
|
+
label,
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
gtmPush("creativeDetails", {
|
|
1646
|
+
id: response?.templateId || params?.id,
|
|
1647
|
+
name: payload.name,
|
|
1648
|
+
channel: MOBILE_PUSH_CHANNEL,
|
|
1649
|
+
timeTaken,
|
|
1650
|
+
content,
|
|
1651
|
+
mode: isEditMode ? EDIT : CREATE,
|
|
1652
|
+
imageAdded: definitionMode === IMAGE.toLowerCase(),
|
|
1653
|
+
});
|
|
1654
|
+
|
|
1655
|
+
// --- BEGIN: Library mode communication fix ---
|
|
1656
|
+
if (!isFullMode) {
|
|
1657
|
+
// In library mode, only communicate to parent/callback, do NOT call create/edit API
|
|
1658
|
+
const formDataForLibrary = {
|
|
1659
|
+
action: 'getFormData',
|
|
1660
|
+
value: payload, // payload is the backend-ready template data with versions.base structure
|
|
1661
|
+
validity: true,
|
|
1662
|
+
type: 'MOBILEPUSH',
|
|
1663
|
+
};
|
|
1664
|
+
if (window && window.parent) {
|
|
1665
|
+
window.parent.postMessage(JSON.stringify(formDataForLibrary), '*');
|
|
1666
|
+
}
|
|
1667
|
+
if (typeof getFormLibraryData === 'function') {
|
|
1668
|
+
try {
|
|
1669
|
+
getFormLibraryData(formDataForLibrary);
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
console.error('[MobilePushNew] Error calling getFormLibraryData:', error);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// Close slidebox after successful save in library mode
|
|
1676
|
+
if (typeof handleClose === 'function') {
|
|
1677
|
+
handleClose();
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// Do not proceed with any API or further side effects in library mode
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
// --- END: Library mode communication fix ---
|
|
1684
|
+
|
|
1685
|
+
// Show success toast and refresh list after create (not edit)
|
|
1686
|
+
if (!isEditMode && response && (response.templateId || response._id)) {
|
|
1687
|
+
const message = `${intl.formatMessage(messages.templateCreateSuccess)}`;
|
|
1688
|
+
CapNotification.success({ key: 'createSuccess', message });
|
|
1689
|
+
// Call handleClose to close the slidebox/modal
|
|
1690
|
+
if (typeof handleClose === 'function') {
|
|
1691
|
+
handleClose();
|
|
1692
|
+
}
|
|
1693
|
+
// Call onCreateComplete to notify parent to refresh list
|
|
1694
|
+
if (typeof onCreateComplete === 'function') {
|
|
1695
|
+
onCreateComplete(true);
|
|
1696
|
+
}
|
|
1697
|
+
// Optionally, trigger a parent refresh (if needed)
|
|
1698
|
+
if (window && window.parent) {
|
|
1699
|
+
window.parent.postMessage(JSON.stringify({ type: 'REFRESH_MOBILEPUSH_TEMPLATES' }), '*');
|
|
1700
|
+
}
|
|
1701
|
+
setTimeout(() => {
|
|
1702
|
+
mobilePushActions.clearCreateResponse();
|
|
1703
|
+
}, 100);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
if (isEditMode) {
|
|
1707
|
+
// Delay handleClose to ensure Templates container processes the response first
|
|
1708
|
+
setTimeout(() => {
|
|
1709
|
+
try {
|
|
1710
|
+
handleClose();
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
console.error("[MobilePushNew] Error calling handleClose:", error);
|
|
1713
|
+
}
|
|
1714
|
+
}, 150);
|
|
1715
|
+
// Delay clearing the response to allow Templates container to process it
|
|
1716
|
+
setTimeout(() => {
|
|
1717
|
+
mobilePushActions.clearEditResponse();
|
|
1718
|
+
}, 100);
|
|
1719
|
+
} else {
|
|
1720
|
+
resetFormData();
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
if (getFormLibraryData && isFullMode) {
|
|
1724
|
+
getFormLibraryData({ validity: true });
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
// --- Only call create/edit API in full mode ---
|
|
1729
|
+
if (isFullMode) {
|
|
1730
|
+
if (isEditMode) {
|
|
1731
|
+
mobilePushActions.editTemplate(payload, (response) => {
|
|
1732
|
+
// Guard: If response is an Error object, show error notification and return
|
|
1733
|
+
if (response instanceof Error) {
|
|
1734
|
+
CapNotification.error({ key: 'createError', message: intl.formatMessage(messages.somethingWentWrong) });
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
handleSuccess({ ...response, isEdit: true });
|
|
1738
|
+
});
|
|
1739
|
+
} else {
|
|
1740
|
+
mobilePushActions.createTemplate(payload, (response) => {
|
|
1741
|
+
// Guard: If response is an Error object, show error notification and return
|
|
1742
|
+
if (response instanceof Error) {
|
|
1743
|
+
const errorMsg = response.message || response.toString();
|
|
1744
|
+
// Check for duplicate name error
|
|
1745
|
+
if (errorMsg && errorMsg.toLowerCase().includes('template name already exist')) {
|
|
1746
|
+
CapNotification.error({ key: 'duplicateName', message: errorMsg });
|
|
1747
|
+
// Do NOT close the slidebox or call onCreateComplete
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
CapNotification.error({ key: 'createError', message: intl.formatMessage(messages.somethingWentWrong) });
|
|
1751
|
+
if (typeof onCreateComplete === 'function') {
|
|
1752
|
+
onCreateComplete(false);
|
|
1753
|
+
}
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
handleSuccess({ ...response, isEdit: false });
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
} else {
|
|
1760
|
+
// In library mode, do NOT call create/edit API, just communicate to parent/callback (already handled above)
|
|
1761
|
+
// Call handleSuccess directly to trigger the postMessage logic
|
|
1762
|
+
handleSuccess({ isLibraryMode: true });
|
|
1763
|
+
}
|
|
1764
|
+
} catch (error) {
|
|
1765
|
+
const errorMsg = getMessageObject(
|
|
1766
|
+
"error",
|
|
1767
|
+
formatMessage(messages.somethingWentWrong),
|
|
1768
|
+
true
|
|
1769
|
+
);
|
|
1770
|
+
globalActionsProps.addMessageToQueue(errorMsg);
|
|
1771
|
+
if (onValidationFail) {
|
|
1772
|
+
onValidationFail();
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}, [
|
|
1776
|
+
templateNameError,
|
|
1777
|
+
formatMessage,
|
|
1778
|
+
templateName,
|
|
1779
|
+
androidContent,
|
|
1780
|
+
iosContent,
|
|
1781
|
+
imageSrc,
|
|
1782
|
+
mpushVideoSrcAndPreview,
|
|
1783
|
+
accountData,
|
|
1784
|
+
params?.mode,
|
|
1785
|
+
params?.id,
|
|
1786
|
+
isEditMode,
|
|
1787
|
+
mobilePushActions,
|
|
1788
|
+
handleClose,
|
|
1789
|
+
getFormLibraryData,
|
|
1790
|
+
globalActionsProps,
|
|
1791
|
+
onValidationFail,
|
|
1792
|
+
resetFormData,
|
|
1793
|
+
validateExternalLink,
|
|
1794
|
+
ctaDataAndroid,
|
|
1795
|
+
ctaDataIos,
|
|
1796
|
+
intl,
|
|
1797
|
+
onCreateComplete,
|
|
1798
|
+
createTimeoutRef,
|
|
1799
|
+
isAndroidSupported,
|
|
1800
|
+
isIosSupported,
|
|
1801
|
+
isAndroidFieldsMissing,
|
|
1802
|
+
isIosFieldsMissing,
|
|
1803
|
+
isCarouselDataValid,
|
|
1804
|
+
isFullMode,
|
|
1805
|
+
templateId,
|
|
1806
|
+
]);
|
|
1807
|
+
|
|
1808
|
+
// Helper to sync content between platforms
|
|
1809
|
+
const syncPlatformContent = useCallback((sourceContent, sourcePlatform) => {
|
|
1810
|
+
const targetPlatform = sourcePlatform === ANDROID ? IOS : ANDROID;
|
|
1811
|
+
const setTargetContent = sourcePlatform === ANDROID ? setIosContent : setAndroidContent;
|
|
1812
|
+
const setTargetTitleError = sourcePlatform === ANDROID ? setIosTitleError : setAndroidTitleError;
|
|
1813
|
+
const setTargetMessageError = sourcePlatform === ANDROID ? setIosMessageError : setAndroidMessageError;
|
|
1814
|
+
// Sync all content fields
|
|
1815
|
+
setTargetContent({
|
|
1816
|
+
...sourceContent,
|
|
1817
|
+
// Preserve platform-specific fields if any
|
|
1818
|
+
buttons: Array.isArray(sourceContent.buttons)
|
|
1819
|
+
? sourceContent.buttons.map((btn) => ({
|
|
1820
|
+
...btn,
|
|
1821
|
+
platform: targetPlatform,
|
|
1822
|
+
}))
|
|
1823
|
+
: [],
|
|
1824
|
+
});
|
|
1825
|
+
|
|
1826
|
+
// Validate synced content
|
|
1827
|
+
setTargetTitleError(validateTitle(sourceContent.title));
|
|
1828
|
+
setTargetMessageError(validateMessage(sourceContent.message));
|
|
1829
|
+
}, []);
|
|
1830
|
+
|
|
1831
|
+
const onTemplateNameChange = useCallback(
|
|
1832
|
+
(ev) => {
|
|
1833
|
+
const { value } = ev.target;
|
|
1834
|
+
setTemplateName(value);
|
|
1835
|
+
const isInvalid = !value || value.trim() === "";
|
|
1836
|
+
setTemplateNameError(isInvalid);
|
|
1837
|
+
if (value && onEnterTemplateName) {
|
|
1838
|
+
onEnterTemplateName();
|
|
1839
|
+
} else if (onRemoveTemplateName) {
|
|
1840
|
+
onRemoveTemplateName();
|
|
1841
|
+
}
|
|
1842
|
+
},
|
|
1843
|
+
[onEnterTemplateName, onRemoveTemplateName]
|
|
1844
|
+
);
|
|
1845
|
+
|
|
1846
|
+
// --- Only show template name input in full mode (not library/consumer mode) ---
|
|
1847
|
+
const createModeContent = useCallback(
|
|
1848
|
+
() => (
|
|
1849
|
+
isFullMode ? (
|
|
1850
|
+
<CapRow className="input-group creative-name-container">
|
|
1851
|
+
<CapInput
|
|
1852
|
+
id="mobile-push-template-name-input"
|
|
1853
|
+
className="mobile-push-template-name-input"
|
|
1854
|
+
key={`template-name-${params?.id || 'create'}`}
|
|
1855
|
+
label={formatMessage(messages.creativeName)}
|
|
1856
|
+
placeholder={formatMessage(messages.creativeNamePlaceholder)}
|
|
1857
|
+
value={templateName}
|
|
1858
|
+
onChange={onTemplateNameChange}
|
|
1859
|
+
status={templateNameError ? "error" : ""}
|
|
1860
|
+
help={
|
|
1861
|
+
templateNameError
|
|
1862
|
+
? formatMessage(messages.emptyTemplateErrorMessage)
|
|
1863
|
+
: ""
|
|
1864
|
+
}
|
|
1865
|
+
/>
|
|
1866
|
+
</CapRow>
|
|
1867
|
+
) : null
|
|
1868
|
+
),
|
|
1869
|
+
[isFullMode, formatMessage, templateName, onTemplateNameChange, templateNameError, params?.id]
|
|
1870
|
+
);
|
|
1871
|
+
|
|
1872
|
+
const liquidMiddleWare = useCallback(() => {
|
|
1873
|
+
const onError = ({ standardErrors, liquidErrors }) => {
|
|
1874
|
+
setErrorMessage((prev) => ({
|
|
1875
|
+
STANDARD_ERROR_MSG: { ...prev.STANDARD_ERROR_MSG, ...standardErrors },
|
|
1876
|
+
LIQUID_ERROR_MSG: { ...prev.LIQUID_ERROR_MSG, ...liquidErrors },
|
|
1877
|
+
}));
|
|
1878
|
+
};
|
|
1879
|
+
const onSuccess = () => handleSave();
|
|
1880
|
+
|
|
1881
|
+
validateMobilePushContent([androidContent, iosContent], {
|
|
1882
|
+
currentTab: activeTab === ANDROID ? 1 : 2,
|
|
1883
|
+
onError,
|
|
1884
|
+
onSuccess,
|
|
1885
|
+
getLiquidTags: globalActionsProps.getLiquidTags,
|
|
1886
|
+
formatMessage,
|
|
1887
|
+
messages: formBuilderMessages,
|
|
1888
|
+
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
1889
|
+
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1890
|
+
isLiquidFlow: hasLiquidSupportFeature(),
|
|
1891
|
+
forwardedTags: {},
|
|
1892
|
+
skipTags: (tag) => {
|
|
1893
|
+
const skipRegexes = [
|
|
1894
|
+
/dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
|
|
1895
|
+
/unsubscribe\(#[a-zA-Z\d]{6}\)/,
|
|
1896
|
+
/Link_to_[a-zA-z]/,
|
|
1897
|
+
/SURVEY.*\.TOKEN/,
|
|
1898
|
+
/^[A-Za-z].*\([a-zA-Z\d]*\)/,
|
|
1899
|
+
];
|
|
1900
|
+
return skipRegexes.some((regex) => regex.test(tag));
|
|
1901
|
+
},
|
|
1902
|
+
singleTab: getSingleTab(accountData),
|
|
1903
|
+
});
|
|
1904
|
+
}, [
|
|
1905
|
+
androidContent,
|
|
1906
|
+
iosContent,
|
|
1907
|
+
activeTab,
|
|
1908
|
+
globalActionsProps,
|
|
1909
|
+
formatMessage,
|
|
1910
|
+
metaEntities,
|
|
1911
|
+
accountData,
|
|
1912
|
+
]);
|
|
1913
|
+
|
|
1914
|
+
const isLiquidFlow = hasLiquidSupportFeature();
|
|
1915
|
+
|
|
1916
|
+
useEffect(() => {
|
|
1917
|
+
// Always map to { label } for both platforms
|
|
1918
|
+
const newButtons = Array.isArray(ctaData)
|
|
1919
|
+
? ctaData.map((data) => ({
|
|
1920
|
+
label: data?.text || '',
|
|
1921
|
+
}))
|
|
1922
|
+
: [];
|
|
1923
|
+
|
|
1924
|
+
if (activeTab === ANDROID) {
|
|
1925
|
+
setAndroidContent((prevContent) => ({
|
|
1926
|
+
...prevContent,
|
|
1927
|
+
buttons: newButtons,
|
|
1928
|
+
}));
|
|
1929
|
+
} else if (activeTab === IOS) {
|
|
1930
|
+
setIosContent((prevContent) => ({
|
|
1931
|
+
...prevContent,
|
|
1932
|
+
buttons: newButtons,
|
|
1933
|
+
}));
|
|
1934
|
+
}
|
|
1935
|
+
}, [ctaData, activeTab]);
|
|
1936
|
+
|
|
1937
|
+
// Sync button data and state for both platforms when sameContent is enabled (e.g., in edit mode)
|
|
1938
|
+
useEffect(() => {
|
|
1939
|
+
if (sameContent) {
|
|
1940
|
+
// Copy button data and state from the active tab to the other platform
|
|
1941
|
+
if (activeTab === ANDROID) {
|
|
1942
|
+
setCtaDataIos(ctaDataAndroid);
|
|
1943
|
+
setPrimaryButtonIos(primaryButtonAndroid);
|
|
1944
|
+
setSecondaryButtonIos(secondaryButtonAndroid);
|
|
1945
|
+
} else {
|
|
1946
|
+
setCtaDataAndroid(ctaDataIos);
|
|
1947
|
+
setPrimaryButtonAndroid(primaryButtonIos);
|
|
1948
|
+
setSecondaryButtonAndroid(secondaryButtonIos);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
// Only run when sameContent or activeTab changes
|
|
1952
|
+
}, [sameContent]);
|
|
1953
|
+
|
|
1954
|
+
const handleSameContentChange = useCallback((e) => {
|
|
1955
|
+
const isCurrentPlatformAndroid = activeTab === ANDROID;
|
|
1956
|
+
syncPlatformContent(isCurrentPlatformAndroid ? androidContent : iosContent, isCurrentPlatformAndroid ? ANDROID : IOS);
|
|
1957
|
+
setSameContent(e.target.checked);
|
|
1958
|
+
}, [activeTab, androidContent, iosContent]);
|
|
1959
|
+
|
|
1960
|
+
const getContentType = useCallback(
|
|
1961
|
+
(content) => {
|
|
1962
|
+
// Get image source from multiple possible locations
|
|
1963
|
+
const getImageSource = () => {
|
|
1964
|
+
// First check content-specific image - handle both string and object formats
|
|
1965
|
+
if (content?.imageSrc) {
|
|
1966
|
+
// If imageSrc is a string, return it directly
|
|
1967
|
+
if (typeof content.imageSrc === 'string') {
|
|
1968
|
+
return content.imageSrc;
|
|
1969
|
+
}
|
|
1970
|
+
// If imageSrc is an object, extract the URL based on active tab
|
|
1971
|
+
if (typeof content.imageSrc === 'object') {
|
|
1972
|
+
if (activeTab === ANDROID && content.imageSrc.androidImageSrc) {
|
|
1973
|
+
return content.imageSrc.androidImageSrc;
|
|
1974
|
+
}
|
|
1975
|
+
if (activeTab === IOS && content.imageSrc.iosImageSrc) {
|
|
1976
|
+
return content.imageSrc.iosImageSrc;
|
|
1977
|
+
}
|
|
1978
|
+
return '';
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// Then check upload state based on active tab
|
|
1983
|
+
if (activeTab === ANDROID && imageSrc?.androidImageSrc) {
|
|
1984
|
+
return imageSrc.androidImageSrc;
|
|
1985
|
+
}
|
|
1986
|
+
if (activeTab === IOS && imageSrc?.iosImageSrc) {
|
|
1987
|
+
return imageSrc.iosImageSrc;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
return '';
|
|
1991
|
+
};
|
|
1992
|
+
|
|
1993
|
+
// Get video source from multiple possible locations
|
|
1994
|
+
const getVideoSource = () => {
|
|
1995
|
+
// First check current platform's content
|
|
1996
|
+
if (content?.videoSrc) {
|
|
1997
|
+
return content.videoSrc;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
// Then check current platform's upload state only - DEFENSIVE: ensure perfect symmetry
|
|
2001
|
+
const uploadVideoSrc = activeTab === ANDROID
|
|
2002
|
+
? videoState?.androidVideoSrc || ''
|
|
2003
|
+
: videoState?.iosVideoSrc || '';
|
|
2004
|
+
return uploadVideoSrc;
|
|
2005
|
+
};
|
|
2006
|
+
|
|
2007
|
+
const getVideoPreview = () => {
|
|
2008
|
+
// First check current platform's content
|
|
2009
|
+
if (content?.videoPreview) {
|
|
2010
|
+
return content.videoPreview;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// Then check current platform's upload state only - DEFENSIVE: ensure perfect symmetry
|
|
2014
|
+
const uploadVideoPreview = activeTab === ANDROID
|
|
2015
|
+
? videoState?.androidVideoPreview || ''
|
|
2016
|
+
: videoState?.iosVideoPreview || '';
|
|
2017
|
+
return uploadVideoPreview;
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
const videoSrc = getVideoSource();
|
|
2021
|
+
const videoPreview = getVideoPreview();
|
|
2022
|
+
|
|
2023
|
+
const bodyGifValue = content?.mediaType === GIF ? (videoSrc || getImageSource()) : "";
|
|
2024
|
+
|
|
2025
|
+
const previewData = {
|
|
2026
|
+
appName: editData?.selectedWeChatAccount?.name || accountData?.selectedWeChatAccount?.name || "",
|
|
2027
|
+
bodyText: content?.message || "",
|
|
2028
|
+
bodyImage: content?.mediaType === IMAGE ? getImageSource() : "",
|
|
2029
|
+
actions: Array.isArray(content?.buttons) ? content.buttons : [],
|
|
2030
|
+
carouselData: content?.carouselData || [],
|
|
2031
|
+
header: content?.title || "",
|
|
2032
|
+
bodyVideo: (content?.mediaType === VIDEO || content?.mediaType === GIF) ? {
|
|
2033
|
+
videoSrc,
|
|
2034
|
+
videoPreview,
|
|
2035
|
+
} : {},
|
|
2036
|
+
bodyGif: bodyGifValue,
|
|
2037
|
+
};
|
|
2038
|
+
return previewData;
|
|
2039
|
+
},
|
|
2040
|
+
[editData, accountData, imageSrc, mpushVideoSrcAndPreview, activeTab, videoState]
|
|
2041
|
+
);
|
|
2042
|
+
|
|
2043
|
+
const previewContent = useMemo(() => {
|
|
2044
|
+
const currentContent = activeTab === ANDROID ? androidContent : iosContent;
|
|
2045
|
+
const preview = getContentType(currentContent);
|
|
2046
|
+
return preview;
|
|
2047
|
+
}, [activeTab, androidContent, iosContent, getContentType]);
|
|
2048
|
+
|
|
2049
|
+
// Add useEffect to handle isGetFormData prop changes
|
|
2050
|
+
useEffect(() => {
|
|
2051
|
+
if (isGetFormData) {
|
|
2052
|
+
handleSave();
|
|
2053
|
+
// Reset the flag to prevent infinite loop
|
|
2054
|
+
if (onValidationFail) {
|
|
2055
|
+
onValidationFail();
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
}, [isGetFormData, handleSave, onValidationFail]);
|
|
2059
|
+
|
|
2060
|
+
// Add message event listener to handle parent communication (like old MobilePush components)
|
|
2061
|
+
useEffect(() => {
|
|
2062
|
+
const handleFrameTasks = (e) => {
|
|
2063
|
+
const { data: type } = e;
|
|
2064
|
+
|
|
2065
|
+
if (typeof type === 'object') {
|
|
2066
|
+
const { action } = type;
|
|
2067
|
+
switch (action) {
|
|
2068
|
+
case "getFormData":
|
|
2069
|
+
handleSave();
|
|
2070
|
+
break;
|
|
2071
|
+
case "startTemplateCreation":
|
|
2072
|
+
// Handle template creation start if needed
|
|
2073
|
+
break;
|
|
2074
|
+
default:
|
|
2075
|
+
break;
|
|
2076
|
+
}
|
|
2077
|
+
} else {
|
|
2078
|
+
switch (type) {
|
|
2079
|
+
case "getFormData":
|
|
2080
|
+
handleSave();
|
|
2081
|
+
break;
|
|
2082
|
+
default:
|
|
2083
|
+
break;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
|
|
2088
|
+
window.addEventListener("message", handleFrameTasks);
|
|
2089
|
+
|
|
2090
|
+
return () => {
|
|
2091
|
+
window.removeEventListener("message", handleFrameTasks);
|
|
2092
|
+
};
|
|
2093
|
+
}, [handleSave]);
|
|
2094
|
+
|
|
2095
|
+
return (
|
|
2096
|
+
<CapSpin
|
|
2097
|
+
spinning={spin || editData?.editTemplateInProgress || fetchingLiquidValidation || getTemplateDetailsInProgress}
|
|
2098
|
+
tip={
|
|
2099
|
+
fetchingLiquidValidation && formatMessage(messages.validationLoadingMessage)
|
|
2100
|
+
}
|
|
2101
|
+
>
|
|
2102
|
+
<CapRow className="mobile-push-container">
|
|
2103
|
+
<CapColumn className="content-section" span={14}>
|
|
2104
|
+
{createModeContent()}
|
|
2105
|
+
{isAndroidSupported && isIosSupported && (
|
|
2106
|
+
<CapCheckbox
|
|
2107
|
+
className="same-content-checkbox"
|
|
2108
|
+
checked={sameContent}
|
|
2109
|
+
onChange={handleSameContentChange}
|
|
2110
|
+
key={`same-content-${sameContent}`}
|
|
2111
|
+
>
|
|
2112
|
+
{formatMessage(messages.sameContentForBothChannels)}
|
|
2113
|
+
</CapCheckbox>
|
|
2114
|
+
)}
|
|
2115
|
+
<CapRow className="platform-header">
|
|
2116
|
+
<CapTab
|
|
2117
|
+
className="platform-tabs"
|
|
2118
|
+
activeKey={activeTab}
|
|
2119
|
+
onChange={setActiveTab}
|
|
2120
|
+
panes={PANES}
|
|
2121
|
+
/>
|
|
2122
|
+
</CapRow>
|
|
2123
|
+
<CapRow>
|
|
2124
|
+
<CapColumn span={24}>
|
|
2125
|
+
<ErrorInfoNote
|
|
2126
|
+
currentTab={activeTab?.toUpperCase()}
|
|
2127
|
+
errorMessages={{
|
|
2128
|
+
LIQUID_ERROR_MSG: errorMessage?.LIQUID_ERROR_MSG,
|
|
2129
|
+
STANDARD_ERROR_MSG: errorMessage?.STANDARD_ERROR_MSG,
|
|
2130
|
+
}}
|
|
2131
|
+
/>
|
|
2132
|
+
|
|
2133
|
+
{/* In library mode, only show save button in create mode (edit mode has Done button from FormBuilder). In full mode, keep old logic. */}
|
|
2134
|
+
{((!isFullMode && computedCreativesMode !== 'edit') || (isFullMode && !isEditMode && computedCreativesMode === 'create')) && (
|
|
2135
|
+
<CapRow className="save-section">
|
|
2136
|
+
<CapColumn span={24}>
|
|
2137
|
+
<CapButton
|
|
2138
|
+
type="primary"
|
|
2139
|
+
onClick={() => {
|
|
2140
|
+
if (isLiquidFlow) {
|
|
2141
|
+
liquidMiddleWare();
|
|
2142
|
+
} else {
|
|
2143
|
+
handleSave();
|
|
2144
|
+
}
|
|
2145
|
+
}}
|
|
2146
|
+
className="save-button"
|
|
2147
|
+
disabled={isSaveDisabled}
|
|
2148
|
+
>
|
|
2149
|
+
{formatMessage(messages.saveTemplate)}
|
|
2150
|
+
</CapButton>
|
|
2151
|
+
</CapColumn>
|
|
2152
|
+
</CapRow>
|
|
2153
|
+
)}
|
|
2154
|
+
|
|
2155
|
+
</CapColumn>
|
|
2156
|
+
</CapRow>
|
|
2157
|
+
</CapColumn>
|
|
2158
|
+
<CapColumn className="preview-section" span={10}>
|
|
2159
|
+
<TemplatePreview
|
|
2160
|
+
device={activeTab === ANDROID ? "android" : "iphone"}
|
|
2161
|
+
content={previewContent}
|
|
2162
|
+
channel={MOBILE_PUSH_CHANNEL}
|
|
2163
|
+
templateData={templateData}
|
|
2164
|
+
/>
|
|
2165
|
+
</CapColumn>
|
|
2166
|
+
</CapRow>
|
|
2167
|
+
</CapSpin>
|
|
2168
|
+
);
|
|
2169
|
+
};
|
|
2170
|
+
|
|
2171
|
+
MobilePushNew.propTypes = {
|
|
2172
|
+
isFullMode: PropTypes.bool,
|
|
2173
|
+
onEnterTemplateName: PropTypes.func,
|
|
2174
|
+
onRemoveTemplateName: PropTypes.func,
|
|
2175
|
+
intl: intlShape.isRequired,
|
|
2176
|
+
location: PropTypes.object,
|
|
2177
|
+
selectedOfferDetails: PropTypes.object,
|
|
2178
|
+
getDefaultTags: PropTypes.func,
|
|
2179
|
+
injectedTags: PropTypes.object,
|
|
2180
|
+
params: PropTypes.object,
|
|
2181
|
+
templateData: PropTypes.object,
|
|
2182
|
+
accountData: PropTypes.object,
|
|
2183
|
+
editData: PropTypes.object,
|
|
2184
|
+
mobilePushActions: PropTypes.object,
|
|
2185
|
+
onValidationFail: PropTypes.func,
|
|
2186
|
+
getFormLibraryData: PropTypes.func,
|
|
2187
|
+
uploadedAssetData: PropTypes.object,
|
|
2188
|
+
uploadedAssetData0: PropTypes.object,
|
|
2189
|
+
uploadedAssetData1: PropTypes.object,
|
|
2190
|
+
uploadAssetSuccess: PropTypes.bool,
|
|
2191
|
+
metaEntities: PropTypes.object,
|
|
2192
|
+
supportedTags: PropTypes.array,
|
|
2193
|
+
globalActions: PropTypes.object,
|
|
2194
|
+
fetchingLiquidValidation: PropTypes.bool,
|
|
2195
|
+
handleClose: PropTypes.func,
|
|
2196
|
+
createTemplateError: PropTypes.any,
|
|
2197
|
+
isGetFormData: PropTypes.bool,
|
|
2198
|
+
getTemplateDetailsInProgress: PropTypes.bool,
|
|
2199
|
+
onCreateComplete: PropTypes.func,
|
|
2200
|
+
};
|
|
2201
|
+
|
|
2202
|
+
MobilePushNew.defaultProps = {
|
|
2203
|
+
isFullMode: false,
|
|
2204
|
+
onEnterTemplateName: () => {},
|
|
2205
|
+
onRemoveTemplateName: () => {},
|
|
2206
|
+
location: {},
|
|
2207
|
+
selectedOfferDetails: {},
|
|
2208
|
+
getDefaultTags: () => {},
|
|
2209
|
+
injectedTags: {},
|
|
2210
|
+
params: {},
|
|
2211
|
+
templateData: {},
|
|
2212
|
+
accountData: {},
|
|
2213
|
+
editData: {},
|
|
2214
|
+
mobilePushActions: {},
|
|
2215
|
+
onValidationFail: () => {},
|
|
2216
|
+
getFormLibraryData: () => {},
|
|
2217
|
+
uploadedAssetData: {},
|
|
2218
|
+
uploadedAssetData0: {},
|
|
2219
|
+
uploadedAssetData1: {},
|
|
2220
|
+
uploadAssetSuccess: false,
|
|
2221
|
+
metaEntities: {},
|
|
2222
|
+
supportedTags: [],
|
|
2223
|
+
globalActions: {},
|
|
2224
|
+
fetchingLiquidValidation: false,
|
|
2225
|
+
handleClose: () => {},
|
|
2226
|
+
createTemplateError: null,
|
|
2227
|
+
isGetFormData: false,
|
|
2228
|
+
getTemplateDetailsInProgress: false,
|
|
2229
|
+
onCreateComplete: () => {},
|
|
2230
|
+
};
|
|
2231
|
+
|
|
2232
|
+
const mapStateToProps = createStructuredSelector({
|
|
2233
|
+
editData: makeSelectMobilePushNew(),
|
|
2234
|
+
injectedTags: setInjectedTags(),
|
|
2235
|
+
currentOrgDetails: selectCurrentOrgDetails(),
|
|
2236
|
+
metaEntities: makeSelectMetaEntities(),
|
|
2237
|
+
loadingTags: isLoadingMetaEntities(),
|
|
2238
|
+
uploadedAssetData: makeSelectUploadedAssetData(),
|
|
2239
|
+
uploadedAssetData0: makeSelectUploadedAssetData0(),
|
|
2240
|
+
uploadedAssetData1: makeSelectUploadedAssetData1(),
|
|
2241
|
+
uploadAssetSuccess: makeSelectUploadAssetSuccess(),
|
|
2242
|
+
assetUploading: makeSelectAssetUploading(),
|
|
2243
|
+
fetchingLiquidValidation: selectLiquidStateDetails(),
|
|
2244
|
+
createTemplateError: makeSelectCreateError(),
|
|
2245
|
+
supportedTags: () => [],
|
|
2246
|
+
accountData: createSelector(
|
|
2247
|
+
(state) => state.get('templates'),
|
|
2248
|
+
(templatesState) => {
|
|
2249
|
+
if (!templatesState) return {};
|
|
2250
|
+
const templates = templatesState.toJS();
|
|
2251
|
+
return templates?.selectedWeChatAccount || {};
|
|
2252
|
+
}
|
|
2253
|
+
),
|
|
2254
|
+
getTemplateDetailsInProgress: makeSelectGetTemplateDetailsInProgress(),
|
|
2255
|
+
});
|
|
2256
|
+
|
|
2257
|
+
const mapDispatchToProps = (dispatch) => ({
|
|
2258
|
+
mobilePushActions: bindActionCreators(actions, dispatch),
|
|
2259
|
+
globalActions: bindActionCreators(globalActions, dispatch),
|
|
2260
|
+
});
|
|
2261
|
+
|
|
2262
|
+
const withSaga = injectSaga({
|
|
2263
|
+
key: "mobilePushNew",
|
|
2264
|
+
saga: v2MobilePushSagas,
|
|
2265
|
+
mode: DAEMON,
|
|
2266
|
+
});
|
|
2267
|
+
|
|
2268
|
+
const withReducer = injectReducer({
|
|
2269
|
+
key: "mobilePushNew",
|
|
2270
|
+
reducer: mobilePushReducer,
|
|
2271
|
+
});
|
|
2272
|
+
|
|
2273
|
+
export default withCreatives({
|
|
2274
|
+
WrappedComponent: MobilePushNew,
|
|
2275
|
+
mapStateToProps,
|
|
2276
|
+
mapDispatchToProps,
|
|
2277
|
+
userAuth: true,
|
|
2278
|
+
sagas: [withSaga],
|
|
2279
|
+
reducers: [withReducer],
|
|
2280
|
+
});
|