@capillarytech/creatives-library 8.0.357 → 8.0.359-alpha.0
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 -75
- package/utils/tests/cdnTransformation.test.js +0 -127
- package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +0 -16
- package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +132 -14
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +163 -54
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +6 -52
- package/v2Components/CommonTestAndPreview/constants.js +0 -2
- package/v2Components/CommonTestAndPreview/index.js +231 -77
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +0 -163
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +364 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +0 -255
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -2
- package/v2Components/CommonTestAndPreview/tests/index.test.js +0 -194
- package/v2Components/FormBuilder/index.js +52 -162
- package/v2Components/TestAndPreviewSlidebox/index.js +2 -2
- package/v2Containers/App/constants.js +0 -3
- package/v2Containers/CreativesContainer/index.js +24 -60
- package/v2Containers/Templates/_templates.scss +77 -0
- package/v2Containers/Templates/index.js +92 -82
- package/v2Containers/Templates/sagas.js +1 -6
- package/v2Containers/Templates/tests/sagas.test.js +6 -23
- package/v2Containers/Viber/constants.js +19 -0
- package/v2Containers/Viber/index.js +714 -47
- package/v2Containers/Viber/index.scss +148 -0
- package/v2Containers/Viber/messages.js +116 -0
- package/v2Containers/Viber/tests/index.test.js +80 -0
- package/v2Containers/WebPush/Create/index.js +8 -91
- package/v2Containers/WebPush/Create/index.scss +0 -7
- package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +0 -169
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +0 -522
- package/v2Containers/App/tests/constants.test.js +0 -61
- package/v2Containers/Templates/tests/webpush.test.js +0 -375
- package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +0 -348
- package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +0 -325
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,86 +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
|
-
// Parse and validate S3_CDN_MAP before proceeding; malformed or empty map
|
|
466
|
-
// means CDN lookups would silently fail, so fall back to the API instead.
|
|
467
|
-
const parsedS3CdnMap = safeJsonParse(env.S3_CDN_MAP, null);
|
|
468
|
-
if (
|
|
469
|
-
!parsedS3CdnMap
|
|
470
|
-
|| typeof parsedS3CdnMap !== 'object'
|
|
471
|
-
|| Array.isArray(parsedS3CdnMap)
|
|
472
|
-
|| !Object.keys(parsedS3CdnMap).length
|
|
473
|
-
) {
|
|
474
|
-
return false;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
saveCdnConfigs({
|
|
478
|
-
hostname: env.CDN_HOSTNAME,
|
|
479
|
-
transformationUrlSuffix: env.CDN_IMG_TRANSFORMATION_URL_SUFFIX,
|
|
480
|
-
bucketPath: env.CREATIVE_ASSETS_BUCKET_PATH,
|
|
481
|
-
qualityCfg: safeJsonParse(env.CDN_IMG_QUALITY_CFG, {}),
|
|
482
|
-
overrideEmailQuality: env.OVERRIDE_IMAGE_QUALITY === 'true',
|
|
483
|
-
overrideEmailQualityMapping: safeJsonParse(env.IMAGE_QUALITY_MAPPING, []),
|
|
484
|
-
s3CdnMap: parsedS3CdnMap,
|
|
485
|
-
});
|
|
486
|
-
return true;
|
|
487
|
-
} catch (e) {
|
|
488
|
-
Bugsnag.leaveBreadcrumb('initCdnConfigFromEnv failed to apply window.APP_ENV');
|
|
489
|
-
Bugsnag.notify(e, (event) => {
|
|
490
|
-
event.severity = 'error';
|
|
491
|
-
});
|
|
492
|
-
return false;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
*
|
|
498
|
-
* @param {*} configsResponse
|
|
425
|
+
*
|
|
426
|
+
* @param {*} configsResponse
|
|
499
427
|
* This util function saves the getCdnConfigs response into the local storage.
|
|
500
428
|
* The following response items are mapped into the following keys in local storage:
|
|
501
429
|
* hostname -> CREATIVES_CDN_BASE_URL
|
|
502
430
|
* qualityCfg ->CREATIVES_CDN_QUALITY_CONFIG
|
|
503
431
|
* transformationUrlSuffix -> CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX
|
|
504
|
-
*
|
|
432
|
+
*
|
|
505
433
|
* 1. If configsReponse is empty. All above mentioned keys are deleted from localstorage.
|
|
506
434
|
* 2. If any one of the above keys is missing. The respective keys are deleted from localstorage.
|
|
507
435
|
* 3. Else it is saved into the localstorage.
|
|
@@ -546,133 +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 optional JSON env vars (qualityCfg, mapping)", () => {
|
|
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: '{"host.s3.amazonaws.com":{"cdn_host":"cdn.example.com","bucket_path":"x"}}',
|
|
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("returns false when S3_CDN_MAP is malformed JSON", () => {
|
|
629
|
-
window.APP_ENV = { ...completeEnv, S3_CDN_MAP: "}{" };
|
|
630
|
-
expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
it.each([
|
|
634
|
-
["empty object", "{}"],
|
|
635
|
-
["array", '["a","b"]'],
|
|
636
|
-
["null", "null"],
|
|
637
|
-
["number", "42"],
|
|
638
|
-
["string", '"foo"'],
|
|
639
|
-
])("returns false when S3_CDN_MAP parses to %s (not a non-empty object)", (_label, value) => {
|
|
640
|
-
window.APP_ENV = { ...completeEnv, S3_CDN_MAP: value };
|
|
641
|
-
expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
it("treats OVERRIDE_IMAGE_QUALITY values other than the string \"true\" as false", () => {
|
|
645
|
-
window.APP_ENV = {
|
|
646
|
-
CDN_HOSTNAME: "https://storage.crm.n.content-cdn.io",
|
|
647
|
-
CDN_IMG_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
|
|
648
|
-
CREATIVE_ASSETS_BUCKET_PATH: "intouch_creative_assets",
|
|
649
|
-
OVERRIDE_IMAGE_QUALITY: "no",
|
|
650
|
-
CDN_IMG_QUALITY_CFG: "{}",
|
|
651
|
-
IMAGE_QUALITY_MAPPING: "[]",
|
|
652
|
-
S3_CDN_MAP: '{"host.s3.amazonaws.com":{"cdn_host":"cdn.example.com","bucket_path":"x"}}',
|
|
653
|
-
};
|
|
654
|
-
|
|
655
|
-
cdnUtils.initCdnConfigFromEnv();
|
|
656
|
-
expect(localStorage.getItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY")).toBe(undefined);
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
it("returns false and notifies Bugsnag when window.APP_ENV access throws", () => {
|
|
660
|
-
// Force the `window.APP_ENV` read inside initCdnConfigFromEnv to throw,
|
|
661
|
-
// which exercises the function's outer catch block.
|
|
662
|
-
Object.defineProperty(window, "APP_ENV", {
|
|
663
|
-
get() { throw new Error("APP_ENV access boom"); },
|
|
664
|
-
configurable: true,
|
|
665
|
-
});
|
|
666
|
-
const breadcrumbSpy = jest.spyOn(Bugsnag, "leaveBreadcrumb");
|
|
667
|
-
|
|
668
|
-
expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
|
|
669
|
-
expect(breadcrumbSpy).toHaveBeenCalledWith(
|
|
670
|
-
"initCdnConfigFromEnv failed to apply window.APP_ENV"
|
|
671
|
-
);
|
|
672
|
-
expect(bugsnagSpy).toHaveBeenCalled();
|
|
673
|
-
});
|
|
674
|
-
});
|
|
675
|
-
|
|
676
549
|
describe("getEmailImageOverrideQuality()",()=>{
|
|
677
550
|
it("Should return quality of last element in array if file size is greater than max provided in array",()=>{
|
|
678
551
|
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|
|
@@ -23,7 +23,6 @@ const PreviewHeader = ({
|
|
|
23
23
|
showDeviceToggle,
|
|
24
24
|
onDeviceChange,
|
|
25
25
|
channel,
|
|
26
|
-
setIsFullscreenOpen,
|
|
27
26
|
}) => {
|
|
28
27
|
// Determine if this is SMS, WhatsApp, RCS, InApp, MobilePush, or Viber channel (uses Android/iOS) or other channels (uses Desktop/Mobile)
|
|
29
28
|
const isSmsChannel = channel === CHANNELS.SMS;
|
|
@@ -32,13 +31,8 @@ const PreviewHeader = ({
|
|
|
32
31
|
const isInAppChannel = channel === CHANNELS.INAPP;
|
|
33
32
|
const isMobilePushChannel = channel === CHANNELS.MOBILEPUSH;
|
|
34
33
|
const isViberChannel = channel === CHANNELS.VIBER;
|
|
35
|
-
const isWebPushChannel = channel === CHANNELS.WEBPUSH;
|
|
36
34
|
const isAndroidIosToggle = isSmsChannel || isWhatsappChannel || isRcsChannel || isInAppChannel || isMobilePushChannel || isViberChannel;
|
|
37
35
|
|
|
38
|
-
const handleOpenFullscreen = () => {
|
|
39
|
-
setIsFullscreenOpen(true);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
36
|
return (
|
|
43
37
|
<CapRow className="preview-chrome">
|
|
44
38
|
<div className="preview-header">
|
|
@@ -86,12 +80,6 @@ const PreviewHeader = ({
|
|
|
86
80
|
)}
|
|
87
81
|
</CapRow>
|
|
88
82
|
)}
|
|
89
|
-
{isWebPushChannel && (
|
|
90
|
-
<CapIcon
|
|
91
|
-
type="expander"
|
|
92
|
-
onClick={() => handleOpenFullscreen()}
|
|
93
|
-
/>
|
|
94
|
-
)}
|
|
95
83
|
</div>
|
|
96
84
|
</CapRow>
|
|
97
85
|
);
|
|
@@ -107,8 +95,6 @@ PreviewHeader.propTypes = {
|
|
|
107
95
|
showDeviceToggle: PropTypes.bool,
|
|
108
96
|
onDeviceChange: PropTypes.func,
|
|
109
97
|
channel: PropTypes.string,
|
|
110
|
-
isFullscreenOpen: PropTypes.bool,
|
|
111
|
-
setIsFullscreenOpen: PropTypes.func,
|
|
112
98
|
};
|
|
113
99
|
|
|
114
100
|
PreviewHeader.defaultProps = {
|
|
@@ -117,8 +103,6 @@ PreviewHeader.defaultProps = {
|
|
|
117
103
|
showDeviceToggle: false,
|
|
118
104
|
onDeviceChange: () => {},
|
|
119
105
|
channel: null,
|
|
120
|
-
isFullscreenOpen: false,
|
|
121
|
-
setIsFullscreenOpen: () => {},
|
|
122
106
|
};
|
|
123
107
|
|
|
124
108
|
export default PreviewHeader;
|
|
@@ -14,7 +14,14 @@ import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
|
14
14
|
import CapImage from '@capillarytech/cap-ui-library/CapImage';
|
|
15
15
|
import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
|
|
16
16
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
ANDROID,
|
|
19
|
+
IOS,
|
|
20
|
+
ANDROID_DEVICE_NAME,
|
|
21
|
+
IOS_DEVICE_NAME,
|
|
22
|
+
VIBER_ACCOUNT_NAME,
|
|
23
|
+
MEDIA_TYPE_CAROUSEL,
|
|
24
|
+
} from '../constants';
|
|
18
25
|
import messages from '../messages';
|
|
19
26
|
import videoPlay from '../../../assets/videoPlay.svg';
|
|
20
27
|
|
|
@@ -22,6 +29,9 @@ import videoPlay from '../../../assets/videoPlay.svg';
|
|
|
22
29
|
const smsMobileAndroid = require('../../../assets/Android.png');
|
|
23
30
|
const smsMobileIos = require('../../../assets/iOS.png');
|
|
24
31
|
|
|
32
|
+
const getTrimmedText = (value = '') => (value ?? '').trim();
|
|
33
|
+
const hasTrimmedText = (value = '') => Boolean(getTrimmedText(value));
|
|
34
|
+
|
|
25
35
|
const ViberPreviewContent = ({
|
|
26
36
|
content,
|
|
27
37
|
device,
|
|
@@ -43,7 +53,34 @@ const ViberPreviewContent = ({
|
|
|
43
53
|
imageURL = '',
|
|
44
54
|
videoParams = {},
|
|
45
55
|
buttonText = '',
|
|
56
|
+
type = '',
|
|
57
|
+
cards = [],
|
|
58
|
+
showCarouselEditorPreview = false,
|
|
46
59
|
} = viberContent;
|
|
60
|
+
const hasCarouselContent = type === MEDIA_TYPE_CAROUSEL;
|
|
61
|
+
|
|
62
|
+
const cardHasMeaningfulContent = (card) => {
|
|
63
|
+
if (!card || typeof card !== 'object') return false;
|
|
64
|
+
if (hasTrimmedText(card?.text)) return true;
|
|
65
|
+
if (hasTrimmedText(card?.mediaUrl)) return true;
|
|
66
|
+
const buttons = card?.buttons ?? [];
|
|
67
|
+
return buttons.some((button) => hasTrimmedText(button?.title));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const hasMeaningfulCarousel =
|
|
71
|
+
hasCarouselContent &&
|
|
72
|
+
Array.isArray(cards) &&
|
|
73
|
+
cards.length > 0 &&
|
|
74
|
+
cards.some(cardHasMeaningfulContent);
|
|
75
|
+
|
|
76
|
+
const showCarouselInPreview =
|
|
77
|
+
hasCarouselContent && (Boolean(showCarouselEditorPreview) || hasMeaningfulCarousel);
|
|
78
|
+
|
|
79
|
+
const previewCarouselCards =
|
|
80
|
+
hasCarouselContent && Array.isArray(cards) && cards.length ? cards : hasCarouselContent ? [{}] : [];
|
|
81
|
+
|
|
82
|
+
const trimmedMessageContent = getTrimmedText(messageContent);
|
|
83
|
+
const trimmedButtonText = getTrimmedText(buttonText);
|
|
47
84
|
|
|
48
85
|
// Get account name (first letter for icon)
|
|
49
86
|
const accountIcon = (accountName || brandName || 'V')[0]?.toUpperCase();
|
|
@@ -99,8 +136,15 @@ const ViberPreviewContent = ({
|
|
|
99
136
|
);
|
|
100
137
|
}
|
|
101
138
|
|
|
102
|
-
// Check if there's any content to display
|
|
103
|
-
const hasContent =
|
|
139
|
+
// Check if there's any content to display (whitespace-only strings do not count)
|
|
140
|
+
const hasContent = Boolean(
|
|
141
|
+
trimmedMessageContent
|
|
142
|
+
|| hasTrimmedText(imageURL)
|
|
143
|
+
|| videoParams?.viberVideoSrc
|
|
144
|
+
|| trimmedButtonText
|
|
145
|
+
|| hasMeaningfulCarousel
|
|
146
|
+
|| (hasCarouselContent && showCarouselEditorPreview)
|
|
147
|
+
);
|
|
104
148
|
|
|
105
149
|
// Render normal Viber preview
|
|
106
150
|
return (
|
|
@@ -127,21 +171,23 @@ const ViberPreviewContent = ({
|
|
|
127
171
|
{hasContent && (
|
|
128
172
|
<CapRow className={`viber-message-container ${device !== ANDROID ? 'viber-message-container-ios' : ''}`}>
|
|
129
173
|
{/* Brand Name Display (from TemplatePreview line 1136) */}
|
|
130
|
-
<CapRow className=
|
|
174
|
+
<CapRow className={`msg-container viber-preview ${showCarouselInPreview ? 'viber-preview-carousel' : ''}`}>
|
|
131
175
|
{/* Account Icon (from TemplatePreview line 1146-1160) */}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
176
|
+
{!hasCarouselContent && (
|
|
177
|
+
<CapRow className="viber-account-icon">
|
|
178
|
+
{accountIcon}
|
|
179
|
+
</CapRow>
|
|
180
|
+
)}
|
|
135
181
|
|
|
136
182
|
{/* Message Bubble (from TemplatePreview line 1161-1223) */}
|
|
137
|
-
<CapRow className=
|
|
183
|
+
<CapRow className={`message-pop align-left viber-message-pop ${showCarouselInPreview ? 'viber-message-pop-carousel' : ''}`}>
|
|
138
184
|
{/* Text Viber preview */}
|
|
139
|
-
{
|
|
185
|
+
{trimmedMessageContent && !hasCarouselContent && (
|
|
140
186
|
<CapLabel type="label15" className="viber-message-text">{messageContent}</CapLabel>
|
|
141
187
|
)}
|
|
142
188
|
|
|
143
189
|
{/* Image Viber preview */}
|
|
144
|
-
{imageURL && (
|
|
190
|
+
{hasTrimmedText(imageURL) && (
|
|
145
191
|
<CapImage
|
|
146
192
|
src={imageURL}
|
|
147
193
|
className="viber-image-preview"
|
|
@@ -171,16 +217,78 @@ const ViberPreviewContent = ({
|
|
|
171
217
|
)}
|
|
172
218
|
|
|
173
219
|
{/* Button Viber preview */}
|
|
174
|
-
{
|
|
220
|
+
{trimmedButtonText && (
|
|
175
221
|
<CapLabel className="viber-button-base">
|
|
176
222
|
<p className="viber-button-card-text">
|
|
177
223
|
{buttonText}
|
|
178
224
|
</p>
|
|
179
225
|
</CapLabel>
|
|
180
226
|
)}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
227
|
+
{/* Carousel Viber preview */}
|
|
228
|
+
{showCarouselInPreview && (
|
|
229
|
+
<>
|
|
230
|
+
<CapRow className="viber-carousel-message-pop">
|
|
231
|
+
{trimmedMessageContent ? (
|
|
232
|
+
<CapLabel
|
|
233
|
+
type="label15"
|
|
234
|
+
className="message-pop-item align-left viber-message-text viber-carousel-message-box-text"
|
|
235
|
+
>
|
|
236
|
+
{messageContent}
|
|
237
|
+
</CapLabel>
|
|
238
|
+
) : (
|
|
239
|
+
<CapRow className="viber-carousel-message-box-placeholder" />
|
|
240
|
+
)}
|
|
241
|
+
<CapLabel type="label1" className="viber-carousel-message-timestamp">
|
|
242
|
+
{timestamp}
|
|
243
|
+
</CapLabel>
|
|
244
|
+
</CapRow>
|
|
245
|
+
|
|
246
|
+
<CapRow className="viber-carousel-cards-pop">
|
|
247
|
+
<CapRow className="viber-carousel-preview-scroll">
|
|
248
|
+
{previewCarouselCards?.map((card, index) => (
|
|
249
|
+
<CapRow className="viber-carousel-preview-card" key={`viber-carousel-preview-card-${index}`}>
|
|
250
|
+
{hasTrimmedText(card?.mediaUrl) ? (
|
|
251
|
+
<CapImage
|
|
252
|
+
src={card?.mediaUrl}
|
|
253
|
+
className="viber-carousel-preview-image"
|
|
254
|
+
alt="Viber carousel card"
|
|
255
|
+
/>
|
|
256
|
+
) : (
|
|
257
|
+
<CapRow className="viber-carousel-preview-image-placeholder" />
|
|
258
|
+
)}
|
|
259
|
+
<CapRow className="viber-carousel-preview-card-body">
|
|
260
|
+
{hasTrimmedText(card?.text) ? (
|
|
261
|
+
<CapLabel type="label15" className="viber-carousel-preview-text">
|
|
262
|
+
{card?.text}
|
|
263
|
+
</CapLabel>
|
|
264
|
+
) : (
|
|
265
|
+
<CapLabel type="label15" className="viber-carousel-preview-text-placeholder" />
|
|
266
|
+
)}
|
|
267
|
+
{(card?.buttons?.filter((cardButton) => hasTrimmedText(cardButton?.title)) ?? [])
|
|
268
|
+
.slice(0, 2)
|
|
269
|
+
.map((cardButton, btnIndex) => {
|
|
270
|
+
const trimmedCardButtonTitle = getTrimmedText(cardButton?.title);
|
|
271
|
+
return (
|
|
272
|
+
<CapLabel
|
|
273
|
+
className={`viber-carousel-preview-button ${btnIndex === 1 ? 'viber-carousel-preview-button-secondary' : ''}`}
|
|
274
|
+
key={`viber-carousel-preview-btn-${index}-${btnIndex}-${trimmedCardButtonTitle}`}
|
|
275
|
+
>
|
|
276
|
+
{trimmedCardButtonTitle}
|
|
277
|
+
</CapLabel>
|
|
278
|
+
);
|
|
279
|
+
})}
|
|
280
|
+
</CapRow>
|
|
281
|
+
</CapRow>
|
|
282
|
+
))}
|
|
283
|
+
</CapRow>
|
|
284
|
+
</CapRow>
|
|
285
|
+
</>
|
|
286
|
+
)}
|
|
287
|
+
{!showCarouselInPreview && (
|
|
288
|
+
<CapLabel type="label1" className="viber-timestamp">
|
|
289
|
+
{timestamp}
|
|
290
|
+
</CapLabel>
|
|
291
|
+
)}
|
|
184
292
|
<CapRow className="empty-placeholder" />
|
|
185
293
|
</CapRow>
|
|
186
294
|
|
|
@@ -214,6 +322,16 @@ ViberPreviewContent.propTypes = {
|
|
|
214
322
|
viberVideoPreviewImg: PropTypes.string,
|
|
215
323
|
}),
|
|
216
324
|
buttonText: PropTypes.string,
|
|
325
|
+
type: PropTypes.string,
|
|
326
|
+
cards: PropTypes.arrayOf(PropTypes.shape({
|
|
327
|
+
text: PropTypes.string,
|
|
328
|
+
mediaUrl: PropTypes.string,
|
|
329
|
+
buttons: PropTypes.arrayOf(PropTypes.shape({
|
|
330
|
+
title: PropTypes.string,
|
|
331
|
+
action: PropTypes.string,
|
|
332
|
+
})),
|
|
333
|
+
})),
|
|
334
|
+
showCarouselEditorPreview: PropTypes.bool,
|
|
217
335
|
}),
|
|
218
336
|
}),
|
|
219
337
|
device: PropTypes.oneOf([ANDROID, IOS]),
|