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