@capillarytech/creatives-library 8.0.340-beta.0.10 → 8.0.340-beta.0.11
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/package.json +1 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +16 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +169 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +54 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +53 -7
- package/v2Components/CommonTestAndPreview/constants.js +2 -0
- package/v2Components/CommonTestAndPreview/index.js +51 -2
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +163 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +522 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +255 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +2 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +194 -0
- package/v2Components/FormBuilder/index.js +162 -52
- package/v2Components/TestAndPreviewSlidebox/index.js +2 -2
- package/v2Containers/App/constants.js +3 -0
- package/v2Containers/App/tests/constants.test.js +61 -0
- package/v2Containers/CreativesContainer/index.js +60 -24
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +24 -36
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -9
- package/v2Containers/Templates/index.js +72 -2
- package/v2Containers/Templates/tests/webpush.test.js +375 -0
- package/v2Containers/WebPush/Create/index.js +91 -8
- package/v2Containers/WebPush/Create/index.scss +9 -0
- package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +348 -0
- package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +325 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +68 -102
package/package.json
CHANGED
|
@@ -23,6 +23,7 @@ const PreviewHeader = ({
|
|
|
23
23
|
showDeviceToggle,
|
|
24
24
|
onDeviceChange,
|
|
25
25
|
channel,
|
|
26
|
+
setIsFullscreenOpen,
|
|
26
27
|
}) => {
|
|
27
28
|
// Determine if this is SMS, WhatsApp, RCS, InApp, MobilePush, or Viber channel (uses Android/iOS) or other channels (uses Desktop/Mobile)
|
|
28
29
|
const isSmsChannel = channel === CHANNELS.SMS;
|
|
@@ -31,8 +32,13 @@ const PreviewHeader = ({
|
|
|
31
32
|
const isInAppChannel = channel === CHANNELS.INAPP;
|
|
32
33
|
const isMobilePushChannel = channel === CHANNELS.MOBILEPUSH;
|
|
33
34
|
const isViberChannel = channel === CHANNELS.VIBER;
|
|
35
|
+
const isWebPushChannel = channel === CHANNELS.WEBPUSH;
|
|
34
36
|
const isAndroidIosToggle = isSmsChannel || isWhatsappChannel || isRcsChannel || isInAppChannel || isMobilePushChannel || isViberChannel;
|
|
35
37
|
|
|
38
|
+
const handleOpenFullscreen = () => {
|
|
39
|
+
setIsFullscreenOpen(true);
|
|
40
|
+
};
|
|
41
|
+
|
|
36
42
|
return (
|
|
37
43
|
<CapRow className="preview-chrome">
|
|
38
44
|
<div className="preview-header">
|
|
@@ -80,6 +86,12 @@ const PreviewHeader = ({
|
|
|
80
86
|
)}
|
|
81
87
|
</CapRow>
|
|
82
88
|
)}
|
|
89
|
+
{isWebPushChannel && (
|
|
90
|
+
<CapIcon
|
|
91
|
+
type="expander"
|
|
92
|
+
onClick={() => handleOpenFullscreen()}
|
|
93
|
+
/>
|
|
94
|
+
)}
|
|
83
95
|
</div>
|
|
84
96
|
</CapRow>
|
|
85
97
|
);
|
|
@@ -95,6 +107,8 @@ PreviewHeader.propTypes = {
|
|
|
95
107
|
showDeviceToggle: PropTypes.bool,
|
|
96
108
|
onDeviceChange: PropTypes.func,
|
|
97
109
|
channel: PropTypes.string,
|
|
110
|
+
isFullscreenOpen: PropTypes.bool,
|
|
111
|
+
setIsFullscreenOpen: PropTypes.func,
|
|
98
112
|
};
|
|
99
113
|
|
|
100
114
|
PreviewHeader.defaultProps = {
|
|
@@ -103,6 +117,8 @@ PreviewHeader.defaultProps = {
|
|
|
103
117
|
showDeviceToggle: false,
|
|
104
118
|
onDeviceChange: () => {},
|
|
105
119
|
channel: null,
|
|
120
|
+
isFullscreenOpen: false,
|
|
121
|
+
setIsFullscreenOpen: () => {},
|
|
106
122
|
};
|
|
107
123
|
|
|
108
124
|
export default PreviewHeader;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
5
|
+
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
6
|
+
import CapModal from '@capillarytech/cap-ui-library/CapModal';
|
|
7
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
8
|
+
import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
|
|
9
|
+
import PreviewControls from '../../../v2Containers/WebPush/Create/preview/PreviewControls';
|
|
10
|
+
import PreviewContent from '../../../v2Containers/WebPush/Create/preview/PreviewContent';
|
|
11
|
+
import DevicePreviewContent from '../../../v2Containers/WebPush/Create/preview/DevicePreviewContent';
|
|
12
|
+
import {
|
|
13
|
+
OS_OPTIONS,
|
|
14
|
+
DEFAULT_OS,
|
|
15
|
+
DEFAULT_BROWSER,
|
|
16
|
+
} from '../../../v2Containers/WebPush/Create/preview/constants';
|
|
17
|
+
import { getBrowserOptionsForOS } from '../../../v2Containers/WebPush/Create/preview/config/notificationMappings';
|
|
18
|
+
import messages from '../messages';
|
|
19
|
+
|
|
20
|
+
const WebPushPreviewContent = ({
|
|
21
|
+
notificationTitle,
|
|
22
|
+
notificationBody,
|
|
23
|
+
imageSrc,
|
|
24
|
+
brandIconSrc,
|
|
25
|
+
buttons,
|
|
26
|
+
url,
|
|
27
|
+
isUpdating,
|
|
28
|
+
error,
|
|
29
|
+
isFullscreenOpen,
|
|
30
|
+
setIsFullscreenOpen,
|
|
31
|
+
selectedCustomer,
|
|
32
|
+
}) => {
|
|
33
|
+
const [selectedOS, setSelectedOS] = useState(DEFAULT_OS);
|
|
34
|
+
const [selectedBrowser, setSelectedBrowser] = useState(DEFAULT_BROWSER);
|
|
35
|
+
|
|
36
|
+
const browserOptionsForOS = getBrowserOptionsForOS(selectedOS);
|
|
37
|
+
|
|
38
|
+
// Coerce browser when OS change removes current browser from available options
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const isValid = browserOptionsForOS.some((opt) => opt.value === selectedBrowser);
|
|
41
|
+
if (!isValid && browserOptionsForOS.length) {
|
|
42
|
+
setSelectedBrowser(browserOptionsForOS[0].value);
|
|
43
|
+
}
|
|
44
|
+
}, [browserOptionsForOS, selectedBrowser]);
|
|
45
|
+
|
|
46
|
+
const handleOSChange = (value) => {
|
|
47
|
+
setSelectedOS(value);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleBrowserChange = (value) => {
|
|
51
|
+
setSelectedBrowser(value);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleCloseFullscreen = () => {
|
|
55
|
+
setIsFullscreenOpen(false);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (isUpdating) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (error) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<CapRow className="webpush-test-preview-container webpush-preview-container">
|
|
68
|
+
<CapRow className="webpush-preview-panel">
|
|
69
|
+
<PreviewControls
|
|
70
|
+
selectedOS={selectedOS}
|
|
71
|
+
selectedBrowser={selectedBrowser}
|
|
72
|
+
osOptions={OS_OPTIONS}
|
|
73
|
+
browserOptions={browserOptionsForOS}
|
|
74
|
+
onOSChange={handleOSChange}
|
|
75
|
+
onBrowserChange={handleBrowserChange}
|
|
76
|
+
layoutMode="newRow"
|
|
77
|
+
showStateDropdown={false}
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
<PreviewContent
|
|
81
|
+
notificationTitle={notificationTitle}
|
|
82
|
+
notificationBody={notificationBody}
|
|
83
|
+
url={url}
|
|
84
|
+
selectedOS={selectedOS}
|
|
85
|
+
selectedBrowser={selectedBrowser}
|
|
86
|
+
notificationState="Collapsed"
|
|
87
|
+
imageSrc={imageSrc}
|
|
88
|
+
brandIconSrc={brandIconSrc}
|
|
89
|
+
buttons={buttons}
|
|
90
|
+
/>
|
|
91
|
+
</CapRow>
|
|
92
|
+
|
|
93
|
+
<CapModal
|
|
94
|
+
visible={isFullscreenOpen}
|
|
95
|
+
onCancel={handleCloseFullscreen}
|
|
96
|
+
width="90%"
|
|
97
|
+
centered
|
|
98
|
+
closable={false}
|
|
99
|
+
footer={null}
|
|
100
|
+
title={null}
|
|
101
|
+
maskClosable
|
|
102
|
+
className="webpush-fullscreen-modal webpush-preview-container"
|
|
103
|
+
>
|
|
104
|
+
<CapRow className="webpush-preview-header">
|
|
105
|
+
<div className="webpush-heading-container">
|
|
106
|
+
<CapRow type="flex" className="preview-for">
|
|
107
|
+
<CapLabel type="label16">
|
|
108
|
+
<FormattedMessage {...messages.previewFor} />
|
|
109
|
+
</CapLabel>
|
|
110
|
+
<CapLabel type="label2">
|
|
111
|
+
{selectedCustomer ? selectedCustomer.name : <FormattedMessage {...messages.defaultPreview} />}
|
|
112
|
+
</CapLabel>
|
|
113
|
+
</CapRow>
|
|
114
|
+
<CapIcon
|
|
115
|
+
type="collapse2"
|
|
116
|
+
onClick={() => handleCloseFullscreen()}
|
|
117
|
+
className="webpush-fullscreen-close-icon"
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
</CapRow>
|
|
121
|
+
<CapDivider className="webpush-fullscreen-divider" />
|
|
122
|
+
<DevicePreviewContent
|
|
123
|
+
notificationTitle={notificationTitle}
|
|
124
|
+
notificationBody={notificationBody}
|
|
125
|
+
url={url}
|
|
126
|
+
imageSrc={imageSrc}
|
|
127
|
+
brandIconSrc={brandIconSrc}
|
|
128
|
+
buttons={buttons}
|
|
129
|
+
/>
|
|
130
|
+
</CapModal>
|
|
131
|
+
</CapRow>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
WebPushPreviewContent.propTypes = {
|
|
136
|
+
notificationTitle: PropTypes.string,
|
|
137
|
+
notificationBody: PropTypes.string,
|
|
138
|
+
imageSrc: PropTypes.string,
|
|
139
|
+
brandIconSrc: PropTypes.string,
|
|
140
|
+
buttons: PropTypes.arrayOf(
|
|
141
|
+
PropTypes.shape({
|
|
142
|
+
text: PropTypes.string,
|
|
143
|
+
url: PropTypes.string,
|
|
144
|
+
type: PropTypes.string,
|
|
145
|
+
})
|
|
146
|
+
),
|
|
147
|
+
url: PropTypes.string,
|
|
148
|
+
isUpdating: PropTypes.bool,
|
|
149
|
+
error: PropTypes.string,
|
|
150
|
+
isFullscreenOpen: PropTypes.bool,
|
|
151
|
+
setIsFullscreenOpen: PropTypes.func,
|
|
152
|
+
selectedCustomer: PropTypes.object,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
WebPushPreviewContent.defaultProps = {
|
|
156
|
+
notificationTitle: '',
|
|
157
|
+
notificationBody: '',
|
|
158
|
+
imageSrc: '',
|
|
159
|
+
brandIconSrc: '',
|
|
160
|
+
buttons: [],
|
|
161
|
+
url: '',
|
|
162
|
+
isUpdating: false,
|
|
163
|
+
error: null,
|
|
164
|
+
isFullscreenOpen: false,
|
|
165
|
+
setIsFullscreenOpen: () => {},
|
|
166
|
+
selectedCustomer: null,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export default WebPushPreviewContent;
|
|
@@ -2348,6 +2348,60 @@
|
|
|
2348
2348
|
}
|
|
2349
2349
|
}
|
|
2350
2350
|
|
|
2351
|
+
// WebPush Test & Preview Styles
|
|
2352
|
+
.webpush-test-preview-container {
|
|
2353
|
+
width: 100%;
|
|
2354
|
+
position: relative;
|
|
2355
|
+
padding-left: $CAP_SPACE_16;
|
|
2356
|
+
padding-right: $CAP_SPACE_16;
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
.webpush-preview-panel {
|
|
2360
|
+
position: relative;
|
|
2361
|
+
width: 100%;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
.webpush-fullscreen-modal {
|
|
2365
|
+
.webpush-fullscreen-divider {
|
|
2366
|
+
margin-top: 0;
|
|
2367
|
+
margin-bottom: $CAP_SPACE_16;
|
|
2368
|
+
}
|
|
2369
|
+
.ant-modal.cap-modal-v2 {
|
|
2370
|
+
width: 90%;
|
|
2371
|
+
max-width: 100%;
|
|
2372
|
+
margin-top: $CAP_SPACE_40;
|
|
2373
|
+
}
|
|
2374
|
+
// Preview Chrome wrapper (matches old TestAndPreviewSlidebox design)
|
|
2375
|
+
.webpush-preview-header {
|
|
2376
|
+
background: $CAP_WHITE;
|
|
2377
|
+
overflow: hidden;
|
|
2378
|
+
|
|
2379
|
+
.preview-divider {
|
|
2380
|
+
margin: 0;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
.webpush-heading-container {
|
|
2384
|
+
display: flex;
|
|
2385
|
+
justify-content: space-between;
|
|
2386
|
+
align-items: center;
|
|
2387
|
+
padding: $CAP_SPACE_16 0 $CAP_SPACE_16 0;
|
|
2388
|
+
|
|
2389
|
+
.preview-for {
|
|
2390
|
+
gap: $CAP_SPACE_04;
|
|
2391
|
+
align-items: center;
|
|
2392
|
+
b {
|
|
2393
|
+
margin-left: $CAP_SPACE_08;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
.webpush-fullscreen-close-icon {
|
|
2398
|
+
width: $CAP_SPACE_24;
|
|
2399
|
+
height: $CAP_SPACE_24;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2351
2405
|
// Responsive adjustments
|
|
2352
2406
|
@media (max-width: 85.714rem) {
|
|
2353
2407
|
.unified-preview {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Routes to channel-specific preview content components
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import React from 'react';
|
|
9
|
+
import React, { useState } from 'react';
|
|
10
10
|
import PropTypes from 'prop-types';
|
|
11
11
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
12
12
|
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
@@ -21,6 +21,7 @@ import InAppPreviewContent from './InAppPreviewContent';
|
|
|
21
21
|
import MobilePushPreviewContent from './MobilePushPreviewContent';
|
|
22
22
|
import ViberPreviewContent from './ViberPreviewContent';
|
|
23
23
|
import ZaloPreviewContent from './ZaloPreviewContent';
|
|
24
|
+
import WebPushPreviewContent from './WebPushPreviewContent';
|
|
24
25
|
import { CHANNELS, DESKTOP, TABLET, MOBILE, ANDROID, IOS } from '../constants';
|
|
25
26
|
import messages from '../messages';
|
|
26
27
|
import './_unifiedPreview.scss';
|
|
@@ -44,6 +45,7 @@ const UnifiedPreview = ({
|
|
|
44
45
|
* For Phase 5, we'll render placeholders
|
|
45
46
|
* Phase 6+ will implement actual content components
|
|
46
47
|
*/
|
|
48
|
+
const [isFullscreenOpen, setIsFullscreenOpen] = useState(false);
|
|
47
49
|
const renderChannelContent = () => {
|
|
48
50
|
// Phase 5: Placeholder content for all channels
|
|
49
51
|
// Phase 6+: Import and render actual channel-specific components
|
|
@@ -197,6 +199,45 @@ const UnifiedPreview = ({
|
|
|
197
199
|
);
|
|
198
200
|
}
|
|
199
201
|
|
|
202
|
+
case CHANNELS.WEBPUSH: {
|
|
203
|
+
// WebPush content arrives as a JSON string (from getCurrentContent or preview API response)
|
|
204
|
+
let webPushOuter = {};
|
|
205
|
+
try {
|
|
206
|
+
webPushOuter = typeof content === 'string' ? JSON.parse(content) : (typeof content === 'object' && content !== null ? content : {});
|
|
207
|
+
} catch (e) {
|
|
208
|
+
webPushOuter = {};
|
|
209
|
+
}
|
|
210
|
+
const { content: webPushInner = {} } = webPushOuter;
|
|
211
|
+
const {
|
|
212
|
+
title,
|
|
213
|
+
message,
|
|
214
|
+
iconImageUrl,
|
|
215
|
+
cta,
|
|
216
|
+
expandableDetails = {},
|
|
217
|
+
} = webPushInner;
|
|
218
|
+
const { media: webPushMedia = [], ctas: webPushCtas = [] } = expandableDetails;
|
|
219
|
+
const webPushButtons = webPushCtas.map(({ title: text = '', actionLink: url = '', type = '' }) => ({
|
|
220
|
+
text,
|
|
221
|
+
url,
|
|
222
|
+
type,
|
|
223
|
+
}));
|
|
224
|
+
return (
|
|
225
|
+
<WebPushPreviewContent
|
|
226
|
+
notificationTitle={title || ''}
|
|
227
|
+
notificationBody={message || ''}
|
|
228
|
+
imageSrc={webPushMedia[0]?.url || ''}
|
|
229
|
+
brandIconSrc={iconImageUrl || ''}
|
|
230
|
+
buttons={webPushButtons}
|
|
231
|
+
url={cta?.actionLink || ''}
|
|
232
|
+
isUpdating={isUpdating}
|
|
233
|
+
error={error}
|
|
234
|
+
isFullscreenOpen={isFullscreenOpen}
|
|
235
|
+
setIsFullscreenOpen={setIsFullscreenOpen}
|
|
236
|
+
selectedCustomer={selectedCustomer}
|
|
237
|
+
/>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
200
241
|
default:
|
|
201
242
|
return (
|
|
202
243
|
<div className="channel-preview-placeholder">
|
|
@@ -212,6 +253,8 @@ const UnifiedPreview = ({
|
|
|
212
253
|
}
|
|
213
254
|
};
|
|
214
255
|
|
|
256
|
+
const showToggle = channel !== CHANNELS.WEBPUSH && showDeviceToggle;
|
|
257
|
+
|
|
215
258
|
/**
|
|
216
259
|
* Render loading state for all channels
|
|
217
260
|
*/
|
|
@@ -222,9 +265,10 @@ const UnifiedPreview = ({
|
|
|
222
265
|
<PreviewHeader
|
|
223
266
|
selectedCustomer={selectedCustomer}
|
|
224
267
|
device={device}
|
|
225
|
-
showDeviceToggle={
|
|
268
|
+
showDeviceToggle={showToggle}
|
|
226
269
|
onDeviceChange={onDeviceChange}
|
|
227
270
|
channel={channel}
|
|
271
|
+
setIsFullscreenOpen={setIsFullscreenOpen}
|
|
228
272
|
/>
|
|
229
273
|
)}
|
|
230
274
|
<CapRow className="preview-loading-container">
|
|
@@ -247,9 +291,10 @@ const UnifiedPreview = ({
|
|
|
247
291
|
<PreviewHeader
|
|
248
292
|
selectedCustomer={selectedCustomer}
|
|
249
293
|
device={device}
|
|
250
|
-
showDeviceToggle={
|
|
294
|
+
showDeviceToggle={showToggle}
|
|
251
295
|
onDeviceChange={onDeviceChange}
|
|
252
296
|
channel={channel}
|
|
297
|
+
setIsFullscreenOpen={setIsFullscreenOpen}
|
|
253
298
|
/>
|
|
254
299
|
)}
|
|
255
300
|
<CapRow className="preview-error-container">
|
|
@@ -275,15 +320,16 @@ const UnifiedPreview = ({
|
|
|
275
320
|
<PreviewHeader
|
|
276
321
|
selectedCustomer={selectedCustomer}
|
|
277
322
|
device={device}
|
|
278
|
-
showDeviceToggle={
|
|
323
|
+
showDeviceToggle={showToggle}
|
|
279
324
|
onDeviceChange={onDeviceChange}
|
|
280
325
|
channel={channel}
|
|
326
|
+
setIsFullscreenOpen={setIsFullscreenOpen}
|
|
281
327
|
/>
|
|
282
328
|
)}
|
|
283
329
|
|
|
284
330
|
{/* Channel-specific preview content */}
|
|
285
|
-
<CapRow
|
|
286
|
-
{[CHANNELS.EMAIL, CHANNELS.SMS, CHANNELS.WHATSAPP, CHANNELS.RCS, CHANNELS.INAPP, CHANNELS.MOBILEPUSH, CHANNELS.VIBER].includes(channel) ? (
|
|
331
|
+
<CapRow className={`preview-content-container ${!showHeader ? 'preview-content-container-no-header' : ''}`}>
|
|
332
|
+
{[CHANNELS.EMAIL, CHANNELS.SMS, CHANNELS.WHATSAPP, CHANNELS.RCS, CHANNELS.INAPP, CHANNELS.MOBILEPUSH, CHANNELS.VIBER, CHANNELS.WEBPUSH].includes(channel) ? (
|
|
287
333
|
renderChannelContent()
|
|
288
334
|
) : (
|
|
289
335
|
<DeviceFrame device={device || DESKTOP}>
|
|
@@ -319,7 +365,7 @@ const UnifiedPreview = ({
|
|
|
319
365
|
UnifiedPreview.propTypes = {
|
|
320
366
|
// Core
|
|
321
367
|
channel: PropTypes.oneOf(Object.values(CHANNELS)).isRequired,
|
|
322
|
-
content: PropTypes.object.isRequired,
|
|
368
|
+
content: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
|
|
323
369
|
|
|
324
370
|
// Display options
|
|
325
371
|
device: PropTypes.oneOf([DESKTOP, TABLET, MOBILE, ANDROID, IOS]),
|
|
@@ -85,6 +85,7 @@ export const CHANNELS = {
|
|
|
85
85
|
MOBILEPUSH: 'MOBILEPUSH',
|
|
86
86
|
VIBER: 'VIBER',
|
|
87
87
|
ZALO: 'ZALO',
|
|
88
|
+
WEBPUSH: 'WEBPUSH',
|
|
88
89
|
};
|
|
89
90
|
export const CHANNELS_USING_ANDROID_PREVIEW_DEVICE = [CHANNELS.SMS, CHANNELS.RCS, CHANNELS.WHATSAPP, CHANNELS.VIBER, CHANNELS.ZALO, CHANNELS.INAPP, CHANNELS.MOBILEPUSH];
|
|
90
91
|
|
|
@@ -203,6 +204,7 @@ export const DYNAMIC_URL = 'DYNAMIC_URL';
|
|
|
203
204
|
export const IMAGE = 'IMAGE';
|
|
204
205
|
export const VIDEO = 'VIDEO';
|
|
205
206
|
export const URL = 'URL';
|
|
207
|
+
export const DAYS = 'DAYS';
|
|
206
208
|
|
|
207
209
|
// Initial Payload Template (for reference)
|
|
208
210
|
export const INITIAL_PAYLOAD = {
|
|
@@ -81,7 +81,8 @@ import {
|
|
|
81
81
|
IMAGE,
|
|
82
82
|
VIDEO,
|
|
83
83
|
URL,
|
|
84
|
-
CHANNELS_USING_ANDROID_PREVIEW_DEVICE
|
|
84
|
+
CHANNELS_USING_ANDROID_PREVIEW_DEVICE,
|
|
85
|
+
DAYS,
|
|
85
86
|
} from './constants';
|
|
86
87
|
|
|
87
88
|
// Import utilities
|
|
@@ -610,6 +611,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
610
611
|
messageBody: contentStr,
|
|
611
612
|
};
|
|
612
613
|
|
|
614
|
+
case CHANNELS.WEBPUSH:
|
|
615
|
+
return {
|
|
616
|
+
...basePayload,
|
|
617
|
+
messageBody: contentStr,
|
|
618
|
+
};
|
|
619
|
+
|
|
613
620
|
default:
|
|
614
621
|
return basePayload;
|
|
615
622
|
}
|
|
@@ -662,6 +669,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
662
669
|
templateContent: contentStr,
|
|
663
670
|
};
|
|
664
671
|
|
|
672
|
+
case CHANNELS.WEBPUSH:
|
|
673
|
+
return {
|
|
674
|
+
templateSubject: formDataObj?.content?.title || '',
|
|
675
|
+
templateContent: contentStr,
|
|
676
|
+
};
|
|
677
|
+
|
|
665
678
|
case CHANNELS.ZALO: {
|
|
666
679
|
// For Zalo, extract content from templateListParams array
|
|
667
680
|
// Combine all variable values into a single string for tag extraction
|
|
@@ -1801,6 +1814,42 @@ const CommonTestAndPreview = (props) => {
|
|
|
1801
1814
|
};
|
|
1802
1815
|
}
|
|
1803
1816
|
|
|
1817
|
+
case CHANNELS.WEBPUSH: {
|
|
1818
|
+
const webpushData = (typeof formDataObj === 'object' && formDataObj !== null)
|
|
1819
|
+
? formDataObj
|
|
1820
|
+
: {};
|
|
1821
|
+
const innerContent = webpushData?.content || {};
|
|
1822
|
+
|
|
1823
|
+
const resolvedTitle = resolveTagsInText(innerContent?.title || '', customValuesObj);
|
|
1824
|
+
const resolvedMessage = resolveTagsInText(innerContent?.message || '', customValuesObj);
|
|
1825
|
+
|
|
1826
|
+
return {
|
|
1827
|
+
...basePayload,
|
|
1828
|
+
webPushMessageContent: {
|
|
1829
|
+
channel: CHANNELS.WEBPUSH,
|
|
1830
|
+
accountId: webpushData?.accountId || null,
|
|
1831
|
+
content: {
|
|
1832
|
+
title: resolvedTitle,
|
|
1833
|
+
message: resolvedMessage,
|
|
1834
|
+
...(innerContent?.iconImageUrl && { iconImageUrl: innerContent.iconImageUrl }),
|
|
1835
|
+
...(innerContent?.cta && { cta: innerContent.cta }),
|
|
1836
|
+
...(innerContent?.expandableDetails && { expandableDetails: innerContent.expandableDetails }),
|
|
1837
|
+
},
|
|
1838
|
+
messageSubject: webpushData?.messageSubject || resolvedTitle || '',
|
|
1839
|
+
},
|
|
1840
|
+
webPushDeliverySettings: {
|
|
1841
|
+
channelSettings: {
|
|
1842
|
+
channel: CHANNELS.WEBPUSH,
|
|
1843
|
+
notificationTtl: {
|
|
1844
|
+
duration: 7,
|
|
1845
|
+
timeUnit: DAYS,
|
|
1846
|
+
},
|
|
1847
|
+
},
|
|
1848
|
+
additionalSettings: {},
|
|
1849
|
+
},
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1804
1853
|
default:
|
|
1805
1854
|
return basePayload;
|
|
1806
1855
|
}
|
|
@@ -1832,7 +1881,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
1832
1881
|
contentObj = hasPreviewCallBeenMade && previewDataHtml?.resolvedBody
|
|
1833
1882
|
? previewDataHtml.resolvedBody
|
|
1834
1883
|
: getCurrentContent || '';
|
|
1835
|
-
} else if (
|
|
1884
|
+
} else if ([CHANNELS.WHATSAPP, CHANNELS.WEBPUSH].includes(channel)) {
|
|
1836
1885
|
// For WhatsApp, content is an object with templateMsg, media, CTA, etc.
|
|
1837
1886
|
// Content comes from WhatsApp component state, passed via content prop
|
|
1838
1887
|
let resolvedContent = null;
|
|
@@ -517,4 +517,167 @@ describe('PreviewHeader', () => {
|
|
|
517
517
|
consoleSpy.mockRestore();
|
|
518
518
|
});
|
|
519
519
|
});
|
|
520
|
+
|
|
521
|
+
describe('WEBPUSH channel - Fullscreen expander', () => {
|
|
522
|
+
it('should render expander icon for WEBPUSH channel', () => {
|
|
523
|
+
const setIsFullscreenOpen = jest.fn();
|
|
524
|
+
const props = {
|
|
525
|
+
...defaultProps,
|
|
526
|
+
channel: CHANNELS.WEBPUSH,
|
|
527
|
+
setIsFullscreenOpen,
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const { container } = render(
|
|
531
|
+
<TestWrapper>
|
|
532
|
+
<PreviewHeader {...props} />
|
|
533
|
+
</TestWrapper>
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
// expander icon should be present for WEBPUSH
|
|
537
|
+
const expanderIcon = container.querySelector('[class*="expander"], [aria-label*="expander"], .anticon');
|
|
538
|
+
expect(expanderIcon).toBeTruthy();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('should NOT render expander icon for non-WEBPUSH channels', () => {
|
|
542
|
+
const setIsFullscreenOpen = jest.fn();
|
|
543
|
+
const props = {
|
|
544
|
+
...defaultProps,
|
|
545
|
+
channel: CHANNELS.EMAIL,
|
|
546
|
+
setIsFullscreenOpen,
|
|
547
|
+
showDeviceToggle: false,
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
const { container } = render(
|
|
551
|
+
<TestWrapper>
|
|
552
|
+
<PreviewHeader {...props} />
|
|
553
|
+
</TestWrapper>
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
// No expander icon for EMAIL channel (only device toggle icons may appear)
|
|
557
|
+
const expanderIcon = container.querySelector('[class*="expander"]');
|
|
558
|
+
expect(expanderIcon).toBeNull();
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('should call setIsFullscreenOpen(true) when expander icon is clicked', () => {
|
|
562
|
+
const setIsFullscreenOpen = jest.fn();
|
|
563
|
+
const props = {
|
|
564
|
+
...defaultProps,
|
|
565
|
+
channel: CHANNELS.WEBPUSH,
|
|
566
|
+
setIsFullscreenOpen,
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
const { container } = render(
|
|
570
|
+
<TestWrapper>
|
|
571
|
+
<PreviewHeader {...props} />
|
|
572
|
+
</TestWrapper>
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const expanderIcon = container.querySelector('[class*="expander"]') ||
|
|
576
|
+
container.querySelector('.anticon');
|
|
577
|
+
if (expanderIcon) {
|
|
578
|
+
fireEvent.click(expanderIcon);
|
|
579
|
+
expect(setIsFullscreenOpen).toHaveBeenCalledWith(true);
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('should render expander icon without ACTIVE class when device is MOBILE', () => {
|
|
584
|
+
const setIsFullscreenOpen = jest.fn();
|
|
585
|
+
const props = {
|
|
586
|
+
...defaultProps,
|
|
587
|
+
channel: CHANNELS.WEBPUSH,
|
|
588
|
+
device: MOBILE,
|
|
589
|
+
setIsFullscreenOpen,
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
const { container } = render(
|
|
593
|
+
<TestWrapper>
|
|
594
|
+
<PreviewHeader {...props} />
|
|
595
|
+
</TestWrapper>
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
// Expander icon no longer adds ACTIVE class based on device
|
|
599
|
+
const activeEl = container.querySelector('.active');
|
|
600
|
+
expect(activeEl).toBeNull();
|
|
601
|
+
// But the expander icon itself should still be present
|
|
602
|
+
const expanderIcon = container.querySelector('[class*="expander"]') ||
|
|
603
|
+
container.querySelector('.anticon');
|
|
604
|
+
expect(expanderIcon).toBeTruthy();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it('should render expander icon without ACTIVE class when device is DESKTOP', () => {
|
|
608
|
+
const setIsFullscreenOpen = jest.fn();
|
|
609
|
+
const props = {
|
|
610
|
+
...defaultProps,
|
|
611
|
+
channel: CHANNELS.WEBPUSH,
|
|
612
|
+
device: DESKTOP,
|
|
613
|
+
setIsFullscreenOpen,
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const { container } = render(
|
|
617
|
+
<TestWrapper>
|
|
618
|
+
<PreviewHeader {...props} />
|
|
619
|
+
</TestWrapper>
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
// Expander icon does not have ACTIVE class for any device
|
|
623
|
+
const activeEl = container.querySelector('.active');
|
|
624
|
+
expect(activeEl).toBeNull();
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('should render expander icon alongside device content for WEBPUSH', () => {
|
|
628
|
+
// Note: suppression of showDeviceToggle for WEBPUSH is done in UnifiedPreview (parent),
|
|
629
|
+
// not in PreviewHeader itself. PreviewHeader only adds the expander icon for WEBPUSH.
|
|
630
|
+
const setIsFullscreenOpen = jest.fn();
|
|
631
|
+
const props = {
|
|
632
|
+
...defaultProps,
|
|
633
|
+
channel: CHANNELS.WEBPUSH,
|
|
634
|
+
setIsFullscreenOpen,
|
|
635
|
+
showDeviceToggle: false,
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const { container } = render(
|
|
639
|
+
<TestWrapper>
|
|
640
|
+
<PreviewHeader {...props} />
|
|
641
|
+
</TestWrapper>
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
// expander icon should be present for WEBPUSH
|
|
645
|
+
const expanderIcon = container.querySelector('[class*="expander"]') ||
|
|
646
|
+
container.querySelector('.anticon');
|
|
647
|
+
expect(expanderIcon).toBeTruthy();
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it('should accept isFullscreenOpen and setIsFullscreenOpen as props', () => {
|
|
651
|
+
const setIsFullscreenOpen = jest.fn();
|
|
652
|
+
const props = {
|
|
653
|
+
...defaultProps,
|
|
654
|
+
channel: CHANNELS.WEBPUSH,
|
|
655
|
+
isFullscreenOpen: false,
|
|
656
|
+
setIsFullscreenOpen,
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
expect(() =>
|
|
660
|
+
render(
|
|
661
|
+
<TestWrapper>
|
|
662
|
+
<PreviewHeader {...props} />
|
|
663
|
+
</TestWrapper>
|
|
664
|
+
)
|
|
665
|
+
).not.toThrow();
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it('should use default setIsFullscreenOpen when not provided', () => {
|
|
669
|
+
const props = {
|
|
670
|
+
...defaultProps,
|
|
671
|
+
channel: CHANNELS.WEBPUSH,
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
expect(() =>
|
|
675
|
+
render(
|
|
676
|
+
<TestWrapper>
|
|
677
|
+
<PreviewHeader {...props} />
|
|
678
|
+
</TestWrapper>
|
|
679
|
+
)
|
|
680
|
+
).not.toThrow();
|
|
681
|
+
});
|
|
682
|
+
});
|
|
520
683
|
});
|