@capillarytech/creatives-library 8.0.125-alpha.6 → 8.0.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/app.js +6 -0
- package/containers/App/constants.js +0 -1
- package/containers/Email/index.js +5 -5
- package/containers/WeChat/RichmediaTemplates/Create/index.js +1 -1
- package/initialReducer.js +2 -0
- package/package.json +1 -1
- package/services/api.js +94 -1
- package/services/tests/api.test.js +191 -0
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +3 -8
- package/tests/integration/TemplateCreation/api-response.js +0 -5
- package/tests/integration/TemplateCreation/msw-handler.js +63 -42
- package/utils/common.js +0 -7
- package/utils/commonUtils.js +6 -2
- package/v2Components/CapImageUpload/index.js +45 -51
- package/v2Components/CapInAppCTA/index.js +0 -1
- package/v2Components/CapTagList/index.js +120 -177
- package/v2Components/CapVideoUpload/constants.js +0 -3
- package/v2Components/CapVideoUpload/index.js +110 -167
- package/v2Components/CapVideoUpload/messages.js +0 -16
- package/v2Components/Carousel/index.js +13 -15
- package/v2Components/CustomerSearchSection/_customerSearch.scss +309 -0
- package/v2Components/CustomerSearchSection/constants.js +5 -0
- package/v2Components/CustomerSearchSection/index.js +362 -0
- package/v2Components/CustomerSearchSection/messages.js +20 -0
- package/v2Components/CustomerSearchSection/tests/utils.test.js +334 -0
- package/v2Components/CustomerSearchSection/utils.js +49 -0
- package/v2Components/ErrorInfoNote/style.scss +0 -1
- package/v2Components/MobilePushPreviewV2/index.js +5 -37
- package/v2Components/TemplatePreview/_templatePreview.scss +72 -114
- package/v2Components/TemplatePreview/index.js +50 -178
- package/v2Components/TemplatePreview/messages.js +0 -4
- package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +543 -0
- package/v2Components/TestAndPreviewSlidebox/actions.js +67 -0
- package/v2Components/TestAndPreviewSlidebox/constants.js +67 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +771 -0
- package/v2Components/TestAndPreviewSlidebox/messages.js +147 -0
- package/v2Components/TestAndPreviewSlidebox/reducer.js +233 -0
- package/v2Components/TestAndPreviewSlidebox/sagas.js +258 -0
- package/v2Components/TestAndPreviewSlidebox/selectors.js +142 -0
- package/v2Components/TestAndPreviewSlidebox/tests/actions.test.js +80 -0
- package/v2Components/TestAndPreviewSlidebox/tests/reducer.test.js +367 -0
- package/v2Components/TestAndPreviewSlidebox/tests/saga.rtl.test.js +192 -0
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +652 -0
- package/v2Components/TestAndPreviewSlidebox/tests/selector.test.js +182 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +21 -9
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +23 -2
- package/v2Containers/CreativesContainer/index.js +160 -195
- package/v2Containers/CreativesContainer/messages.js +4 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +21 -0
- package/v2Containers/Email/index.js +18 -6
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +10 -0
- package/v2Containers/EmailWrapper/index.js +6 -0
- package/v2Containers/InApp/constants.js +0 -1
- package/v2Containers/InApp/index.js +13 -13
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
- package/v2Containers/MobilePush/Create/index.js +0 -1
- package/v2Containers/MobilePush/commonMethods.js +14 -7
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +23 -5
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
- package/v2Containers/TagList/index.js +10 -56
- package/v2Containers/Templates/_templates.scss +1 -101
- package/v2Containers/Templates/index.js +35 -147
- package/v2Containers/Templates/messages.js +0 -8
- package/v2Containers/Templates/sagas.js +0 -2
- package/v2Containers/WeChat/RichmediaTemplates/Create/index.js +1 -1
- package/v2Containers/Whatsapp/constants.js +0 -1
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +35 -0
- package/utils/createPayload.js +0 -270
- package/utils/tests/createPayload.test.js +0 -761
- package/v2Components/CapMpushCTA/constants.js +0 -25
- package/v2Components/CapMpushCTA/index.js +0 -332
- package/v2Components/CapMpushCTA/index.scss +0 -95
- package/v2Components/CapMpushCTA/messages.js +0 -89
- package/v2Components/TemplatePreview/assets/images/Android _ With date and time.svg +0 -29
- package/v2Components/TemplatePreview/assets/images/android.svg +0 -9
- package/v2Components/TemplatePreview/assets/images/iOS _ With date and time.svg +0 -26
- package/v2Components/TemplatePreview/assets/images/ios.svg +0 -9
- package/v2Containers/Email/tests/index.test.js +0 -35
- package/v2Containers/MobilePushNew/actions.js +0 -116
- package/v2Containers/MobilePushNew/components/CtaButtons.js +0 -170
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +0 -686
- package/v2Containers/MobilePushNew/components/PlatformContentFields.js +0 -279
- package/v2Containers/MobilePushNew/components/index.js +0 -5
- package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +0 -779
- package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +0 -2114
- package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +0 -343
- package/v2Containers/MobilePushNew/constants.js +0 -115
- package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +0 -1299
- package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +0 -1223
- package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +0 -246
- package/v2Containers/MobilePushNew/hooks/useUpload.js +0 -709
- package/v2Containers/MobilePushNew/index.js +0 -2170
- package/v2Containers/MobilePushNew/index.scss +0 -308
- package/v2Containers/MobilePushNew/messages.js +0 -226
- package/v2Containers/MobilePushNew/reducer.js +0 -160
- package/v2Containers/MobilePushNew/sagas.js +0 -198
- package/v2Containers/MobilePushNew/selectors.js +0 -55
- package/v2Containers/MobilePushNew/tests/reducer.test.js +0 -741
- package/v2Containers/MobilePushNew/tests/sagas.test.js +0 -863
- package/v2Containers/MobilePushNew/tests/selectors.test.js +0 -425
- package/v2Containers/MobilePushNew/tests/utils.test.js +0 -322
- package/v2Containers/MobilePushNew/utils.js +0 -33
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* TestAndPreviewSlidebox
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import PropTypes from 'prop-types';
|
|
8
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
9
|
+
import { convert } from "html-to-text";
|
|
10
|
+
import { createStructuredSelector } from 'reselect';
|
|
11
|
+
import { connect } from 'react-redux';
|
|
12
|
+
import isEmpty from 'lodash/isEmpty';
|
|
13
|
+
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
14
|
+
import { compose, bindActionCreators } from 'redux';
|
|
15
|
+
import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
|
|
16
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
17
|
+
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
18
|
+
import CapSwitch from '@capillarytech/cap-ui-library/CapSwitch';
|
|
19
|
+
import CapButton from '@capillarytech/cap-ui-library/CapButton';
|
|
20
|
+
import CapInput from '@capillarytech/cap-ui-library/CapInput';
|
|
21
|
+
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
22
|
+
import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
|
|
23
|
+
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
24
|
+
import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
|
|
25
|
+
import CapStepsAccordian from '@capillarytech/cap-ui-library/CapStepsAccordian';
|
|
26
|
+
import CapTreeSelect from '@capillarytech/cap-ui-library/CapTreeSelect';
|
|
27
|
+
import CapInfoNote from '@capillarytech/cap-ui-library/CapInfoNote';
|
|
28
|
+
import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
29
|
+
import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
|
|
30
|
+
import messages from './messages';
|
|
31
|
+
import injectReducer from '../../utils/injectReducer';
|
|
32
|
+
import injectSaga from '../../utils/injectSaga';
|
|
33
|
+
import * as previewAndTestActions from './actions';
|
|
34
|
+
import { previewAndTestSaga } from './sagas';
|
|
35
|
+
import './_testAndPreviewSlidebox.scss';
|
|
36
|
+
import reducer from './reducer';
|
|
37
|
+
import CustomerSearchSection from '../CustomerSearchSection';
|
|
38
|
+
import {
|
|
39
|
+
makeSelectPreviewAndTest,
|
|
40
|
+
makeSelectExtractedTags,
|
|
41
|
+
makeSelectIsExtractingTags,
|
|
42
|
+
makeSelectCustomers,
|
|
43
|
+
makeSelectIsSearchingCustomer,
|
|
44
|
+
makeSelectTags,
|
|
45
|
+
makeSelectPreviewData,
|
|
46
|
+
makeSelectIsUpdatingPreview,
|
|
47
|
+
makeSelectTestCustomers,
|
|
48
|
+
makeSelectIsFetchingTestCustomers,
|
|
49
|
+
makeSelectTestGroups,
|
|
50
|
+
makeSelectIsFetchingTestGroups,
|
|
51
|
+
makeSelectMessageMetaConfigId,
|
|
52
|
+
makeSelectPrefilledValues,
|
|
53
|
+
makeSelectIsSendingTestMessage,
|
|
54
|
+
} from './selectors';
|
|
55
|
+
import {
|
|
56
|
+
INITIAL_PAYLOAD, EMAIL, TEST, DESKTOP, ACTIVE, MOBILE,
|
|
57
|
+
} from './constants';
|
|
58
|
+
import { GLOBAL_CONVERT_OPTIONS } from '../FormBuilder/constants';
|
|
59
|
+
|
|
60
|
+
const TestAndPreviewSlidebox = (props) => {
|
|
61
|
+
const {
|
|
62
|
+
intl: { formatMessage },
|
|
63
|
+
show,
|
|
64
|
+
onClose,
|
|
65
|
+
formData,
|
|
66
|
+
content,
|
|
67
|
+
actions,
|
|
68
|
+
extractedTags,
|
|
69
|
+
isExtractingTags,
|
|
70
|
+
previewData,
|
|
71
|
+
isUpdatingPreview,
|
|
72
|
+
customers,
|
|
73
|
+
isSearchingCustomer,
|
|
74
|
+
testCustomers,
|
|
75
|
+
isFetchingTestCustomers,
|
|
76
|
+
testGroups,
|
|
77
|
+
isFetchingTestGroups,
|
|
78
|
+
messageMetaConfigId,
|
|
79
|
+
prefilledValues,
|
|
80
|
+
isSendingTestMessage,
|
|
81
|
+
} = props;
|
|
82
|
+
|
|
83
|
+
// State management
|
|
84
|
+
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
|
85
|
+
const [requiredTags, setRequiredTags] = useState([]);
|
|
86
|
+
const [optionalTags, setOptionalTags] = useState([]);
|
|
87
|
+
const [customValues, setCustomValues] = useState({});
|
|
88
|
+
const [showJSON, setShowJSON] = useState(false);
|
|
89
|
+
const [tagsExtracted, setTagsExtracted] = useState(false);
|
|
90
|
+
const [previewDevice, setPreviewDevice] = useState('desktop');
|
|
91
|
+
const [previewDataHtml, setPreviewDataHtml] = useState(previewData || '');
|
|
92
|
+
const [selectedTestEntities, setSelectedTestEntities] = useState([]);
|
|
93
|
+
|
|
94
|
+
const isUpdatePreviewDisabled = useMemo(() => (
|
|
95
|
+
requiredTags.some((tag) => !customValues[tag.fullPath])
|
|
96
|
+
), [requiredTags, customValues]);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (show) {
|
|
100
|
+
actions.fetchTestCustomersRequested();
|
|
101
|
+
actions.fetchTestGroupsRequested();
|
|
102
|
+
const payloadContent = convert(
|
|
103
|
+
content,
|
|
104
|
+
GLOBAL_CONVERT_OPTIONS
|
|
105
|
+
);
|
|
106
|
+
actions.extractTagsRequested(formData['template-subject'] || '', payloadContent);
|
|
107
|
+
}
|
|
108
|
+
}, [show]);
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (previewData) {
|
|
112
|
+
setPreviewDataHtml(previewData);
|
|
113
|
+
}
|
|
114
|
+
}, [previewData]);
|
|
115
|
+
|
|
116
|
+
// Listen for extract tags API result
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (extractedTags?.length > 0) {
|
|
119
|
+
// Categorize tags into required and optional
|
|
120
|
+
const required = [];
|
|
121
|
+
const optional = [];
|
|
122
|
+
|
|
123
|
+
const processTag = (tag, parentPath = '') => {
|
|
124
|
+
const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
|
|
125
|
+
|
|
126
|
+
if (tag?.metaData?.userDriven === false) {
|
|
127
|
+
required.push({
|
|
128
|
+
...tag,
|
|
129
|
+
fullPath: currentPath,
|
|
130
|
+
});
|
|
131
|
+
} else if (tag?.metaData?.userDriven === true) {
|
|
132
|
+
optional.push({
|
|
133
|
+
...tag,
|
|
134
|
+
fullPath: currentPath,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (tag?.children?.length > 0) {
|
|
139
|
+
tag.children.forEach((child) => processTag(child, currentPath));
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
extractedTags.forEach((tag) => processTag(tag));
|
|
144
|
+
|
|
145
|
+
setRequiredTags(required);
|
|
146
|
+
setOptionalTags(optional);
|
|
147
|
+
|
|
148
|
+
// Initialize custom values for required tags
|
|
149
|
+
const initialValues = {};
|
|
150
|
+
required.forEach((tag) => {
|
|
151
|
+
initialValues[tag.fullPath] = '';
|
|
152
|
+
});
|
|
153
|
+
optional.forEach((tag) => {
|
|
154
|
+
initialValues[tag.fullPath] = '';
|
|
155
|
+
});
|
|
156
|
+
setCustomValues(initialValues);
|
|
157
|
+
}
|
|
158
|
+
}, [extractedTags]);
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (tagsExtracted && selectedCustomer) {
|
|
162
|
+
const userDrivenTags = optionalTags?.map((tag) => tag.name);
|
|
163
|
+
if (userDrivenTags?.length > 0) {
|
|
164
|
+
const payload = {
|
|
165
|
+
channel: EMAIL,
|
|
166
|
+
messageTitle: formData['template-subject'],
|
|
167
|
+
messageBody: content,
|
|
168
|
+
resolvedTags: customValues,
|
|
169
|
+
userId: selectedCustomer?.customerId,
|
|
170
|
+
};
|
|
171
|
+
actions.getPrefilledValuesRequested(payload);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}, [selectedCustomer, tagsExtracted]);
|
|
175
|
+
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (prefilledValues && !isEmpty(prefilledValues)) {
|
|
178
|
+
setCustomValues((prev) => ({
|
|
179
|
+
...prev,
|
|
180
|
+
...prefilledValues,
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
}, [JSON.stringify(prefilledValues)]);
|
|
184
|
+
|
|
185
|
+
const testEntitiesTreeData = useMemo(() => {
|
|
186
|
+
const groupsNode = {
|
|
187
|
+
title: 'Groups',
|
|
188
|
+
value: 'groups-node',
|
|
189
|
+
children: testGroups?.map((group) => ({ title: group?.groupName, value: group?.groupId })),
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const customersNode = {
|
|
193
|
+
title: 'Individuals',
|
|
194
|
+
value: 'customers-node',
|
|
195
|
+
children: testCustomers?.map((customer) => ({ title: customer.name, value: customer?.userId })),
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return [groupsNode, customersNode];
|
|
199
|
+
}, [testCustomers, testGroups]);
|
|
200
|
+
|
|
201
|
+
// Handler to close the slidebox
|
|
202
|
+
const handleClose = () => {
|
|
203
|
+
// Reset state when closing
|
|
204
|
+
setSelectedCustomer(null);
|
|
205
|
+
setRequiredTags([]);
|
|
206
|
+
setOptionalTags([]);
|
|
207
|
+
setCustomValues({});
|
|
208
|
+
setShowJSON(false);
|
|
209
|
+
setTagsExtracted(false);
|
|
210
|
+
setPreviewDevice('desktop');
|
|
211
|
+
setPreviewDataHtml('');
|
|
212
|
+
setSelectedTestEntities([]);
|
|
213
|
+
actions.clearPrefilledValues();
|
|
214
|
+
if (onClose) {
|
|
215
|
+
onClose();
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const handleSearchCustomer = (query) => {
|
|
220
|
+
actions.searchCustomersRequested(query);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Handle customer selection from CustomerSearchSection
|
|
224
|
+
const handleCustomerSelect = (customer) => {
|
|
225
|
+
setSelectedCustomer(customer);
|
|
226
|
+
setCustomValues((prev) => {
|
|
227
|
+
const newValues = { ...prev };
|
|
228
|
+
optionalTags.forEach((tag) => {
|
|
229
|
+
delete newValues[tag.fullPath];
|
|
230
|
+
});
|
|
231
|
+
return newValues;
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const handleClearSelection = () => {
|
|
236
|
+
setSelectedCustomer(null);
|
|
237
|
+
setCustomValues((prev) => {
|
|
238
|
+
const newValues = { ...prev };
|
|
239
|
+
optionalTags.forEach((tag) => {
|
|
240
|
+
delete newValues[tag.fullPath];
|
|
241
|
+
});
|
|
242
|
+
return newValues;
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Handle custom value changes
|
|
247
|
+
const handleCustomValueChange = (tagPath, value) => {
|
|
248
|
+
setCustomValues((prev) => ({
|
|
249
|
+
...prev,
|
|
250
|
+
[tagPath]: value?.trim(),
|
|
251
|
+
}));
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Handle JSON text change
|
|
255
|
+
const handleJSONTextChange = (value) => {
|
|
256
|
+
try {
|
|
257
|
+
const parsedJSON = JSON.parse(value);
|
|
258
|
+
setCustomValues(parsedJSON);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
CapNotification.error({
|
|
261
|
+
message: formatMessage(messages.invalidJSON),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// Handle discard custom values
|
|
267
|
+
const handleDiscardCustomValues = () => {
|
|
268
|
+
const resetValues = {};
|
|
269
|
+
requiredTags.forEach((tag) => {
|
|
270
|
+
resetValues[tag?.fullPath] = '';
|
|
271
|
+
});
|
|
272
|
+
optionalTags.forEach((tag) => {
|
|
273
|
+
resetValues[tag?.fullPath] = '';
|
|
274
|
+
});
|
|
275
|
+
setCustomValues(resetValues);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Handle update preview
|
|
279
|
+
const handleUpdatePreview = () => {
|
|
280
|
+
const payload = {
|
|
281
|
+
channel: EMAIL,
|
|
282
|
+
messageTitle: formData['template-subject'],
|
|
283
|
+
messageBody: content,
|
|
284
|
+
resolvedTags: customValues,
|
|
285
|
+
userId: selectedCustomer?.customerId,
|
|
286
|
+
};
|
|
287
|
+
actions.updatePreviewRequested(payload);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// Handle extract tags button click
|
|
291
|
+
const handleExtractTags = () => {
|
|
292
|
+
setTagsExtracted(true);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const handleTestEntitiesChange = (value) => {
|
|
296
|
+
setSelectedTestEntities(value);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const handleSendTestMessage = () => {
|
|
300
|
+
const allUserIds = [];
|
|
301
|
+
selectedTestEntities.forEach((entityId) => {
|
|
302
|
+
const group = testGroups.find((g) => g.groupId === entityId);
|
|
303
|
+
if (group) {
|
|
304
|
+
allUserIds.push(...group.userIds);
|
|
305
|
+
} else {
|
|
306
|
+
allUserIds.push(entityId);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
const uniqueUserIds = [...new Set(allUserIds)];
|
|
310
|
+
actions.createMessageMetaRequested({
|
|
311
|
+
...INITIAL_PAYLOAD,
|
|
312
|
+
emailMessageContent: {
|
|
313
|
+
channel: EMAIL,
|
|
314
|
+
messageBody: previewData?.resolvedBody || content,
|
|
315
|
+
messageSubject: previewData?.resolvedTitle || formData['template-subject'],
|
|
316
|
+
},
|
|
317
|
+
}, messageMetaConfigId, (response) => {
|
|
318
|
+
const payload = {
|
|
319
|
+
messageMetaConfigId: response?.entity || messageMetaConfigId,
|
|
320
|
+
recipientDetails: uniqueUserIds,
|
|
321
|
+
resolvedTagValues: customValues,
|
|
322
|
+
clientId: 434,
|
|
323
|
+
communicationType: TEST,
|
|
324
|
+
};
|
|
325
|
+
actions.sendTestMessageRequested(payload, (result) => {
|
|
326
|
+
if (result) {
|
|
327
|
+
CapNotification.success({
|
|
328
|
+
message: formatMessage(messages.testMessageSent),
|
|
329
|
+
});
|
|
330
|
+
} else {
|
|
331
|
+
CapNotification.error({
|
|
332
|
+
message: formatMessage(messages.testMessageFailed),
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const renderLeftPanelContent = () => {
|
|
340
|
+
if (isExtractingTags) {
|
|
341
|
+
return (
|
|
342
|
+
<CapRow className="loading-container">
|
|
343
|
+
<CapSpin size="large" />
|
|
344
|
+
<CapRow className="loading-text">
|
|
345
|
+
<FormattedMessage {...messages.extractingTags} />
|
|
346
|
+
</CapRow>
|
|
347
|
+
</CapRow>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (extractedTags?.length > 0) {
|
|
351
|
+
return (
|
|
352
|
+
<>
|
|
353
|
+
{/* Customer Search Section */}
|
|
354
|
+
<CapRow className="panel-section customer-section">
|
|
355
|
+
<CapHeader size="label1" title={<FormattedMessage {...messages.customerSearchTitle} />} />
|
|
356
|
+
<CustomerSearchSection
|
|
357
|
+
selectedCustomer={selectedCustomer}
|
|
358
|
+
onCustomerSelect={handleCustomerSelect}
|
|
359
|
+
onSearch={handleSearchCustomer}
|
|
360
|
+
customers={customers}
|
|
361
|
+
isSearchingCustomer={isSearchingCustomer}
|
|
362
|
+
onClearSelection={handleClearSelection}
|
|
363
|
+
/>
|
|
364
|
+
</CapRow>
|
|
365
|
+
{/* Tags Section */}
|
|
366
|
+
{!tagsExtracted && (
|
|
367
|
+
<CapRow className="panel-section">
|
|
368
|
+
<CapButton type="flat" className="extract-tags-button" onClick={handleExtractTags}>
|
|
369
|
+
<CapLabel type="label33">
|
|
370
|
+
<FormattedMessage {...messages.enterCustomValuesForTags} />
|
|
371
|
+
</CapLabel>
|
|
372
|
+
</CapButton>
|
|
373
|
+
</CapRow>
|
|
374
|
+
)}
|
|
375
|
+
{tagsExtracted && renderCustomValuesEditor()}
|
|
376
|
+
</>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return (
|
|
380
|
+
<CapInfoNote
|
|
381
|
+
className="no-tags-extracted-info-note"
|
|
382
|
+
noteText={(
|
|
383
|
+
<CapLabel type="label31">
|
|
384
|
+
<FormattedMessage {...messages.noTagsExtracted} />
|
|
385
|
+
</CapLabel>
|
|
386
|
+
)}
|
|
387
|
+
/>
|
|
388
|
+
);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Header content for the slidebox
|
|
392
|
+
const slideboxHeader = (
|
|
393
|
+
<CapRow className="test-preview-header">
|
|
394
|
+
<CapIcon type="arrow-left" onClick={handleClose} className="back-icon" />
|
|
395
|
+
<CapHeader title={<FormattedMessage {...messages.testAndPreviewHeader} />} />
|
|
396
|
+
</CapRow>
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// Render custom values editor table
|
|
400
|
+
const renderCustomValuesEditor = () => {
|
|
401
|
+
if (isExtractingTags) {
|
|
402
|
+
return (
|
|
403
|
+
<CapRow className="loading-container">
|
|
404
|
+
<CapSpin size="large" />
|
|
405
|
+
<CapRow className="loading-text">
|
|
406
|
+
<FormattedMessage {...messages.extractingTags} />
|
|
407
|
+
</CapRow>
|
|
408
|
+
</CapRow>
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<CapRow className="custom-values-editor">
|
|
414
|
+
{isUpdatePreviewDisabled && (
|
|
415
|
+
<CapLabel type="label16" className="values-missing-message">
|
|
416
|
+
<FormattedMessage {...messages.valuesMissing} />
|
|
417
|
+
</CapLabel>
|
|
418
|
+
)}
|
|
419
|
+
<CapRow className="editor-header">
|
|
420
|
+
<CapRow className="json-toggle">
|
|
421
|
+
<span className="toggle-label">
|
|
422
|
+
<FormattedMessage {...messages.showJSON} />
|
|
423
|
+
</span>
|
|
424
|
+
<CapSwitch
|
|
425
|
+
checked={showJSON}
|
|
426
|
+
onChange={setShowJSON}
|
|
427
|
+
size="small"
|
|
428
|
+
/>
|
|
429
|
+
</CapRow>
|
|
430
|
+
</CapRow>
|
|
431
|
+
{showJSON ? (
|
|
432
|
+
<CapRow className="json-editor">
|
|
433
|
+
<CapRow className="json-editor-container">
|
|
434
|
+
<CapRow className="line-numbers">
|
|
435
|
+
{JSON.stringify(customValues, null, 2).split('\n').map((_, index) => (
|
|
436
|
+
<CapRow key={`line-${index + 1}`} className="line-number">
|
|
437
|
+
{index + 1}
|
|
438
|
+
</CapRow>
|
|
439
|
+
))}
|
|
440
|
+
</CapRow>
|
|
441
|
+
<textarea
|
|
442
|
+
className="json-textarea"
|
|
443
|
+
value={JSON.stringify(customValues, null, 2)}
|
|
444
|
+
onChange={(e) => handleJSONTextChange(e.target.value)}
|
|
445
|
+
placeholder={formatMessage(messages.enterValue)}
|
|
446
|
+
rows={Math.max(10, JSON.stringify(customValues, null, 2).split('\n').length)}
|
|
447
|
+
spellCheck={false}
|
|
448
|
+
/>
|
|
449
|
+
</CapRow>
|
|
450
|
+
</CapRow>
|
|
451
|
+
) : (
|
|
452
|
+
<>
|
|
453
|
+
{extractedTags?.length > 0 && (
|
|
454
|
+
<CapRow className="values-table">
|
|
455
|
+
<CapRow className="table-header">
|
|
456
|
+
<CapLabel type="label31" className="header-cell">
|
|
457
|
+
<FormattedMessage {...messages.personalizationTags} />
|
|
458
|
+
</CapLabel>
|
|
459
|
+
<CapLabel type="label31" className="header-cell">
|
|
460
|
+
<FormattedMessage {...messages.customValues} />
|
|
461
|
+
</CapLabel>
|
|
462
|
+
</CapRow>
|
|
463
|
+
{requiredTags.map((tag) => (
|
|
464
|
+
<CapRow key={tag.fullPath} className="value-row">
|
|
465
|
+
<CapRow className="tag-name">
|
|
466
|
+
{tag.fullPath}
|
|
467
|
+
<span className="required-tag-indicator">*</span>
|
|
468
|
+
</CapRow>
|
|
469
|
+
<CapRow className="tag-input">
|
|
470
|
+
<CapInput
|
|
471
|
+
type="text"
|
|
472
|
+
isRequired
|
|
473
|
+
className="tag-input-field"
|
|
474
|
+
value={customValues[tag.fullPath] || ''}
|
|
475
|
+
onChange={(e) => handleCustomValueChange(tag.fullPath, e.target.value)}
|
|
476
|
+
placeholder={formatMessage(messages.enterValue)}
|
|
477
|
+
size="small"
|
|
478
|
+
/>
|
|
479
|
+
</CapRow>
|
|
480
|
+
</CapRow>
|
|
481
|
+
))}
|
|
482
|
+
{optionalTags.map((tag) => (
|
|
483
|
+
<CapRow key={tag.fullPath} className="value-row">
|
|
484
|
+
<CapRow className="tag-name">{tag.fullPath}</CapRow>
|
|
485
|
+
<CapRow className="tag-input">
|
|
486
|
+
<CapInput
|
|
487
|
+
type="text"
|
|
488
|
+
className="tag-input-field"
|
|
489
|
+
value={customValues[tag.fullPath] || ''}
|
|
490
|
+
onChange={(e) => handleCustomValueChange(tag.fullPath, e.target.value)}
|
|
491
|
+
placeholder={formatMessage(messages.enterValue)}
|
|
492
|
+
size="small"
|
|
493
|
+
/>
|
|
494
|
+
</CapRow>
|
|
495
|
+
</CapRow>
|
|
496
|
+
))}
|
|
497
|
+
</CapRow>
|
|
498
|
+
)}
|
|
499
|
+
</>
|
|
500
|
+
)}
|
|
501
|
+
<div className="editor-actions">
|
|
502
|
+
<CapButton
|
|
503
|
+
className="discard-button"
|
|
504
|
+
type="flat"
|
|
505
|
+
size="small"
|
|
506
|
+
onClick={handleDiscardCustomValues}
|
|
507
|
+
icon="close"
|
|
508
|
+
>
|
|
509
|
+
<FormattedMessage {...messages.discardCustomValues} />
|
|
510
|
+
</CapButton>
|
|
511
|
+
<CapButton
|
|
512
|
+
type="primary"
|
|
513
|
+
size="small"
|
|
514
|
+
onClick={handleUpdatePreview}
|
|
515
|
+
loading={isUpdatingPreview}
|
|
516
|
+
disabled={isUpdatePreviewDisabled}
|
|
517
|
+
>
|
|
518
|
+
<FormattedMessage {...messages.updatePreview} />
|
|
519
|
+
</CapButton>
|
|
520
|
+
</div>
|
|
521
|
+
</CapRow>
|
|
522
|
+
);
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// Render send test message section
|
|
526
|
+
const renderSendTestMessage = () => (
|
|
527
|
+
<CapStepsAccordian
|
|
528
|
+
showNumberSteps={false}
|
|
529
|
+
isChevronIcon
|
|
530
|
+
expandIconPosition="right"
|
|
531
|
+
items={[
|
|
532
|
+
{
|
|
533
|
+
header: <CapHeader
|
|
534
|
+
size="regular"
|
|
535
|
+
description={<FormattedMessage {...messages.testMessageDescription} />}
|
|
536
|
+
title={<FormattedMessage {...messages.sendTestMessage} />}
|
|
537
|
+
/>,
|
|
538
|
+
content: (
|
|
539
|
+
<CapRow className="send-test-content">
|
|
540
|
+
<CapHeader size="label1" title={<FormattedMessage {...messages.testCustomers} />} />
|
|
541
|
+
<CapTreeSelect
|
|
542
|
+
className="test-customers-tree-select"
|
|
543
|
+
loading={isFetchingTestCustomers || isFetchingTestGroups}
|
|
544
|
+
treeData={testEntitiesTreeData}
|
|
545
|
+
onChange={handleTestEntitiesChange}
|
|
546
|
+
value={selectedTestEntities}
|
|
547
|
+
multiple
|
|
548
|
+
placeholder={formatMessage(messages.testCustomersPlaceholder)}
|
|
549
|
+
/>
|
|
550
|
+
<CapButton onClick={handleSendTestMessage} disabled={isEmpty(selectedTestEntities) || isEmpty(formData['template-subject']) || isSendingTestMessage}>
|
|
551
|
+
<FormattedMessage {...messages.sendTestButton} />
|
|
552
|
+
</CapButton>
|
|
553
|
+
</CapRow>),
|
|
554
|
+
key: 1,
|
|
555
|
+
},
|
|
556
|
+
]}
|
|
557
|
+
/>
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
const renderPreview = () => (
|
|
561
|
+
<CapRow className="preview-section panel-section">
|
|
562
|
+
<PreviewChrome
|
|
563
|
+
device={previewDevice}
|
|
564
|
+
onDeviceChange={setPreviewDevice}
|
|
565
|
+
customer={selectedCustomer}
|
|
566
|
+
subject={formData['template-subject']}
|
|
567
|
+
>
|
|
568
|
+
{isUpdatingPreview && (
|
|
569
|
+
<CapRow className="loading-container">
|
|
570
|
+
<CapSpin />
|
|
571
|
+
<CapRow className="loading-text">{formatMessage(messages.updatingPreview)}</CapRow>
|
|
572
|
+
</CapRow>
|
|
573
|
+
)}
|
|
574
|
+
{!isUpdatingPreview && previewDataHtml && (
|
|
575
|
+
<iframe
|
|
576
|
+
srcDoc={previewDataHtml?.resolvedBody}
|
|
577
|
+
title="Email Preview"
|
|
578
|
+
width="100%"
|
|
579
|
+
height="100%"
|
|
580
|
+
frameBorder="0"
|
|
581
|
+
/>
|
|
582
|
+
)}
|
|
583
|
+
{!isUpdatingPreview && !previewDataHtml && (
|
|
584
|
+
<iframe
|
|
585
|
+
srcDoc={content}
|
|
586
|
+
title="Email Preview"
|
|
587
|
+
width="100%"
|
|
588
|
+
height="100%"
|
|
589
|
+
frameBorder="0" />
|
|
590
|
+
)}
|
|
591
|
+
</PreviewChrome>
|
|
592
|
+
</CapRow>
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
return (
|
|
596
|
+
<CapSlideBox
|
|
597
|
+
className="test-and-preview-slidebox"
|
|
598
|
+
header={slideboxHeader}
|
|
599
|
+
handleClose={handleClose}
|
|
600
|
+
show={show}
|
|
601
|
+
size="size-xl"
|
|
602
|
+
content={(
|
|
603
|
+
<CapRow className="test-preview-container">
|
|
604
|
+
{(
|
|
605
|
+
<CapRow className="test-and-preview-panels">
|
|
606
|
+
{/* Left Panel */}
|
|
607
|
+
<CapRow className="left-panel">
|
|
608
|
+
{renderLeftPanelContent()}
|
|
609
|
+
<CapDivider className="panel-divider" />
|
|
610
|
+
{/* Send Test Message Section */}
|
|
611
|
+
{(
|
|
612
|
+
<CapRow className="panel-section send-test-section">
|
|
613
|
+
{renderSendTestMessage()}
|
|
614
|
+
</CapRow>
|
|
615
|
+
)}
|
|
616
|
+
<CapDivider className="panel-divider" />
|
|
617
|
+
</CapRow>
|
|
618
|
+
|
|
619
|
+
{/* Right Panel - Email Preview */}
|
|
620
|
+
<CapRow className="right-panel">
|
|
621
|
+
{renderPreview()}
|
|
622
|
+
</CapRow>
|
|
623
|
+
</CapRow>
|
|
624
|
+
)}
|
|
625
|
+
</CapRow>
|
|
626
|
+
)}
|
|
627
|
+
/>
|
|
628
|
+
);
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
const PreviewChrome = ({
|
|
632
|
+
device, onDeviceChange, subject, customer, children,
|
|
633
|
+
}) => (
|
|
634
|
+
<CapRow className="preview-chrome">
|
|
635
|
+
<div className="preview-header">
|
|
636
|
+
<CapRow type="flex" className="preview-for">
|
|
637
|
+
<CapLabel type="label1">
|
|
638
|
+
<FormattedMessage {...messages.previewFor} />
|
|
639
|
+
</CapLabel>
|
|
640
|
+
<CapLabel type="label2">
|
|
641
|
+
{customer ? customer.name : <FormattedMessage {...messages.defaultPreview} />}
|
|
642
|
+
</CapLabel>
|
|
643
|
+
</CapRow>
|
|
644
|
+
<CapRow className="device-toggle">
|
|
645
|
+
<CapIcon
|
|
646
|
+
type="desktop"
|
|
647
|
+
size="s"
|
|
648
|
+
className={device === DESKTOP ? ACTIVE : ''}
|
|
649
|
+
onClick={() => onDeviceChange('desktop')} />
|
|
650
|
+
<CapIcon
|
|
651
|
+
type="mobile"
|
|
652
|
+
className={device === MOBILE ? ACTIVE : ''}
|
|
653
|
+
onClick={() => onDeviceChange('mobile')} />
|
|
654
|
+
</CapRow>
|
|
655
|
+
</div>
|
|
656
|
+
<CapRow className={`preview-body ${device}`}>
|
|
657
|
+
<CapRow className="browser-bar">
|
|
658
|
+
<CapRow className="browser-controls">
|
|
659
|
+
<CapIcon type="chevron-left" />
|
|
660
|
+
<CapIcon type="chevron-right" />
|
|
661
|
+
<CapIcon className="refresh-icon" type="refreshCircle" size="s" />
|
|
662
|
+
</CapRow>
|
|
663
|
+
<CapRow className="address-bar">
|
|
664
|
+
<CapLabel type="label3" className="address-bar-label">
|
|
665
|
+
<FormattedMessage {...messages.browserAddressBar} />
|
|
666
|
+
</CapLabel>
|
|
667
|
+
<CapIcon className="browser-address-bar-icon" type="star" size="s" />
|
|
668
|
+
</CapRow>
|
|
669
|
+
</CapRow>
|
|
670
|
+
<CapDivider className="preview-divider" />
|
|
671
|
+
<CapRow className="email-header">
|
|
672
|
+
<CapIcon type="arrow-left" className="back-arrow" />
|
|
673
|
+
<CapLabel type="label17" className="email-subject">{subject}</CapLabel>
|
|
674
|
+
<CapRow className="email-meta">
|
|
675
|
+
<span><FormattedMessage {...messages.timeAgo} /></span>
|
|
676
|
+
<CapRow className="dots" />
|
|
677
|
+
<CapRow className="dots" />
|
|
678
|
+
<CapRow className="dots" />
|
|
679
|
+
</CapRow>
|
|
680
|
+
</CapRow>
|
|
681
|
+
<CapRow className="email-from">
|
|
682
|
+
<CapRow className="sender-avatar" />
|
|
683
|
+
<CapRow className="sender-info">
|
|
684
|
+
<CapRow className="sender-name"><FormattedMessage {...messages.senderName} /></CapRow>
|
|
685
|
+
<CapRow className="recipient-info">to me</CapRow>
|
|
686
|
+
</CapRow>
|
|
687
|
+
</CapRow>
|
|
688
|
+
<CapRow className="email-content">
|
|
689
|
+
{children}
|
|
690
|
+
</CapRow>
|
|
691
|
+
</CapRow>
|
|
692
|
+
</CapRow>
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
PreviewChrome.propTypes = {
|
|
696
|
+
device: PropTypes.string.isRequired,
|
|
697
|
+
onDeviceChange: PropTypes.func.isRequired,
|
|
698
|
+
subject: PropTypes.string,
|
|
699
|
+
customer: PropTypes.object.isRequired,
|
|
700
|
+
children: PropTypes.node.isRequired,
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
PreviewChrome.defaultProps = {
|
|
704
|
+
subject: 'Welcome email',
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
TestAndPreviewSlidebox.propTypes = {
|
|
708
|
+
show: PropTypes.bool.isRequired,
|
|
709
|
+
onClose: PropTypes.func.isRequired,
|
|
710
|
+
formData: PropTypes.object,
|
|
711
|
+
currentChannel: PropTypes.string,
|
|
712
|
+
actions: PropTypes.object.isRequired,
|
|
713
|
+
previewData: PropTypes.object.isRequired,
|
|
714
|
+
isUpdatingPreview: PropTypes.bool.isRequired,
|
|
715
|
+
content: PropTypes.string.isRequired,
|
|
716
|
+
extractedTags: PropTypes.array.isRequired,
|
|
717
|
+
isExtractingTags: PropTypes.bool.isRequired,
|
|
718
|
+
customers: PropTypes.array.isRequired,
|
|
719
|
+
isSearchingCustomer: PropTypes.bool.isRequired,
|
|
720
|
+
intl: PropTypes.object.isRequired,
|
|
721
|
+
testCustomers: PropTypes.array.isRequired,
|
|
722
|
+
isFetchingTestCustomers: PropTypes.bool.isRequired,
|
|
723
|
+
testGroups: PropTypes.array.isRequired,
|
|
724
|
+
isFetchingTestGroups: PropTypes.bool.isRequired,
|
|
725
|
+
messageMetaConfigId: PropTypes.string,
|
|
726
|
+
prefilledValues: PropTypes.object,
|
|
727
|
+
isSendingTestMessage: PropTypes.bool.isRequired,
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
TestAndPreviewSlidebox.defaultProps = {
|
|
731
|
+
formData: null,
|
|
732
|
+
currentChannel: EMAIL,
|
|
733
|
+
messageMetaConfigId: null,
|
|
734
|
+
prefilledValues: {},
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
// Redux connection
|
|
738
|
+
const mapStateToProps = createStructuredSelector({
|
|
739
|
+
previewAndTest: makeSelectPreviewAndTest(),
|
|
740
|
+
extractedTags: makeSelectExtractedTags(),
|
|
741
|
+
isExtractingTags: makeSelectIsExtractingTags(),
|
|
742
|
+
customers: makeSelectCustomers(),
|
|
743
|
+
tags: makeSelectTags(),
|
|
744
|
+
previewData: makeSelectPreviewData(),
|
|
745
|
+
isUpdatingPreview: makeSelectIsUpdatingPreview(),
|
|
746
|
+
isSearchingCustomer: makeSelectIsSearchingCustomer(),
|
|
747
|
+
testCustomers: makeSelectTestCustomers(),
|
|
748
|
+
isFetchingTestCustomers: makeSelectIsFetchingTestCustomers(),
|
|
749
|
+
testGroups: makeSelectTestGroups(),
|
|
750
|
+
isFetchingTestGroups: makeSelectIsFetchingTestGroups(),
|
|
751
|
+
messageMetaConfigId: makeSelectMessageMetaConfigId(),
|
|
752
|
+
prefilledValues: makeSelectPrefilledValues(),
|
|
753
|
+
isSendingTestMessage: makeSelectIsSendingTestMessage(),
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
function mapDispatchToProps(dispatch) {
|
|
757
|
+
return {
|
|
758
|
+
actions: bindActionCreators(previewAndTestActions, dispatch),
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const withReducer = injectReducer({ key: 'previewAndTest', reducer });
|
|
763
|
+
const withSaga = injectSaga({ key: 'previewAndTest', saga: previewAndTestSaga, mode: DAEMON });
|
|
764
|
+
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
765
|
+
|
|
766
|
+
export default compose(
|
|
767
|
+
withReducer,
|
|
768
|
+
withSaga,
|
|
769
|
+
withConnect,
|
|
770
|
+
injectIntl,
|
|
771
|
+
)(TestAndPreviewSlidebox);
|