@capillarytech/creatives-library 8.0.354 → 8.0.356
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/index.html +0 -1
- package/package.json +1 -1
- package/utils/cdnTransformation.js +3 -63
- package/utils/tests/cdnTransformation.test.js +0 -111
- 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 +52 -6
- 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/Templates/index.js +72 -2
- package/v2Containers/Templates/sagas.js +1 -6
- package/v2Containers/Templates/tests/sagas.test.js +6 -23
- package/v2Containers/Templates/tests/webpush.test.js +375 -0
- package/v2Containers/WebPush/Create/index.js +91 -8
- package/v2Containers/WebPush/Create/index.scss +7 -0
- package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +348 -0
- package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +325 -0
package/index.html
CHANGED
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
<!-- End Google Tag Manager -->
|
|
24
24
|
<script>try{Typekit.load({ async: true });}catch(e){console.log(e)}</script>
|
|
25
25
|
<title>Capillary - Creatives</title>
|
|
26
|
-
<script>try{window.APP_ENV=__ENV_OBJECT__;}catch(e){window.APP_ENV={};}</script>
|
|
27
26
|
</head>
|
|
28
27
|
<body>
|
|
29
28
|
<noscript>
|
package/package.json
CHANGED
|
@@ -422,74 +422,14 @@ export function removeAllCdnLocalStorageItems() {
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
/**
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*/
|
|
428
|
-
function safeJsonParse(value, fallback) {
|
|
429
|
-
if (!value) return fallback;
|
|
430
|
-
try {
|
|
431
|
-
return JSON.parse(value);
|
|
432
|
-
} catch (e) {
|
|
433
|
-
return fallback;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Reads CDN config from window.APP_ENV (injected at container startup by entrypoint.sh)
|
|
439
|
-
* and populates localStorage via saveCdnConfigs — same shape as the legacy API response.
|
|
440
|
-
*
|
|
441
|
-
* Returns true if env config was found and applied, false otherwise (saga falls back to API).
|
|
442
|
-
*
|
|
443
|
-
* In deployed containers, window.APP_ENV is populated → no API call. In local dev
|
|
444
|
-
* (`npm start`), window.APP_ENV is empty → returns false → saga falls back to the API
|
|
445
|
-
* (legacy path, retained for the rollout window).
|
|
446
|
-
*
|
|
447
|
-
* Closes the VAPT info-disclosure surface (CAP-183204) by eliminating the
|
|
448
|
-
* /getCdnTransformationConfig API response in production.
|
|
449
|
-
*/
|
|
450
|
-
export function initCdnConfigFromEnv() {
|
|
451
|
-
try {
|
|
452
|
-
const env = (typeof window !== 'undefined' && window.APP_ENV) || {};
|
|
453
|
-
|
|
454
|
-
// All four fields are required to construct a CDN URL. If any is missing,
|
|
455
|
-
// partial config would silently make every getCdnUrl call fall through to the
|
|
456
|
-
// raw S3 URL. Return false so the saga falls back to the API (which may still
|
|
457
|
-
// have the values during the cluster-by-cluster rollout window).
|
|
458
|
-
if (!env.CDN_HOSTNAME
|
|
459
|
-
|| !env.CDN_IMG_TRANSFORMATION_URL_SUFFIX
|
|
460
|
-
|| !env.CREATIVE_ASSETS_BUCKET_PATH
|
|
461
|
-
|| !env.S3_CDN_MAP) {
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
saveCdnConfigs({
|
|
466
|
-
hostname: env.CDN_HOSTNAME,
|
|
467
|
-
transformationUrlSuffix: env.CDN_IMG_TRANSFORMATION_URL_SUFFIX,
|
|
468
|
-
bucketPath: env.CREATIVE_ASSETS_BUCKET_PATH,
|
|
469
|
-
qualityCfg: safeJsonParse(env.CDN_IMG_QUALITY_CFG, {}),
|
|
470
|
-
overrideEmailQuality: env.OVERRIDE_IMAGE_QUALITY === 'true',
|
|
471
|
-
overrideEmailQualityMapping: safeJsonParse(env.IMAGE_QUALITY_MAPPING, []),
|
|
472
|
-
s3CdnMap: safeJsonParse(env.S3_CDN_MAP, {}),
|
|
473
|
-
});
|
|
474
|
-
return true;
|
|
475
|
-
} catch (e) {
|
|
476
|
-
Bugsnag.leaveBreadcrumb('initCdnConfigFromEnv failed to apply window.APP_ENV');
|
|
477
|
-
Bugsnag.notify(e, (event) => {
|
|
478
|
-
event.severity = 'error';
|
|
479
|
-
});
|
|
480
|
-
return false;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
*
|
|
486
|
-
* @param {*} configsResponse
|
|
425
|
+
*
|
|
426
|
+
* @param {*} configsResponse
|
|
487
427
|
* This util function saves the getCdnConfigs response into the local storage.
|
|
488
428
|
* The following response items are mapped into the following keys in local storage:
|
|
489
429
|
* hostname -> CREATIVES_CDN_BASE_URL
|
|
490
430
|
* qualityCfg ->CREATIVES_CDN_QUALITY_CONFIG
|
|
491
431
|
* transformationUrlSuffix -> CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX
|
|
492
|
-
*
|
|
432
|
+
*
|
|
493
433
|
* 1. If configsReponse is empty. All above mentioned keys are deleted from localstorage.
|
|
494
434
|
* 2. If any one of the above keys is missing. The respective keys are deleted from localstorage.
|
|
495
435
|
* 3. Else it is saved into the localstorage.
|
|
@@ -546,117 +546,6 @@ describe("cdnTransformationTests", () => {
|
|
|
546
546
|
});
|
|
547
547
|
});
|
|
548
548
|
|
|
549
|
-
describe("initCdnConfigFromEnv()", () => {
|
|
550
|
-
afterEach(() => {
|
|
551
|
-
delete window.APP_ENV;
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
it("returns false when window.APP_ENV is undefined", () => {
|
|
555
|
-
delete window.APP_ENV;
|
|
556
|
-
expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
const completeEnv = {
|
|
560
|
-
CDN_HOSTNAME: "https://storage.crm.n.content-cdn.io",
|
|
561
|
-
CDN_IMG_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
|
|
562
|
-
CREATIVE_ASSETS_BUCKET_PATH: "intouch_creative_assets",
|
|
563
|
-
OVERRIDE_IMAGE_QUALITY: "false",
|
|
564
|
-
CDN_IMG_QUALITY_CFG: "{}",
|
|
565
|
-
IMAGE_QUALITY_MAPPING: "[]",
|
|
566
|
-
S3_CDN_MAP: '{"host.s3.amazonaws.com":{"cdn_host":"cdn.example.com","bucket_path":"x"}}',
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
it.each([
|
|
570
|
-
["CDN_HOSTNAME"],
|
|
571
|
-
["CDN_IMG_TRANSFORMATION_URL_SUFFIX"],
|
|
572
|
-
["CREATIVE_ASSETS_BUCKET_PATH"],
|
|
573
|
-
["S3_CDN_MAP"],
|
|
574
|
-
])("returns false when required field %s is missing", (missingKey) => {
|
|
575
|
-
window.APP_ENV = { ...completeEnv };
|
|
576
|
-
delete window.APP_ENV[missingKey];
|
|
577
|
-
expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
it.each([
|
|
581
|
-
["CDN_HOSTNAME"],
|
|
582
|
-
["CDN_IMG_TRANSFORMATION_URL_SUFFIX"],
|
|
583
|
-
["CREATIVE_ASSETS_BUCKET_PATH"],
|
|
584
|
-
["S3_CDN_MAP"],
|
|
585
|
-
])("returns false when required field %s is empty string", (emptyKey) => {
|
|
586
|
-
window.APP_ENV = { ...completeEnv, [emptyKey]: "" };
|
|
587
|
-
expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
it("populates localStorage and returns true for a complete env", () => {
|
|
591
|
-
window.APP_ENV = {
|
|
592
|
-
CDN_HOSTNAME: "https://storage.crm.n.content-cdn.io",
|
|
593
|
-
CDN_IMG_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
|
|
594
|
-
CREATIVE_ASSETS_BUCKET_PATH: "intouch_creative_assets",
|
|
595
|
-
OVERRIDE_IMAGE_QUALITY: "true",
|
|
596
|
-
CDN_IMG_QUALITY_CFG: '{"EMAIL":65,"DEFAULT":70}',
|
|
597
|
-
IMAGE_QUALITY_MAPPING: '[[30,90],[80,85]]',
|
|
598
|
-
S3_CDN_MAP: '{"host.s3.amazonaws.com":{"cdn_host":"cdn.example.com","bucket_path":"x"}}',
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
expect(cdnUtils.initCdnConfigFromEnv()).toBe(true);
|
|
602
|
-
expect(localStorage.getItem("CREATIVES_CDN_BASE_URL")).toBe("https://storage.crm.n.content-cdn.io");
|
|
603
|
-
expect(localStorage.getItem("CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX")).toBe("cdn-cgi/image");
|
|
604
|
-
expect(localStorage.getItem("CREATIVES_S3_BUCKET_PATH")).toBe("intouch_creative_assets");
|
|
605
|
-
expect(localStorage.getItem("CREATIVES_CDN_QUALITY_CONFIG")).toBe('{"EMAIL":65,"DEFAULT":70}');
|
|
606
|
-
expect(localStorage.getItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY")).toBe("true");
|
|
607
|
-
expect(localStorage.getItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING")).toBe("[[30,90],[80,85]]");
|
|
608
|
-
expect(JSON.parse(localStorage.getItem("S3_CDN_MAP"))).toEqual({
|
|
609
|
-
"host.s3.amazonaws.com": { cdn_host: "cdn.example.com", bucket_path: "x" },
|
|
610
|
-
});
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
it("uses fallback values for malformed JSON env vars instead of throwing", () => {
|
|
614
|
-
window.APP_ENV = {
|
|
615
|
-
CDN_HOSTNAME: "https://storage.crm.n.content-cdn.io",
|
|
616
|
-
CDN_IMG_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
|
|
617
|
-
CREATIVE_ASSETS_BUCKET_PATH: "intouch_creative_assets",
|
|
618
|
-
OVERRIDE_IMAGE_QUALITY: "false",
|
|
619
|
-
CDN_IMG_QUALITY_CFG: "not-valid-json{{",
|
|
620
|
-
IMAGE_QUALITY_MAPPING: "also-broken",
|
|
621
|
-
S3_CDN_MAP: "}{",
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
expect(cdnUtils.initCdnConfigFromEnv()).toBe(true);
|
|
625
|
-
expect(localStorage.getItem("CREATIVES_CDN_BASE_URL")).toBe("https://storage.crm.n.content-cdn.io");
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
it("treats OVERRIDE_IMAGE_QUALITY values other than the string \"true\" as false", () => {
|
|
629
|
-
window.APP_ENV = {
|
|
630
|
-
CDN_HOSTNAME: "https://storage.crm.n.content-cdn.io",
|
|
631
|
-
CDN_IMG_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
|
|
632
|
-
CREATIVE_ASSETS_BUCKET_PATH: "intouch_creative_assets",
|
|
633
|
-
OVERRIDE_IMAGE_QUALITY: "no",
|
|
634
|
-
CDN_IMG_QUALITY_CFG: "{}",
|
|
635
|
-
IMAGE_QUALITY_MAPPING: "[]",
|
|
636
|
-
S3_CDN_MAP: "{}",
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
cdnUtils.initCdnConfigFromEnv();
|
|
640
|
-
expect(localStorage.getItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY")).toBe(undefined);
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
it("returns false and notifies Bugsnag when window.APP_ENV access throws", () => {
|
|
644
|
-
// Force the `window.APP_ENV` read inside initCdnConfigFromEnv to throw,
|
|
645
|
-
// which exercises the function's outer catch block.
|
|
646
|
-
Object.defineProperty(window, "APP_ENV", {
|
|
647
|
-
get() { throw new Error("APP_ENV access boom"); },
|
|
648
|
-
configurable: true,
|
|
649
|
-
});
|
|
650
|
-
const breadcrumbSpy = jest.spyOn(Bugsnag, "leaveBreadcrumb");
|
|
651
|
-
|
|
652
|
-
expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
|
|
653
|
-
expect(breadcrumbSpy).toHaveBeenCalledWith(
|
|
654
|
-
"initCdnConfigFromEnv failed to apply window.APP_ENV"
|
|
655
|
-
);
|
|
656
|
-
expect(bugsnagSpy).toHaveBeenCalled();
|
|
657
|
-
});
|
|
658
|
-
});
|
|
659
|
-
|
|
660
549
|
describe("getEmailImageOverrideQuality()",()=>{
|
|
661
550
|
it("Should return quality of last element in array if file size is greater than max provided in array",()=>{
|
|
662
551
|
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|
|
@@ -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;
|
|
@@ -2350,6 +2350,60 @@
|
|
|
2350
2350
|
}
|
|
2351
2351
|
}
|
|
2352
2352
|
|
|
2353
|
+
// WebPush Test & Preview Styles
|
|
2354
|
+
.webpush-test-preview-container {
|
|
2355
|
+
width: 100%;
|
|
2356
|
+
position: relative;
|
|
2357
|
+
padding-left: $CAP_SPACE_16;
|
|
2358
|
+
padding-right: $CAP_SPACE_16;
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
.webpush-preview-panel {
|
|
2362
|
+
position: relative;
|
|
2363
|
+
width: 100%;
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
.webpush-fullscreen-modal {
|
|
2367
|
+
.webpush-fullscreen-divider {
|
|
2368
|
+
margin-top: 0;
|
|
2369
|
+
margin-bottom: $CAP_SPACE_16;
|
|
2370
|
+
}
|
|
2371
|
+
.ant-modal.cap-modal-v2 {
|
|
2372
|
+
width: 90%;
|
|
2373
|
+
max-width: 100%;
|
|
2374
|
+
margin-top: $CAP_SPACE_40;
|
|
2375
|
+
}
|
|
2376
|
+
// Preview Chrome wrapper (matches old TestAndPreviewSlidebox design)
|
|
2377
|
+
.webpush-preview-header {
|
|
2378
|
+
background: $CAP_WHITE;
|
|
2379
|
+
overflow: hidden;
|
|
2380
|
+
|
|
2381
|
+
.preview-divider {
|
|
2382
|
+
margin: 0;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
.webpush-heading-container {
|
|
2386
|
+
display: flex;
|
|
2387
|
+
justify-content: space-between;
|
|
2388
|
+
align-items: center;
|
|
2389
|
+
padding: $CAP_SPACE_16 0 $CAP_SPACE_16 0;
|
|
2390
|
+
|
|
2391
|
+
.preview-for {
|
|
2392
|
+
gap: $CAP_SPACE_04;
|
|
2393
|
+
align-items: center;
|
|
2394
|
+
b {
|
|
2395
|
+
margin-left: $CAP_SPACE_08;
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
.webpush-fullscreen-close-icon {
|
|
2400
|
+
width: $CAP_SPACE_24;
|
|
2401
|
+
height: $CAP_SPACE_24;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2353
2407
|
// Responsive adjustments
|
|
2354
2408
|
@media (max-width: 85.714rem) {
|
|
2355
2409
|
.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
331
|
<CapRow className={`preview-content-container ${!showHeader ? 'preview-content-container-no-header' : ''}`}>
|
|
286
|
-
{[CHANNELS.EMAIL, CHANNELS.SMS, CHANNELS.WHATSAPP, CHANNELS.RCS, CHANNELS.INAPP, CHANNELS.MOBILEPUSH, CHANNELS.VIBER].includes(channel) ? (
|
|
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 = {
|