@capillarytech/creatives-library 8.0.301 → 8.0.302
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/constants/unified.js +1 -0
- package/package.json +1 -1
- package/utils/common.js +7 -1
- package/v2Components/CapDeviceContent/index.js +10 -7
- package/v2Containers/BeePopupEditor/index.js +9 -2
- package/v2Containers/CreativesContainer/SlideBoxContent.js +35 -3
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +69 -1
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +121 -4
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +110 -155
- package/v2Containers/InApp/index.js +183 -14
- package/v2Containers/InApp/tests/mockData.js +1 -1
package/constants/unified.js
CHANGED
|
@@ -45,6 +45,7 @@ export const GIFT_CARDS = 'GIFT_CARDS';
|
|
|
45
45
|
export const PROMO_ENGINE = 'PROMO_ENGINE';
|
|
46
46
|
export const LIQUID_SUPPORT = 'ENABLE_LIQUID_SUPPORT';
|
|
47
47
|
export const ENABLE_NEW_MPUSH = 'ENABLE_NEW_MPUSH';
|
|
48
|
+
export const ENABLE_NEW_EDITOR_FLOW_INAPP = 'ENABLE_NEW_EDITOR_FLOW_INAPP';
|
|
48
49
|
export const SUPPORT_CK_EDITOR = 'SUPPORT_CK_EDITOR';
|
|
49
50
|
export const CUSTOM_TAG = 'CustomTagMessage';
|
|
50
51
|
export const CUSTOMER_EXTENDED_FIELD = 'Customer extended fields';
|
package/package.json
CHANGED
package/utils/common.js
CHANGED
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
ENABLE_WEBPUSH,
|
|
25
25
|
LIQUID_SUPPORT,
|
|
26
26
|
SUPPORT_CK_EDITOR,
|
|
27
|
-
ENABLE_NEW_MPUSH
|
|
27
|
+
ENABLE_NEW_MPUSH,
|
|
28
|
+
ENABLE_NEW_EDITOR_FLOW_INAPP
|
|
28
29
|
} from '../constants/unified';
|
|
29
30
|
import { apiMessageFormatHandler } from './commonUtils';
|
|
30
31
|
|
|
@@ -142,6 +143,11 @@ export const hasNewMobilePushFeatureEnabled = Auth.hasFeatureAccess.bind(
|
|
|
142
143
|
ENABLE_NEW_MPUSH,
|
|
143
144
|
);
|
|
144
145
|
|
|
146
|
+
export const hasNewEditorFlowInAppEnabled = Auth.hasFeatureAccess.bind(
|
|
147
|
+
null,
|
|
148
|
+
ENABLE_NEW_EDITOR_FLOW_INAPP,
|
|
149
|
+
);
|
|
150
|
+
|
|
145
151
|
//filtering tags based on scope
|
|
146
152
|
export const filterTags = (tagsToFilter, tagsList) => tagsList?.filter(
|
|
147
153
|
(tag) => !tagsToFilter?.includes(tag?.definition?.value)
|
|
@@ -63,6 +63,7 @@ const CapDeviceContent = (props) => {
|
|
|
63
63
|
deepLinkValue,
|
|
64
64
|
setDeepLinkValue,
|
|
65
65
|
onCopyTitleAndContent,
|
|
66
|
+
isOtherDeviceSupported,
|
|
66
67
|
tags,
|
|
67
68
|
onTagSelect,
|
|
68
69
|
handleOnTagsContextChange,
|
|
@@ -167,13 +168,15 @@ const CapDeviceContent = (props) => {
|
|
|
167
168
|
return (
|
|
168
169
|
<>
|
|
169
170
|
<CapRow className="creatives-device-content">
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
{isOtherDeviceSupported && (
|
|
172
|
+
<CapLink
|
|
173
|
+
title={isAndroid
|
|
174
|
+
? formatMessage(messages.copyContentFromIOS)
|
|
175
|
+
: formatMessage(messages.copyCotentFromAndroid)}
|
|
176
|
+
className="inapp-copy-content"
|
|
177
|
+
onClick={onCopyTitleAndContent}
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
177
180
|
<CapRow className="creatives-inapp-title">
|
|
178
181
|
<CapColumn
|
|
179
182
|
className="inapp-content-main"
|
|
@@ -40,6 +40,11 @@ function BeePopupEditor(props) {
|
|
|
40
40
|
const savedCallback = useRef();
|
|
41
41
|
const beeInstanceRef = useRef(null);
|
|
42
42
|
const isInitializedRef = useRef(false);
|
|
43
|
+
const beeJsonRef = useRef(beeJson);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
beeJsonRef.current = beeJson;
|
|
47
|
+
}, [beeJson]);
|
|
43
48
|
|
|
44
49
|
const [visibleTaglist, setVisibleTaglist] = useState(false);
|
|
45
50
|
const [selectedTag, setSelectedTag] = useState({});
|
|
@@ -111,8 +116,10 @@ function BeePopupEditor(props) {
|
|
|
111
116
|
window.BeePlugin.create(tokenData, beeConfig, (instance) => {
|
|
112
117
|
beePluginInstance = instance;
|
|
113
118
|
beeInstanceRef.current = instance;
|
|
114
|
-
//
|
|
115
|
-
|
|
119
|
+
// Use ref to get the latest beeJson — the closure captures the value at effect time,
|
|
120
|
+
// but beeJsonRef.current is always up-to-date (e.g. when template data loads async)
|
|
121
|
+
const latestBeeJson = beeJsonRef.current;
|
|
122
|
+
const parseJson = typeof latestBeeJson === 'string' ? JSON.parse(latestBeeJson) : latestBeeJson;
|
|
116
123
|
beePluginInstance.start(parseJson);
|
|
117
124
|
saveBeeInstance(beePluginInstance, device);
|
|
118
125
|
isInitializedRef.current = true;
|
|
@@ -1078,8 +1078,38 @@ export function SlideBoxContent(props) {
|
|
|
1078
1078
|
)}
|
|
1079
1079
|
|
|
1080
1080
|
{isCreateInApp && (
|
|
1081
|
-
|
|
1082
|
-
|
|
1081
|
+
(isFullMode && !commonUtil.hasNewEditorFlowInAppEnabled()) ||
|
|
1082
|
+
(!isFullMode && isLoyaltyModule) ||
|
|
1083
|
+
(!isFullMode && !isLoyaltyModule && !commonUtil.hasNewEditorFlowInAppEnabled()) ? (
|
|
1084
|
+
<InApp
|
|
1085
|
+
key="creatives-inapp-create"
|
|
1086
|
+
location={{ pathname: '/inapp/create', query, search: '' }}
|
|
1087
|
+
setIsLoadingContent={setIsLoadingContent}
|
|
1088
|
+
isGetFormData={isGetFormData}
|
|
1089
|
+
getFormData={getFormData}
|
|
1090
|
+
getDefaultTags={type}
|
|
1091
|
+
isFullMode={isFullMode}
|
|
1092
|
+
templateData={templateData}
|
|
1093
|
+
cap={cap}
|
|
1094
|
+
showTemplateName={showTemplateName}
|
|
1095
|
+
showLiquidErrorInFooter={showLiquidErrorInFooter}
|
|
1096
|
+
onValidationFail={onValidationFail}
|
|
1097
|
+
forwardedTags={forwardedTags}
|
|
1098
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1099
|
+
onPreviewContentClicked={onPreviewContentClicked}
|
|
1100
|
+
onTestContentClicked={onTestContentClicked}
|
|
1101
|
+
eventContextTags={eventContextTags}
|
|
1102
|
+
onCreateComplete={onCreateComplete}
|
|
1103
|
+
handleClose={handleClose}
|
|
1104
|
+
moduleType={moduleType}
|
|
1105
|
+
showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
|
|
1106
|
+
handleTestAndPreview={handleTestAndPreview}
|
|
1107
|
+
handleCloseTestAndPreview={handleCloseTestAndPreview}
|
|
1108
|
+
isTestAndPreviewMode={isTestAndPreviewMode}
|
|
1109
|
+
/>
|
|
1110
|
+
) : (
|
|
1111
|
+
<InAppWrapper
|
|
1112
|
+
key="creatives-inapp-wrapper"
|
|
1083
1113
|
date={new Date().getMilliseconds()}
|
|
1084
1114
|
setIsLoadingContent={setIsLoadingContent}
|
|
1085
1115
|
onInAppEditorTypeChange={onInAppEditorTypeChange}
|
|
@@ -1114,10 +1144,12 @@ export function SlideBoxContent(props) {
|
|
|
1114
1144
|
handleCloseTestAndPreview={handleCloseTestAndPreview}
|
|
1115
1145
|
isTestAndPreviewMode={isTestAndPreviewMode}
|
|
1116
1146
|
/>
|
|
1147
|
+
)
|
|
1117
1148
|
)}
|
|
1118
|
-
|
|
1149
|
+
|
|
1119
1150
|
{isEditInApp && (<InApp
|
|
1120
1151
|
isFullMode={isFullMode}
|
|
1152
|
+
isLoyaltyModule={isLoyaltyModule}
|
|
1121
1153
|
templateData={templateData}
|
|
1122
1154
|
getFormData={getFormData}
|
|
1123
1155
|
getDefaultTags={type}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { shallowWithIntl } from '../../../helpers/intl-enzym-test-helpers';
|
|
3
|
-
|
|
4
3
|
import { SlideBoxContent } from '../SlideBoxContent';
|
|
5
4
|
import mockdata from '../../mockdata';
|
|
6
5
|
import { templateDetailsImage, templateDetailsVideo, templateDetailsText, templateDetails_ } from '../../Viber/tests/mockData';
|
|
@@ -42,6 +41,15 @@ jest.mock('../../WebPush', () => ({
|
|
|
42
41
|
),
|
|
43
42
|
}));
|
|
44
43
|
|
|
44
|
+
jest.mock('v2Containers/InApp', () => () => <div data-test="inapp" />);
|
|
45
|
+
jest.mock('v2Containers/InAppWrapper', () => () => <div data-test="inapp-wrapper" />);
|
|
46
|
+
|
|
47
|
+
jest.mock('../../../utils/commonUtils', () => ({
|
|
48
|
+
hasNewEditorFlowInAppEnabled: jest.fn(),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
import commonUtil from '../../../utils/commonUtils';
|
|
52
|
+
|
|
45
53
|
describe('Test SlideBoxContent container', () => {
|
|
46
54
|
const onCreateComplete = jest.fn();
|
|
47
55
|
let renderedComponent;
|
|
@@ -910,4 +918,64 @@ describe('Test SlideBoxContent container', () => {
|
|
|
910
918
|
expect(renderedComponent).toMatchSnapshot();
|
|
911
919
|
});
|
|
912
920
|
});
|
|
921
|
+
|
|
922
|
+
describe('InApp vs InAppWrapper rendering conditions', () => {
|
|
923
|
+
|
|
924
|
+
beforeEach(() => {
|
|
925
|
+
jest.clearAllMocks();
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
it('renders InAppWrapper when isFullMode=true and new editor disabled', () => {
|
|
929
|
+
commonUtil.hasNewEditorFlowInAppEnabled.mockReturnValue(false);
|
|
930
|
+
|
|
931
|
+
renderFunction(
|
|
932
|
+
'INAPP',
|
|
933
|
+
'createTemplate',
|
|
934
|
+
{ mode: 'create' },
|
|
935
|
+
{ isFullMode: true, isLoyaltyModule: false }
|
|
936
|
+
);
|
|
937
|
+
|
|
938
|
+
expect(renderedComponent).toMatchSnapshot();
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it('renders InApp when isFullMode=false and loyalty module enabled', () => {
|
|
942
|
+
commonUtil.hasNewEditorFlowInAppEnabled.mockReturnValue(false);
|
|
943
|
+
|
|
944
|
+
renderFunction(
|
|
945
|
+
'INAPP',
|
|
946
|
+
'createTemplate',
|
|
947
|
+
{ mode: 'create' },
|
|
948
|
+
{ isFullMode: false, isLoyaltyModule: true }
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
expect(renderedComponent).toMatchSnapshot();
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
it('renders InApp when not full mode, not loyalty and new editor disabled', () => {
|
|
955
|
+
commonUtil.hasNewEditorFlowInAppEnabled.mockReturnValue(false);
|
|
956
|
+
|
|
957
|
+
renderFunction(
|
|
958
|
+
'INAPP',
|
|
959
|
+
'createTemplate',
|
|
960
|
+
{ mode: 'create' },
|
|
961
|
+
{ isFullMode: false, isLoyaltyModule: false }
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
expect(renderedComponent).toMatchSnapshot();
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
it('renders InAppWrapper when full mode and new editor enabled', () => {
|
|
968
|
+
commonUtil.hasNewEditorFlowInAppEnabled.mockReturnValue(true);
|
|
969
|
+
|
|
970
|
+
renderFunction(
|
|
971
|
+
'INAPP',
|
|
972
|
+
'createTemplate',
|
|
973
|
+
{ mode: 'create' },
|
|
974
|
+
{ isFullMode: true, isLoyaltyModule: false }
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
expect(renderedComponent).toMatchSnapshot();
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
});
|
|
913
981
|
});
|
|
@@ -202,6 +202,114 @@ exports[`Test SlideBoxContent container Email component isTestAndPreviewMode IIF
|
|
|
202
202
|
</SlideBoxContent__CreativesWrapper>
|
|
203
203
|
`;
|
|
204
204
|
|
|
205
|
+
exports[`Test SlideBoxContent container InApp vs InAppWrapper rendering conditions renders InApp when isFullMode=false and loyalty module enabled 1`] = `
|
|
206
|
+
<SlideBoxContent__CreativesWrapper>
|
|
207
|
+
<Component
|
|
208
|
+
getDefaultTags=""
|
|
209
|
+
isFullMode={false}
|
|
210
|
+
key="creatives-inapp-create"
|
|
211
|
+
location={
|
|
212
|
+
Object {
|
|
213
|
+
"pathname": "/inapp/create",
|
|
214
|
+
"query": Object {
|
|
215
|
+
"isEditFromCampaigns": undefined,
|
|
216
|
+
"module": "library",
|
|
217
|
+
"type": "embedded",
|
|
218
|
+
},
|
|
219
|
+
"search": "",
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
onCreateComplete={[MockFunction]}
|
|
223
|
+
templateData={
|
|
224
|
+
Object {
|
|
225
|
+
"mode": "create",
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/>
|
|
229
|
+
</SlideBoxContent__CreativesWrapper>
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
exports[`Test SlideBoxContent container InApp vs InAppWrapper rendering conditions renders InApp when not full mode, not loyalty and new editor disabled 1`] = `
|
|
233
|
+
<SlideBoxContent__CreativesWrapper>
|
|
234
|
+
<Component
|
|
235
|
+
getDefaultTags=""
|
|
236
|
+
isFullMode={false}
|
|
237
|
+
key="creatives-inapp-create"
|
|
238
|
+
location={
|
|
239
|
+
Object {
|
|
240
|
+
"pathname": "/inapp/create",
|
|
241
|
+
"query": Object {
|
|
242
|
+
"isEditFromCampaigns": undefined,
|
|
243
|
+
"module": "library",
|
|
244
|
+
"type": "embedded",
|
|
245
|
+
},
|
|
246
|
+
"search": "",
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
onCreateComplete={[MockFunction]}
|
|
250
|
+
templateData={
|
|
251
|
+
Object {
|
|
252
|
+
"mode": "create",
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/>
|
|
256
|
+
</SlideBoxContent__CreativesWrapper>
|
|
257
|
+
`;
|
|
258
|
+
|
|
259
|
+
exports[`Test SlideBoxContent container InApp vs InAppWrapper rendering conditions renders InAppWrapper when full mode and new editor enabled 1`] = `
|
|
260
|
+
<SlideBoxContent__CreativesWrapper>
|
|
261
|
+
<Component
|
|
262
|
+
getDefaultTags=""
|
|
263
|
+
isFullMode={true}
|
|
264
|
+
key="creatives-inapp-create"
|
|
265
|
+
location={
|
|
266
|
+
Object {
|
|
267
|
+
"pathname": "/inapp/create",
|
|
268
|
+
"query": Object {
|
|
269
|
+
"isEditFromCampaigns": undefined,
|
|
270
|
+
"module": "default",
|
|
271
|
+
"type": false,
|
|
272
|
+
},
|
|
273
|
+
"search": "",
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
onCreateComplete={[MockFunction]}
|
|
277
|
+
templateData={
|
|
278
|
+
Object {
|
|
279
|
+
"mode": "create",
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/>
|
|
283
|
+
</SlideBoxContent__CreativesWrapper>
|
|
284
|
+
`;
|
|
285
|
+
|
|
286
|
+
exports[`Test SlideBoxContent container InApp vs InAppWrapper rendering conditions renders InAppWrapper when isFullMode=true and new editor disabled 1`] = `
|
|
287
|
+
<SlideBoxContent__CreativesWrapper>
|
|
288
|
+
<Component
|
|
289
|
+
getDefaultTags=""
|
|
290
|
+
isFullMode={true}
|
|
291
|
+
key="creatives-inapp-create"
|
|
292
|
+
location={
|
|
293
|
+
Object {
|
|
294
|
+
"pathname": "/inapp/create",
|
|
295
|
+
"query": Object {
|
|
296
|
+
"isEditFromCampaigns": undefined,
|
|
297
|
+
"module": "default",
|
|
298
|
+
"type": false,
|
|
299
|
+
},
|
|
300
|
+
"search": "",
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
onCreateComplete={[MockFunction]}
|
|
304
|
+
templateData={
|
|
305
|
+
Object {
|
|
306
|
+
"mode": "create",
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/>
|
|
310
|
+
</SlideBoxContent__CreativesWrapper>
|
|
311
|
+
`;
|
|
312
|
+
|
|
205
313
|
exports[`Test SlideBoxContent container Should handle isTestAndPreviewMode IIFE implementation correctly 1`] = `
|
|
206
314
|
<SlideBoxContent__CreativesWrapper>
|
|
207
315
|
<ForwardRef
|
|
@@ -1103,17 +1211,26 @@ exports[`Test SlideBoxContent container Should render correct component for what
|
|
|
1103
1211
|
|
|
1104
1212
|
exports[`Test SlideBoxContent container Should render correct component for whatsapp channel create mode 2`] = `
|
|
1105
1213
|
<SlideBoxContent__CreativesWrapper>
|
|
1106
|
-
<
|
|
1107
|
-
date={0}
|
|
1214
|
+
<Component
|
|
1108
1215
|
getDefaultTags=""
|
|
1109
|
-
key="creatives-inapp-
|
|
1216
|
+
key="creatives-inapp-create"
|
|
1217
|
+
location={
|
|
1218
|
+
Object {
|
|
1219
|
+
"pathname": "/inapp/create",
|
|
1220
|
+
"query": Object {
|
|
1221
|
+
"isEditFromCampaigns": undefined,
|
|
1222
|
+
"module": "library",
|
|
1223
|
+
"type": "embedded",
|
|
1224
|
+
},
|
|
1225
|
+
"search": "",
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1110
1228
|
onCreateComplete={[MockFunction]}
|
|
1111
1229
|
templateData={
|
|
1112
1230
|
Object {
|
|
1113
1231
|
"mode": "create",
|
|
1114
1232
|
}
|
|
1115
1233
|
}
|
|
1116
|
-
type=""
|
|
1117
1234
|
/>
|
|
1118
1235
|
</SlideBoxContent__CreativesWrapper>
|
|
1119
1236
|
`;
|
|
@@ -39,33 +39,58 @@ jest.mock('../../../utils/common', () => ({
|
|
|
39
39
|
// Mock HTMLEditor component - must be before imports
|
|
40
40
|
jest.mock('../../../v2Components/HtmlEditor', () => {
|
|
41
41
|
const React = require('react');
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
|
|
43
|
+
const MockHTMLEditor = function MockHTMLEditor({
|
|
44
|
+
variant,
|
|
45
|
+
initialContent,
|
|
46
|
+
onContentChange,
|
|
47
|
+
onSave,
|
|
48
|
+
'data-test': dataTest,
|
|
49
|
+
}) {
|
|
50
|
+
return React.createElement(
|
|
51
|
+
'div',
|
|
52
|
+
{ 'data-testid': dataTest || 'inapp-html-editor' },
|
|
44
53
|
React.createElement('div', { 'data-testid': 'html-editor-variant' }, variant),
|
|
45
|
-
React.createElement(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
54
|
+
React.createElement(
|
|
55
|
+
'div',
|
|
56
|
+
{ 'data-testid': 'html-editor-initial-android' },
|
|
57
|
+
initialContent?.android || ''
|
|
58
|
+
),
|
|
59
|
+
React.createElement(
|
|
60
|
+
'div',
|
|
61
|
+
{ 'data-testid': 'html-editor-initial-ios' },
|
|
62
|
+
initialContent?.ios || ''
|
|
63
|
+
),
|
|
64
|
+
React.createElement(
|
|
65
|
+
'button',
|
|
66
|
+
{
|
|
67
|
+
'data-testid': 'html-editor-change-button',
|
|
68
|
+
onClick: () => {
|
|
69
|
+
if (onContentChange) {
|
|
70
|
+
onContentChange({
|
|
71
|
+
android: '<p>Updated Android HTML</p>',
|
|
72
|
+
ios: '<p>Updated iOS HTML</p>',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
'Simulate Content Change'
|
|
78
|
+
),
|
|
79
|
+
React.createElement(
|
|
80
|
+
'button',
|
|
81
|
+
{
|
|
82
|
+
'data-testid': 'html-editor-save-button',
|
|
83
|
+
onClick: () => {
|
|
84
|
+
if (onSave) {
|
|
85
|
+
onSave({
|
|
86
|
+
android: '<p>Saved Android HTML</p>',
|
|
87
|
+
ios: '<p>Saved iOS HTML</p>',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
'Simulate Save'
|
|
93
|
+
)
|
|
69
94
|
);
|
|
70
95
|
};
|
|
71
96
|
MockHTMLEditor.displayName = 'MockHTMLEditor';
|
|
@@ -75,22 +100,25 @@ jest.mock('../../../v2Components/HtmlEditor', () => {
|
|
|
75
100
|
};
|
|
76
101
|
});
|
|
77
102
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
import { IntlProvider } from 'react-intl';
|
|
82
|
-
import { Provider } from 'react-redux';
|
|
83
|
-
import { configureStore } from '@capillarytech/vulcan-react-sdk/utils';
|
|
84
|
-
import history from '../../../utils/history';
|
|
85
|
-
import { initialReducer } from '../../../initialReducer';
|
|
86
|
-
import { InApp } from '../index';
|
|
87
|
-
import { INAPP_EDITOR_TYPES } from '../../InAppWrapper/constants';
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// Mock other dependencies
|
|
103
|
+
/**
|
|
104
|
+
* Mock CapDeviceContent to ensure HTMLEditor renders
|
|
105
|
+
*/
|
|
91
106
|
jest.mock('../../../v2Components/CapDeviceContent', () => {
|
|
107
|
+
const React = require('react');
|
|
108
|
+
const HTMLEditor = require('../../../v2Components/HtmlEditor').default;
|
|
109
|
+
|
|
92
110
|
return function MockCapDeviceContent() {
|
|
93
|
-
return
|
|
111
|
+
return (
|
|
112
|
+
<div data-testid="cap-device-content">
|
|
113
|
+
<HTMLEditor
|
|
114
|
+
variant="inapp"
|
|
115
|
+
initialContent={{ android: '', ios: '' }}
|
|
116
|
+
onContentChange={jest.fn()}
|
|
117
|
+
onSave={jest.fn()}
|
|
118
|
+
data-test="inapp-html-editor"
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
94
122
|
};
|
|
95
123
|
});
|
|
96
124
|
|
|
@@ -100,7 +128,20 @@ jest.mock('../../../v2Components/TemplatePreview', () => {
|
|
|
100
128
|
};
|
|
101
129
|
});
|
|
102
130
|
|
|
131
|
+
import React from 'react';
|
|
132
|
+
import { render, screen, fireEvent, act } from '@testing-library/react';
|
|
133
|
+
import '@testing-library/jest-dom';
|
|
134
|
+
import { IntlProvider } from 'react-intl';
|
|
135
|
+
import { Provider } from 'react-redux';
|
|
136
|
+
import { configureStore } from '@capillarytech/vulcan-react-sdk/utils';
|
|
137
|
+
|
|
138
|
+
import history from '../../../utils/history';
|
|
139
|
+
import { initialReducer } from '../../../initialReducer';
|
|
140
|
+
import { InApp } from '../index';
|
|
141
|
+
import { INAPP_EDITOR_TYPES } from '../../InAppWrapper/constants';
|
|
142
|
+
|
|
103
143
|
let store;
|
|
144
|
+
|
|
104
145
|
beforeAll(() => {
|
|
105
146
|
store = configureStore({}, initialReducer, history);
|
|
106
147
|
});
|
|
@@ -135,8 +176,6 @@ const defaultProps = {
|
|
|
135
176
|
configs: {
|
|
136
177
|
android: '1',
|
|
137
178
|
ios: '1',
|
|
138
|
-
accessToken: 'test-token',
|
|
139
|
-
deeplink: '{}',
|
|
140
179
|
},
|
|
141
180
|
},
|
|
142
181
|
},
|
|
@@ -145,7 +184,6 @@ const defaultProps = {
|
|
|
145
184
|
query: {},
|
|
146
185
|
search: '',
|
|
147
186
|
},
|
|
148
|
-
getDefaultTags: null,
|
|
149
187
|
supportedTags: [],
|
|
150
188
|
metaEntities: {
|
|
151
189
|
tags: {
|
|
@@ -158,26 +196,11 @@ const defaultProps = {
|
|
|
158
196
|
currentOrgDetails: {
|
|
159
197
|
accessibleFeatures: [],
|
|
160
198
|
},
|
|
161
|
-
fetchingLiquidValidation: false,
|
|
162
|
-
getTemplateDetailsInProgress: false,
|
|
163
|
-
isEditInApp: false,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// Create stable defaultData objects outside renderComponent to avoid reference changes
|
|
167
|
-
const createStableDefaultData = (editorType) => {
|
|
168
|
-
if (!editorType) return {};
|
|
169
|
-
return { 'editor-type': editorType };
|
|
170
199
|
};
|
|
171
200
|
|
|
172
201
|
const renderComponent = (props = {}) => {
|
|
173
202
|
const mergedProps = { ...defaultProps, ...props };
|
|
174
|
-
|
|
175
|
-
if (!mergedProps.defaultData) {
|
|
176
|
-
mergedProps.defaultData = {};
|
|
177
|
-
} else if (mergedProps.defaultData['editor-type']) {
|
|
178
|
-
// Create a stable reference for defaultData
|
|
179
|
-
mergedProps.defaultData = createStableDefaultData(mergedProps.defaultData['editor-type']);
|
|
180
|
-
}
|
|
203
|
+
|
|
181
204
|
return render(
|
|
182
205
|
<Provider store={store}>
|
|
183
206
|
<IntlProvider locale="en" messages={{}}>
|
|
@@ -195,36 +218,33 @@ describe('InApp HTMLEditor Integration', () => {
|
|
|
195
218
|
describe('Template Name Editing', () => {
|
|
196
219
|
test('should not truncate template name when editing', async () => {
|
|
197
220
|
const showTemplateNameMock = jest.fn();
|
|
221
|
+
|
|
198
222
|
renderComponent({
|
|
199
|
-
defaultData: {
|
|
223
|
+
defaultData: {
|
|
224
|
+
'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR,
|
|
225
|
+
'template-name': 'abcd',
|
|
226
|
+
},
|
|
200
227
|
showTemplateName: showTemplateNameMock,
|
|
201
228
|
});
|
|
202
229
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// Verify showTemplateName was called with the template name
|
|
209
|
-
// The template name input is handled by the wrapper, not the InApp component
|
|
210
|
-
// So we verify that the callback was set up correctly
|
|
230
|
+
const editor = await screen.findByTestId('inapp-html-editor');
|
|
231
|
+
expect(editor).toBeInTheDocument();
|
|
211
232
|
expect(showTemplateNameMock).toBeDefined();
|
|
212
233
|
});
|
|
213
234
|
|
|
214
235
|
test('should preserve template name when showTemplateName callback is provided', async () => {
|
|
215
236
|
const showTemplateNameMock = jest.fn();
|
|
237
|
+
|
|
216
238
|
renderComponent({
|
|
217
|
-
defaultData: {
|
|
239
|
+
defaultData: {
|
|
240
|
+
'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR,
|
|
241
|
+
'template-name': 'test',
|
|
242
|
+
},
|
|
218
243
|
showTemplateName: showTemplateNameMock,
|
|
219
244
|
});
|
|
220
245
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// Verify the component renders correctly with template name
|
|
227
|
-
// The template name is managed by the wrapper component
|
|
246
|
+
const editor = await screen.findByTestId('inapp-html-editor');
|
|
247
|
+
expect(editor).toBeInTheDocument();
|
|
228
248
|
expect(showTemplateNameMock).toBeDefined();
|
|
229
249
|
});
|
|
230
250
|
});
|
|
@@ -232,40 +252,7 @@ describe('InApp HTMLEditor Integration', () => {
|
|
|
232
252
|
describe('TAG API Calls', () => {
|
|
233
253
|
test('should only make one TAG API call when HTML Editor is used', async () => {
|
|
234
254
|
const fetchSchemaForEntityMock = jest.fn();
|
|
235
|
-
|
|
236
|
-
renderComponent({
|
|
237
|
-
defaultData: { 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR },
|
|
238
|
-
globalActions: {
|
|
239
|
-
...defaultProps.globalActions,
|
|
240
|
-
fetchSchemaForEntity: fetchSchemaForEntityMock,
|
|
241
|
-
},
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// Wait for component to mount and initialize
|
|
245
|
-
await waitFor(() => {
|
|
246
|
-
// Component should be rendered
|
|
247
|
-
expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
|
|
248
|
-
});
|
|
249
255
|
|
|
250
|
-
// Wait a bit for all useEffects to complete
|
|
251
|
-
await waitFor(() => {
|
|
252
|
-
// After isHTMLTemplate is set to true, fetchSchemaForEntity should not be called again
|
|
253
|
-
// The initial call might happen before isHTMLTemplate is set, but subsequent calls should not happen
|
|
254
|
-
// For HTML Editor, tags should only be fetched via handleOnTagsContextChange
|
|
255
|
-
}, { timeout: 1000 });
|
|
256
|
-
|
|
257
|
-
// For HTML Editor, fetchSchemaForEntity should not be called after isHTMLTemplate is set
|
|
258
|
-
// (It might be called once initially before isHTMLTemplate is set, but that's acceptable)
|
|
259
|
-
// The key is that it's not called multiple times
|
|
260
|
-
const callCount = fetchSchemaForEntityMock.mock.calls.length;
|
|
261
|
-
// Allow 0 or 1 calls (1 if it was called before isHTMLTemplate was set)
|
|
262
|
-
expect(callCount).toBeLessThanOrEqual(1);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
test('should make TAG API call only once when handleOnTagsContextChange is called', async () => {
|
|
266
|
-
const fetchSchemaForEntityMock = jest.fn();
|
|
267
|
-
|
|
268
|
-
// We need to access the handler, but since it's internal, we'll test via HTMLEditor's onContextChange
|
|
269
256
|
renderComponent({
|
|
270
257
|
defaultData: { 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR },
|
|
271
258
|
globalActions: {
|
|
@@ -274,19 +261,8 @@ describe('InApp HTMLEditor Integration', () => {
|
|
|
274
261
|
},
|
|
275
262
|
});
|
|
276
263
|
|
|
277
|
-
await
|
|
278
|
-
expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Wait a bit for all useEffects to complete
|
|
282
|
-
await waitFor(() => {
|
|
283
|
-
// After isHTMLTemplate is set to true, fetchSchemaForEntity should not be called again
|
|
284
|
-
}, { timeout: 1000 });
|
|
264
|
+
await screen.findByTestId('inapp-html-editor');
|
|
285
265
|
|
|
286
|
-
// Simulate context change from HTMLEditor (which would call onContextChange)
|
|
287
|
-
// Since we can't directly access the handler, we verify the behavior
|
|
288
|
-
// by checking that fetchSchemaForEntity is not called multiple times
|
|
289
|
-
// (It might be called once initially before isHTMLTemplate is set, but that's acceptable)
|
|
290
266
|
const callCount = fetchSchemaForEntityMock.mock.calls.length;
|
|
291
267
|
expect(callCount).toBeLessThanOrEqual(1);
|
|
292
268
|
});
|
|
@@ -298,45 +274,37 @@ describe('InApp HTMLEditor Integration', () => {
|
|
|
298
274
|
defaultData: { 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR },
|
|
299
275
|
});
|
|
300
276
|
|
|
301
|
-
await
|
|
302
|
-
expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
|
|
303
|
-
});
|
|
277
|
+
await screen.findByTestId('inapp-html-editor');
|
|
304
278
|
|
|
305
|
-
// Simulate content being added
|
|
306
279
|
const changeButton = screen.getByTestId('html-editor-change-button');
|
|
280
|
+
|
|
307
281
|
act(() => {
|
|
308
282
|
fireEvent.click(changeButton);
|
|
309
283
|
});
|
|
310
284
|
|
|
311
|
-
// Get the layout radio buttons
|
|
312
285
|
const layoutRadios = container.querySelectorAll('input[type="radio"]');
|
|
313
|
-
expect(layoutRadios.length).toBeGreaterThan(0);
|
|
314
286
|
|
|
315
|
-
// Change layout type
|
|
316
287
|
if (layoutRadios.length > 1) {
|
|
317
288
|
act(() => {
|
|
318
|
-
fireEvent.change(layoutRadios[1], {
|
|
289
|
+
fireEvent.change(layoutRadios[1], {
|
|
290
|
+
target: { value: 'HEADER' },
|
|
291
|
+
});
|
|
319
292
|
});
|
|
320
293
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
await waitFor(() => {
|
|
324
|
-
const editor = screen.getByTestId('inapp-html-editor');
|
|
325
|
-
expect(editor).toBeInTheDocument();
|
|
326
|
-
});
|
|
294
|
+
const editor = await screen.findByTestId('inapp-html-editor');
|
|
295
|
+
expect(editor).toBeInTheDocument();
|
|
327
296
|
}
|
|
328
297
|
});
|
|
329
298
|
});
|
|
330
299
|
|
|
331
300
|
describe('Layout Labels', () => {
|
|
332
301
|
test('should use correct layout labels in constants', () => {
|
|
333
|
-
const { INAPP_LAYOUT_DETAILS, INAPP_MESSAGE_LAYOUT_TYPES } =
|
|
334
|
-
|
|
335
|
-
|
|
302
|
+
const { INAPP_LAYOUT_DETAILS, INAPP_MESSAGE_LAYOUT_TYPES } =
|
|
303
|
+
require('../constants');
|
|
304
|
+
|
|
336
305
|
expect(INAPP_MESSAGE_LAYOUT_TYPES.TOPBANNER).toBe('HEADER');
|
|
337
306
|
expect(INAPP_LAYOUT_DETAILS.HEADER).toBeDefined();
|
|
338
|
-
|
|
339
|
-
// Verify that FOOTER maps to Bottom banner
|
|
307
|
+
|
|
340
308
|
expect(INAPP_MESSAGE_LAYOUT_TYPES.BOTTOMBANNER).toBe('FOOTER');
|
|
341
309
|
expect(INAPP_LAYOUT_DETAILS.FOOTER).toBeDefined();
|
|
342
310
|
});
|
|
@@ -346,29 +314,16 @@ describe('InApp HTMLEditor Integration', () => {
|
|
|
346
314
|
test('should allow creating template with Android-only content when both devices supported', async () => {
|
|
347
315
|
renderComponent({
|
|
348
316
|
defaultData: { 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR },
|
|
349
|
-
accountData: {
|
|
350
|
-
selectedWeChatAccount: {
|
|
351
|
-
...defaultProps.accountData.selectedWeChatAccount,
|
|
352
|
-
configs: {
|
|
353
|
-
android: '1',
|
|
354
|
-
ios: '1',
|
|
355
|
-
},
|
|
356
|
-
},
|
|
357
|
-
},
|
|
358
317
|
});
|
|
359
318
|
|
|
360
|
-
await
|
|
361
|
-
expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
|
|
362
|
-
});
|
|
319
|
+
await screen.findByTestId('inapp-html-editor');
|
|
363
320
|
|
|
364
|
-
// Simulate adding content only for Android
|
|
365
321
|
const changeButton = screen.getByTestId('html-editor-change-button');
|
|
322
|
+
|
|
366
323
|
act(() => {
|
|
367
324
|
fireEvent.click(changeButton);
|
|
368
325
|
});
|
|
369
326
|
|
|
370
|
-
// Done button should be enabled (not disabled)
|
|
371
|
-
// We can't directly test isDisableDone, but we can verify the component renders
|
|
372
327
|
expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
|
|
373
328
|
});
|
|
374
329
|
});
|
|
@@ -14,6 +14,8 @@ import CapRow from "@capillarytech/cap-ui-library/CapRow";
|
|
|
14
14
|
import CapColumn from "@capillarytech/cap-ui-library/CapColumn";
|
|
15
15
|
import CapButton from "@capillarytech/cap-ui-library/CapButton";
|
|
16
16
|
import CapNotification from "@capillarytech/cap-ui-library/CapNotification";
|
|
17
|
+
import CapTab from "@capillarytech/cap-ui-library/CapTab";
|
|
18
|
+
import CapInput from "@capillarytech/cap-ui-library/CapInput";
|
|
17
19
|
import { makeSelectInApp, makeSelectAccount, makeSelectGetTemplateDetailsInProgress } from "./selectors";
|
|
18
20
|
import * as globalActions from '../Cap/actions';
|
|
19
21
|
import {
|
|
@@ -52,18 +54,20 @@ import {
|
|
|
52
54
|
IOS_CAPITAL,
|
|
53
55
|
} from "./constants";
|
|
54
56
|
import { INAPP, SMS } from "../CreativesContainer/constants";
|
|
57
|
+
import { AI_CONTENT_BOT_DISABLED } from "../../constants/unified";
|
|
55
58
|
import {
|
|
56
59
|
ALL, TAG, EMBEDDED, DEFAULT, FULL, LIBRARY,
|
|
57
60
|
} from "../Whatsapp/constants";
|
|
58
61
|
import { getCdnUrl } from "../../utils/cdnTransformation";
|
|
59
62
|
import { getCtaObject, hasAnyErrors, getSingleTab } from "./utils";
|
|
60
63
|
import { validateInAppContent } from "../../utils/commonUtils";
|
|
61
|
-
import { hasLiquidSupportFeature } from "../../utils/common";
|
|
64
|
+
import { hasLiquidSupportFeature, hasNewEditorFlowInAppEnabled } from "../../utils/common";
|
|
62
65
|
import formBuilderMessages from "../../v2Components/FormBuilder/messages";
|
|
63
66
|
import HTMLEditor from "../../v2Components/HtmlEditor";
|
|
64
67
|
import { HTML_EDITOR_VARIANTS } from "../../v2Components/HtmlEditor/constants";
|
|
65
68
|
import { INAPP_EDITOR_TYPES } from "../InAppWrapper/constants";
|
|
66
69
|
import InappAdvanced from "../InappAdvance/index";
|
|
70
|
+
import CapDeviceContent from "../../v2Components/CapDeviceContent";
|
|
67
71
|
import { ErrorInfoNote } from "../../v2Components/ErrorInfoNote";
|
|
68
72
|
|
|
69
73
|
let editContent = {};
|
|
@@ -74,6 +78,7 @@ export const InApp = (props) => {
|
|
|
74
78
|
actions,
|
|
75
79
|
globalActions,
|
|
76
80
|
isFullMode,
|
|
81
|
+
isLoyaltyModule,
|
|
77
82
|
onCreateComplete,
|
|
78
83
|
params,
|
|
79
84
|
templateData = {},
|
|
@@ -878,7 +883,7 @@ export const InApp = (props) => {
|
|
|
878
883
|
// Use 'html editor template' as title for HTML editor to differentiate from BEE editor
|
|
879
884
|
title: isHTMLTemplate ? 'html editor template' : titleAndroid,
|
|
880
885
|
message: androidMessage,
|
|
881
|
-
bodyType:
|
|
886
|
+
bodyType: bodyTypeForBackend,
|
|
882
887
|
expandableDetails: {
|
|
883
888
|
style: androidExpandableStyle,
|
|
884
889
|
message: androidMessage,
|
|
@@ -1240,11 +1245,13 @@ export const InApp = (props) => {
|
|
|
1240
1245
|
&& !isBEEeditor
|
|
1241
1246
|
&& !isBeeFreeTemplate;
|
|
1242
1247
|
|
|
1248
|
+
const isNewEditorFlowEnabled = !isLoyaltyModule && hasNewEditorFlowInAppEnabled();
|
|
1249
|
+
|
|
1243
1250
|
// Use state if available, otherwise fall back to direct data check
|
|
1244
|
-
const shouldUseHTMLEditor = isHTMLTemplate || isHTMLTemplateFromData;
|
|
1251
|
+
const shouldUseHTMLEditor = isNewEditorFlowEnabled && (isHTMLTemplate || isHTMLTemplateFromData);
|
|
1245
1252
|
|
|
1246
1253
|
// Only route to Bee editor if it's explicitly a Bee editor AND not an HTML template
|
|
1247
|
-
const shouldUseBeeEditor = (isBEEeditor || isBeeFreeTemplate) && !shouldUseHTMLEditor;
|
|
1254
|
+
const shouldUseBeeEditor = isNewEditorFlowEnabled && (isBEEeditor || isBeeFreeTemplate) && !shouldUseHTMLEditor;
|
|
1248
1255
|
|
|
1249
1256
|
// Early returns to avoid nested ternary
|
|
1250
1257
|
if (isEditInApp && getTemplateDetailsInProgress) {
|
|
@@ -1280,10 +1287,157 @@ export const InApp = (props) => {
|
|
|
1280
1287
|
);
|
|
1281
1288
|
}
|
|
1282
1289
|
|
|
1290
|
+
// ── Old-flow helpers (used by CapDeviceContent when flag is disabled) ──────
|
|
1291
|
+
const isAiContentBotDisabled = currentOrgDetails?.accessibleFeatures?.includes(AI_CONTENT_BOT_DISABLED);
|
|
1292
|
+
|
|
1293
|
+
const templateDescErrorHandler = (value) => {
|
|
1294
|
+
const { unsupportedTags, isBraceError } = validateTags({
|
|
1295
|
+
content: value,
|
|
1296
|
+
tagsParam: tags,
|
|
1297
|
+
injectedTagsParams: injectedTags,
|
|
1298
|
+
location,
|
|
1299
|
+
tagModule: getDefaultTags,
|
|
1300
|
+
isFullMode,
|
|
1301
|
+
}) || {};
|
|
1302
|
+
if (unsupportedTags?.length > 0) {
|
|
1303
|
+
return formatMessage(globalMessages.unsupportedTagsValidationError, { unsupportedTags });
|
|
1304
|
+
}
|
|
1305
|
+
if (isBraceError) {
|
|
1306
|
+
return formatMessage(globalMessages.braceValidationError);
|
|
1307
|
+
}
|
|
1308
|
+
return '';
|
|
1309
|
+
};
|
|
1310
|
+
|
|
1311
|
+
const onCopyTitleAndContent = () => {
|
|
1312
|
+
if (panes === ANDROID) {
|
|
1313
|
+
setTitleAndroid(titleIos);
|
|
1314
|
+
setTemplateMessageAndroid(templateMessageIos);
|
|
1315
|
+
setInAppImageSrcAndroid(inAppImageSrcIos);
|
|
1316
|
+
} else {
|
|
1317
|
+
setTitleIos(titleAndroid);
|
|
1318
|
+
setTemplateMessageIos(templateMessageAndroid);
|
|
1319
|
+
setInAppImageSrcIos(inAppImageSrcAndroid);
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
const onTagSelect = (value, index) => {
|
|
1324
|
+
const tag = `{{${value}}}`;
|
|
1325
|
+
if (panes === ANDROID) {
|
|
1326
|
+
if (index === 0) setTitleAndroid((prev) => prev + tag);
|
|
1327
|
+
else setTemplateMessageAndroid((prev) => prev + tag);
|
|
1328
|
+
} else if (index === 0) {
|
|
1329
|
+
setTitleIos((prev) => prev + tag);
|
|
1330
|
+
} else {
|
|
1331
|
+
setTemplateMessageIos((prev) => prev + tag);
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
|
|
1335
|
+
// Device support flags (same derivation as InappAdvance)
|
|
1336
|
+
const isAndroidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
|
|
1337
|
+
const isIosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
|
|
1338
|
+
|
|
1339
|
+
// CapDeviceContent tab panes (old flow)
|
|
1340
|
+
const DEVICE_PANES = [
|
|
1341
|
+
{
|
|
1342
|
+
content: (
|
|
1343
|
+
<CapDeviceContent
|
|
1344
|
+
panes={ANDROID}
|
|
1345
|
+
actions={actions}
|
|
1346
|
+
editData={editData}
|
|
1347
|
+
isFullMode={isFullMode}
|
|
1348
|
+
inAppImageSrc={inAppImageSrcAndroid}
|
|
1349
|
+
setInAppImageSrc={setInAppImageSrcAndroid}
|
|
1350
|
+
isEditFlow={isEditFlow}
|
|
1351
|
+
ctaData={ctaDataAndroid}
|
|
1352
|
+
setCtaData={setCtaDataAndroid}
|
|
1353
|
+
buttonType={buttonTypeAndroid}
|
|
1354
|
+
setButtonType={setButtonTypeAndroid}
|
|
1355
|
+
templateMediaType={templateMediaType}
|
|
1356
|
+
setTemplateMediaType={setTemplateMediaType}
|
|
1357
|
+
title={titleAndroid}
|
|
1358
|
+
setTitle={setTitleAndroid}
|
|
1359
|
+
templateMessageError={templateMessageErrorAndroid}
|
|
1360
|
+
templateMessage={templateMessageAndroid}
|
|
1361
|
+
setTemplateMessage={setTemplateMessageAndroid}
|
|
1362
|
+
setTemplateMessageError={setTemplateMessageErrorAndroid}
|
|
1363
|
+
addActionLink={addActionLinkAndroid}
|
|
1364
|
+
setAddActionLink={setAddActionLinkAndroid}
|
|
1365
|
+
deepLink={deepLink}
|
|
1366
|
+
deepLinkValue={deepLinkValueAndroid}
|
|
1367
|
+
setDeepLinkValue={setDeepLinkValueAndroid}
|
|
1368
|
+
onCopyTitleAndContent={onCopyTitleAndContent}
|
|
1369
|
+
isOtherDeviceSupported={isIosSupported}
|
|
1370
|
+
tags={tags}
|
|
1371
|
+
onTagSelect={onTagSelect}
|
|
1372
|
+
handleOnTagsContextChange={handleOnTagsContextChange}
|
|
1373
|
+
templateDescErrorHandler={templateDescErrorHandler}
|
|
1374
|
+
templateTitleError={templateTitleErrorAndroid}
|
|
1375
|
+
setTemplateTitleError={setTemplateTitleErrorAndroid}
|
|
1376
|
+
isAiContentBotDisabled={isAiContentBotDisabled}
|
|
1377
|
+
injectedTags={injectedTags}
|
|
1378
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1379
|
+
location={location}
|
|
1380
|
+
/>
|
|
1381
|
+
),
|
|
1382
|
+
tab: <FormattedMessage {...messages.Android} />,
|
|
1383
|
+
key: ANDROID,
|
|
1384
|
+
isSupported: isAndroidSupported,
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
content: (
|
|
1388
|
+
<CapDeviceContent
|
|
1389
|
+
panes={IOS}
|
|
1390
|
+
actions={actions}
|
|
1391
|
+
editData={editData}
|
|
1392
|
+
isFullMode={isFullMode}
|
|
1393
|
+
inAppImageSrc={inAppImageSrcIos}
|
|
1394
|
+
setInAppImageSrc={setInAppImageSrcIos}
|
|
1395
|
+
isEditFlow={isEditFlow}
|
|
1396
|
+
ctaData={ctaDataIos}
|
|
1397
|
+
setCtaData={setCtaDataIos}
|
|
1398
|
+
buttonType={buttonTypeIos}
|
|
1399
|
+
setButtonType={setButtonTypeIos}
|
|
1400
|
+
templateMediaType={templateMediaType}
|
|
1401
|
+
setTemplateMediaType={setTemplateMediaType}
|
|
1402
|
+
title={titleIos}
|
|
1403
|
+
setTitle={setTitleIos}
|
|
1404
|
+
templateMessageError={templateMessageErrorIos}
|
|
1405
|
+
templateMessage={templateMessageIos}
|
|
1406
|
+
setTemplateMessage={setTemplateMessageIos}
|
|
1407
|
+
setTemplateMessageError={setTemplateMessageErrorIos}
|
|
1408
|
+
addActionLink={addActionLinkIos}
|
|
1409
|
+
setAddActionLink={setAddActionLinkIos}
|
|
1410
|
+
deepLink={deepLink}
|
|
1411
|
+
deepLinkValue={deepLinkValueIos}
|
|
1412
|
+
setDeepLinkValue={setDeepLinkValueIos}
|
|
1413
|
+
onCopyTitleAndContent={onCopyTitleAndContent}
|
|
1414
|
+
isOtherDeviceSupported={isAndroidSupported}
|
|
1415
|
+
tags={tags}
|
|
1416
|
+
onTagSelect={onTagSelect}
|
|
1417
|
+
handleOnTagsContextChange={handleOnTagsContextChange}
|
|
1418
|
+
templateDescErrorHandler={templateDescErrorHandler}
|
|
1419
|
+
templateTitleError={templateTitleErrorIos}
|
|
1420
|
+
setTemplateTitleError={setTemplateTitleErrorIos}
|
|
1421
|
+
isAiContentBotDisabled={isAiContentBotDisabled}
|
|
1422
|
+
injectedTags={injectedTags}
|
|
1423
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1424
|
+
location={location}
|
|
1425
|
+
/>
|
|
1426
|
+
),
|
|
1427
|
+
tab: <FormattedMessage {...messages.Ios} />,
|
|
1428
|
+
key: IOS,
|
|
1429
|
+
isSupported: isIosSupported,
|
|
1430
|
+
},
|
|
1431
|
+
];
|
|
1432
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1433
|
+
|
|
1434
|
+
// Calculate column span: HTML editor = 18, everything else = 24
|
|
1435
|
+
const editorColumnSpan = shouldUseHTMLEditor ? 18 : 24;
|
|
1436
|
+
|
|
1283
1437
|
return (
|
|
1284
1438
|
<CapSpin spinning={spin || fetchingLiquidValidation} tip={fetchingLiquidValidation ? <FormattedMessage {...formBuilderMessages.liquidSpinText} /> : ""}>
|
|
1285
1439
|
<CapRow className="cap-inapp-creatives">
|
|
1286
|
-
<CapColumn span={
|
|
1440
|
+
<CapColumn span={editorColumnSpan}>
|
|
1287
1441
|
{/* Creative layout type */}
|
|
1288
1442
|
{shouldUseHTMLEditor && (
|
|
1289
1443
|
<>
|
|
@@ -1304,7 +1458,7 @@ export const InApp = (props) => {
|
|
|
1304
1458
|
/>
|
|
1305
1459
|
</>
|
|
1306
1460
|
)}
|
|
1307
|
-
{shouldUseHTMLEditor
|
|
1461
|
+
{shouldUseHTMLEditor && (
|
|
1308
1462
|
<HTMLEditor
|
|
1309
1463
|
key={`inapp-html-editor-v${htmlEditorContentVersion}`}
|
|
1310
1464
|
variant={HTML_EDITOR_VARIANTS.INAPP}
|
|
@@ -1320,19 +1474,16 @@ export const InApp = (props) => {
|
|
|
1320
1474
|
location={location}
|
|
1321
1475
|
selectedOfferDetails={selectedOfferDetails}
|
|
1322
1476
|
onTagSelect={() => {
|
|
1323
|
-
// Tag insertion
|
|
1324
|
-
// Content updates will be propagated via onContentChange callback
|
|
1477
|
+
// Tag insertion handled by HTMLEditor's CodeEditorPane at cursor position
|
|
1325
1478
|
}}
|
|
1326
|
-
// Don't pass globalActions to prevent duplicate API calls
|
|
1327
|
-
// HTMLEditor will use onContextChange instead
|
|
1328
1479
|
onContextChange={handleOnTagsContextChange}
|
|
1329
|
-
// Pass validation errors to HTMLEditor for display
|
|
1330
1480
|
errors={errorMessage}
|
|
1331
1481
|
isFullMode={isFullMode}
|
|
1332
1482
|
data-test="inapp-html-editor"
|
|
1333
1483
|
style={{ width: '138%' }}
|
|
1334
1484
|
/>
|
|
1335
|
-
)
|
|
1485
|
+
)}
|
|
1486
|
+
{!shouldUseHTMLEditor && isNewEditorFlowEnabled && (
|
|
1336
1487
|
<InappAdvanced
|
|
1337
1488
|
getFormData={getFormData}
|
|
1338
1489
|
setIsLoadingContent={setIsLoadingContent}
|
|
@@ -1357,11 +1508,29 @@ export const InApp = (props) => {
|
|
|
1357
1508
|
onCreateComplete={onCreateComplete}
|
|
1358
1509
|
/>
|
|
1359
1510
|
)}
|
|
1511
|
+
{!shouldUseHTMLEditor && !isNewEditorFlowEnabled && (
|
|
1512
|
+
<>
|
|
1513
|
+
<CapInput
|
|
1514
|
+
label={<FormattedMessage {...messages.creativeName} />}
|
|
1515
|
+
onChange={({ target: { value } }) => setTempName(value)}
|
|
1516
|
+
value={tempName}
|
|
1517
|
+
labelPosition="top"
|
|
1518
|
+
size="default"
|
|
1519
|
+
/>
|
|
1520
|
+
<CapTab
|
|
1521
|
+
panes={DEVICE_PANES.filter((devicePane) => devicePane?.isSupported === true)}
|
|
1522
|
+
onChange={(value) => setPanes(value)}
|
|
1523
|
+
activeKey={panes}
|
|
1524
|
+
defaultActiveKey={panes}
|
|
1525
|
+
className="inapp-template-device-tab"
|
|
1526
|
+
/>
|
|
1527
|
+
</>
|
|
1528
|
+
)}
|
|
1360
1529
|
</CapColumn>
|
|
1361
1530
|
</CapRow>
|
|
1362
|
-
{/* Footer with Done/Update button -
|
|
1531
|
+
{/* Footer with Done/Update button - show for HTML editor and old CapDeviceContent flow */}
|
|
1363
1532
|
{
|
|
1364
|
-
shouldUseHTMLEditor && (
|
|
1533
|
+
(shouldUseHTMLEditor || !isNewEditorFlowEnabled) && (
|
|
1365
1534
|
<>
|
|
1366
1535
|
{hasAnyErrors(errorMessage) && (
|
|
1367
1536
|
<ErrorInfoNote
|