@designcrowd/library.brand-page 6.1.14 → 6.1.16
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/lib/src/{CreatedOnBrandCrowd-de-de-CNqGW-GB.js → CreatedOnBrandCrowd-de-de-CmNhDMIf.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-BWZeor_R.js → CreatedOnBrandCrowd-en-us-DgplxxWa.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-D_-5Qdpd.js → CreatedOnBrandCrowd-es-es-DFdhFOa1.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-BC6Xo2uA.js → CreatedOnBrandCrowd-fr-ca-DjLCWBLB.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-BC6Xo2uA.js → CreatedOnBrandCrowd-fr-fr-DjLCWBLB.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-D10DwNv3.js → CreatedOnBrandCrowd-pt-br-CI2XD0OS.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-D10DwNv3.js → CreatedOnBrandCrowd-pt-pt-CI2XD0OS.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-BDHrHOOB.js → CreatedOnDcom-de-de-CkLkBztx.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-EbniAwLE.js → CreatedOnDcom-en-us-frnxBJJv.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-BpAIMuUH.js → CreatedOnDcom-es-es-CrqgiERj.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-BDSu4efU.js → CreatedOnDcom-fr-ca-CPoEcusj.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-BR7f1SWB.js → CreatedOnDcom-fr-fr-BqrRH3dV.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-DzY1XBcr.js → CreatedOnDcom-pt-br-CpbVi3Sw.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-1v5N9vgW.js → CreatedOnDcom-pt-pt-s0XZ3c7y.js} +1 -1
- package/lib/src/brand-page/models/index.d.ts +4 -1
- package/lib/src/brand-page/models/index.js +4 -1
- package/lib/src/brand-page/utils/view-model.util.js +18 -2
- package/lib/src/brand-page/view-models.d.ts +2 -1
- package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +4 -1
- package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +3 -0
- package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +15 -0
- package/lib/src/brand-page-maker/composables/useEventTracker.js +36 -0
- package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +18 -0
- package/lib/src/{bundled-translations-BiFAlQdC.js → bundled-translations-B2q5GWTQ.js} +1 -1
- package/lib/src/{bundled-translations.de-DE-DuRWnOP-.js → bundled-translations.de-DE-FhH2H_rd.js} +5 -5
- package/lib/src/{bundled-translations.es-ES-CokHdZtW.js → bundled-translations.es-ES-_ZExxMi7.js} +5 -5
- package/lib/src/{bundled-translations.fr-CA-CJcMmubA.js → bundled-translations.fr-CA-Tg-ODIiC.js} +1 -1
- package/lib/src/{bundled-translations.fr-FR-DYPup8fJ.js → bundled-translations.fr-FR-C0sU51Mz.js} +1 -1
- package/lib/src/{bundled-translations.pt-BR-GSsp1kuV.js → bundled-translations.pt-BR-MYFIhA4u.js} +9 -9
- package/lib/src/{bundled-translations.pt-PT-DbkY8UQ3.js → bundled-translations.pt-PT-DeUBSzCB.js} +9 -9
- package/lib/src/bundles/bundled-translations.de-DE.json +1 -0
- package/lib/src/bundles/bundled-translations.es-ES.json +1 -0
- package/lib/src/bundles/bundled-translations.fr-CA.json +1 -0
- package/lib/src/bundles/bundled-translations.fr-FR.json +1 -0
- package/lib/src/bundles/bundled-translations.json +1 -0
- package/lib/src/bundles/bundled-translations.pt-BR.json +1 -0
- package/lib/src/bundles/bundled-translations.pt-PT.json +1 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +1 -1
- package/lib/src/css/library.brand-page-brandPage.min.css +1 -1
- package/lib/src/css/library.brand-page-designCom.min.css +1 -1
- package/lib/src/css/library.brand-page-preview.min.css +1 -1
- package/lib/src/index.mjs +2 -2
- package/lib/src/{lib-BMP5cseh.js → lib-O_eJgJHP.js} +16498 -16437
- package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +3 -0
- package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +3 -0
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +3 -0
- package/lib/src/shared/logo-settings/components/EditLogoSettings.mixin.d.ts +3 -0
- package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +5 -0
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +5 -0
- package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.js +25 -0
- package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +3 -0
- package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +3 -0
- package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +3 -0
- package/lib/src/shared/template-settings/components/TemplatesSettings.mixin.d.ts +3 -0
- package/lib/src/tracker.d.ts +1 -1
- package/lib/src/tracker.js +1 -1
- package/lib/src/tracker.mjs +9 -7
- package/lib/src/{useEventTracker-Dmc2jFdN.js → useEventTracker-C5Ql5p7u.js} +56 -22
- package/lib/src/utils/minify-brand-page-metadata.d.ts +1 -0
- package/lib/src/utils/minify-brand-page-metadata.js +13 -3
- package/package.json +1 -1
- package/src/brand-page/models/index.ts +9 -2
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +18 -6
- package/src/brand-page/utils/__snapshots__/migration2.util.test.ts.snap +6 -2
- package/src/brand-page/utils/__snapshots__/model.util.test.ts.snap +12 -4
- package/src/brand-page/utils/view-model.util.test.ts +145 -1
- package/src/brand-page/utils/view-model.util.ts +27 -2
- package/src/brand-page/view-models.ts +2 -0
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +9 -1
- package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.preview-gate.test.ts +38 -1
- package/src/brand-page-maker/components/preview-modal/PreviewModalWithState.test.ts +123 -0
- package/src/brand-page-maker/components/preview-modal/PreviewModalWithState.vue +25 -0
- package/src/brand-page-maker/composables/useEventTracker.ts +41 -0
- package/src/bundles/bundled-translations.de-DE.json +1 -0
- package/src/bundles/bundled-translations.es-ES.json +1 -0
- package/src/bundles/bundled-translations.fr-CA.json +1 -0
- package/src/bundles/bundled-translations.fr-FR.json +1 -0
- package/src/bundles/bundled-translations.json +1 -0
- package/src/bundles/bundled-translations.pt-BR.json +1 -0
- package/src/bundles/bundled-translations.pt-PT.json +1 -0
- package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.test.ts +111 -0
- package/src/shared/page-settings/components/header-settings-panel/HeaderSettingsPanel.vue +18 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.de-DE.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.es-ES.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.fr-CA.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.fr-FR.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.pt-BR.json +1 -0
- package/src/shared/page-settings/components/page-settings/i18n/page-settings.pt-PT.json +1 -0
- package/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.ts +33 -0
- package/src/tracker.ts +2 -0
- package/src/utils/minify-brand-page-metadata.test.ts +70 -1
- package/src/utils/minify-brand-page-metadata.ts +19 -4
|
@@ -6,12 +6,13 @@ import {
|
|
|
6
6
|
type ContactFormContent,
|
|
7
7
|
type ColorItem,
|
|
8
8
|
} from '../models';
|
|
9
|
-
import { BRAND_PAGE_TYPES, PageStyles } from '../models';
|
|
9
|
+
import { BRAND_PAGE_TYPES, PageHeaderDesktop, PageStyles } from '../models';
|
|
10
10
|
import {
|
|
11
11
|
mapToBannerSectionViewModel,
|
|
12
12
|
mapToContactFormViewModel,
|
|
13
13
|
mapToImageViewModel,
|
|
14
14
|
mapToMailingListViewModel,
|
|
15
|
+
mapToPageHeaderViewModel,
|
|
15
16
|
mapToProfileViewModel,
|
|
16
17
|
mapToTextViewModel,
|
|
17
18
|
mapToViewModel,
|
|
@@ -508,6 +509,149 @@ describe('view model util', () => {
|
|
|
508
509
|
expect(url.searchParams.has('w')).toBe(true);
|
|
509
510
|
expect(wParamValue).toBe('1080');
|
|
510
511
|
});
|
|
512
|
+
|
|
513
|
+
describe('mapToPageHeaderViewModel background opacity (BP-5851)', () => {
|
|
514
|
+
const mapHeader = (header: PageHeaderDesktop) =>
|
|
515
|
+
mapToPageHeaderViewModel(header, new PageStyles(), []);
|
|
516
|
+
|
|
517
|
+
test('constructing without an explicit backgroundOpacity defaults it to 100 and renders unchanged', () => {
|
|
518
|
+
const header = new PageHeaderDesktop({
|
|
519
|
+
backgroundStyle: { backgroundColor: '#ff0000' },
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
expect(header.backgroundStyle.backgroundOpacity).toBe(100);
|
|
523
|
+
|
|
524
|
+
const viewModel = mapHeader(header);
|
|
525
|
+
|
|
526
|
+
expect(viewModel.backgroundStyle).toEqual({ backgroundColor: '#ff0000' });
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test('header constructed with no options still gets a concrete backgroundOpacity', () => {
|
|
530
|
+
expect(new PageHeaderDesktop().backgroundStyle.backgroundOpacity).toBe(
|
|
531
|
+
100,
|
|
532
|
+
);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test('un-constructed legacy header (parsed metadata that never ran the constructor) renders fully opaque', () => {
|
|
536
|
+
// The maker's live-preview path can hold the header JSON.parse'd verbatim.
|
|
537
|
+
const header = JSON.parse(
|
|
538
|
+
JSON.stringify(
|
|
539
|
+
new PageHeaderDesktop({
|
|
540
|
+
backgroundStyle: { backgroundColor: '#ff0000' },
|
|
541
|
+
}),
|
|
542
|
+
),
|
|
543
|
+
) as PageHeaderDesktop;
|
|
544
|
+
|
|
545
|
+
delete header.backgroundStyle.backgroundOpacity;
|
|
546
|
+
|
|
547
|
+
const viewModel = mapHeader(header);
|
|
548
|
+
|
|
549
|
+
expect(viewModel.backgroundStyle).toEqual({ backgroundColor: '#ff0000' });
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test('a header with backgroundStyle itself absent renders fully opaque instead of throwing', () => {
|
|
553
|
+
const header = new PageHeaderDesktop();
|
|
554
|
+
|
|
555
|
+
delete (header as { backgroundStyle?: unknown }).backgroundStyle;
|
|
556
|
+
|
|
557
|
+
expect(() => mapHeader(header)).not.toThrow();
|
|
558
|
+
expect(mapHeader(header).backgroundStyle).toEqual({});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test('opacity 100 (default) leaves backgroundColor unchanged', () => {
|
|
562
|
+
const header = new PageHeaderDesktop({
|
|
563
|
+
backgroundStyle: { backgroundColor: '#ff0000', backgroundOpacity: 100 },
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const viewModel = mapHeader(header);
|
|
567
|
+
|
|
568
|
+
expect(viewModel.backgroundStyle.backgroundColor).toBe('#ff0000');
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test('opacity 60 composites into the expected 8-digit hex backgroundColor', () => {
|
|
572
|
+
const header = new PageHeaderDesktop({
|
|
573
|
+
backgroundStyle: { backgroundColor: '#ff0000', backgroundOpacity: 60 },
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
const viewModel = mapHeader(header);
|
|
577
|
+
|
|
578
|
+
expect(viewModel.backgroundStyle.backgroundColor).toBe('#ff000099');
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
test('opacity below 100 composites into an 8-digit hex backgroundColor', () => {
|
|
582
|
+
const header = new PageHeaderDesktop({
|
|
583
|
+
backgroundStyle: { backgroundColor: '#ff0000', backgroundOpacity: 50 },
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
const viewModel = mapHeader(header);
|
|
587
|
+
|
|
588
|
+
expect(viewModel.backgroundStyle.backgroundColor).toBe('#ff000080');
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test('opacity 0 composites to the transparent keyword', () => {
|
|
592
|
+
const header = new PageHeaderDesktop({
|
|
593
|
+
backgroundStyle: { backgroundColor: '#ff0000', backgroundOpacity: 0 },
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
const viewModel = mapHeader(header);
|
|
597
|
+
|
|
598
|
+
expect(viewModel.backgroundStyle.backgroundColor).toBe('transparent');
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
test('a palette-matched (var(--palette-n)) backgroundColor is faded via color-mix, not handed back unfaded', () => {
|
|
602
|
+
const header = new PageHeaderDesktop({
|
|
603
|
+
backgroundStyle: {
|
|
604
|
+
backgroundColor: 'var(--palette-1)',
|
|
605
|
+
backgroundOpacity: 30,
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const viewModel = mapHeader(header);
|
|
610
|
+
|
|
611
|
+
expect(viewModel.backgroundStyle.backgroundColor).toBe(
|
|
612
|
+
'color-mix(in srgb, var(--palette-1) 30%, transparent)',
|
|
613
|
+
);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test('transparent backgroundColor is left unchanged regardless of opacity', () => {
|
|
617
|
+
const header = new PageHeaderDesktop({
|
|
618
|
+
backgroundStyle: {
|
|
619
|
+
backgroundColor: 'transparent',
|
|
620
|
+
backgroundOpacity: 40,
|
|
621
|
+
},
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
const viewModel = mapHeader(header);
|
|
625
|
+
|
|
626
|
+
expect(viewModel.backgroundStyle.backgroundColor).toBe('transparent');
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
test('unset backgroundColor is left unchanged regardless of opacity', () => {
|
|
630
|
+
const header = new PageHeaderDesktop({
|
|
631
|
+
backgroundStyle: { backgroundOpacity: 40 },
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const viewModel = mapHeader(header);
|
|
635
|
+
|
|
636
|
+
expect(viewModel.backgroundStyle.backgroundColor).toBeUndefined();
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
test.each([100, 30])(
|
|
640
|
+
'backgroundOpacity %i never leaks into the rendered style, and CSS opacity is never set (children must stay opaque)',
|
|
641
|
+
(backgroundOpacity) => {
|
|
642
|
+
const header = new PageHeaderDesktop({
|
|
643
|
+
backgroundStyle: { backgroundColor: '#ff0000', backgroundOpacity },
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
const viewModel = mapHeader(header);
|
|
647
|
+
|
|
648
|
+
expect(viewModel.backgroundStyle).not.toHaveProperty(
|
|
649
|
+
'backgroundOpacity',
|
|
650
|
+
);
|
|
651
|
+
expect(viewModel.backgroundStyle.opacity).toBeUndefined();
|
|
652
|
+
},
|
|
653
|
+
);
|
|
654
|
+
});
|
|
511
655
|
});
|
|
512
656
|
|
|
513
657
|
// The reCAPTCHA footnote colour is a raw colour anchored to the surface it sits on.
|
|
@@ -88,9 +88,10 @@ import type {
|
|
|
88
88
|
NavItemViewModel,
|
|
89
89
|
} from '../view-models';
|
|
90
90
|
import {
|
|
91
|
+
backgroundColorFromStyle,
|
|
91
92
|
isTransparentColour,
|
|
92
93
|
mutedTextForBackground,
|
|
93
|
-
|
|
94
|
+
withColorOpacity,
|
|
94
95
|
} from './color.util';
|
|
95
96
|
import { resolveColorReference } from './color-palette.util';
|
|
96
97
|
import {
|
|
@@ -1281,7 +1282,31 @@ export const mapToPageHeaderViewModel = (
|
|
|
1281
1282
|
hamburgerMenuStyle,
|
|
1282
1283
|
}),
|
|
1283
1284
|
style: pageHeader.style,
|
|
1284
|
-
backgroundStyle: pageHeader
|
|
1285
|
+
backgroundStyle: getHeaderBackgroundStyleWithOpacity(pageHeader),
|
|
1286
|
+
};
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
// Folds backgroundOpacity into backgroundColor's alpha so only the container's paint fades, not its children.
|
|
1290
|
+
const getHeaderBackgroundStyleWithOpacity = (
|
|
1291
|
+
pageHeader: PageHeaderDesktop | PageHeaderMobile,
|
|
1292
|
+
): BrandPageCssStyle => {
|
|
1293
|
+
// Real defaults: an un-constructed maker header, or metadata older than migration-11's backfill, can lack these entirely.
|
|
1294
|
+
const { backgroundOpacity = 100, ...backgroundStyle } =
|
|
1295
|
+
pageHeader.backgroundStyle ?? {};
|
|
1296
|
+
const { backgroundColor } = backgroundStyle;
|
|
1297
|
+
|
|
1298
|
+
if (
|
|
1299
|
+
backgroundOpacity >= 100 ||
|
|
1300
|
+
!backgroundColor ||
|
|
1301
|
+
isTransparentColour(backgroundColor)
|
|
1302
|
+
) {
|
|
1303
|
+
return backgroundStyle;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// withColorOpacity, not setRgbAlpha: fades a var(--palette-n) reference too.
|
|
1307
|
+
return {
|
|
1308
|
+
...backgroundStyle,
|
|
1309
|
+
backgroundColor: withColorOpacity(backgroundColor, backgroundOpacity / 100),
|
|
1285
1310
|
};
|
|
1286
1311
|
};
|
|
1287
1312
|
|
|
@@ -259,9 +259,11 @@ export interface PageHeaderViewModel extends Omit<
|
|
|
259
259
|
| 'logoTemplateImage'
|
|
260
260
|
| 'logoImageHeight'
|
|
261
261
|
| 'logoImageWidth'
|
|
262
|
+
| 'backgroundStyle'
|
|
262
263
|
> {
|
|
263
264
|
logo: LogoViewModel;
|
|
264
265
|
pages: PageViewModel[];
|
|
266
|
+
backgroundStyle: BrandPageCssStyle;
|
|
265
267
|
}
|
|
266
268
|
|
|
267
269
|
export interface PageFooterViewModel extends Omit<
|
|
@@ -48,7 +48,11 @@ import AssetLibraryClient from '../../../clients/asset-library-clients';
|
|
|
48
48
|
import { useShopConfigStore } from '@shared/ecommerce-settings/store/shop-settings-store';
|
|
49
49
|
import type { ShopConfigData } from '@brand-page/models/shop.model';
|
|
50
50
|
import { useProductConfigStore } from '@brand-page-maker/stores/product-config/product-config-store';
|
|
51
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
useEventTracker,
|
|
53
|
+
UPGRADE_CTA_LOCATIONS,
|
|
54
|
+
PREVIEW_CTA_LOCATIONS,
|
|
55
|
+
} from '../../composables/useEventTracker';
|
|
52
56
|
import { commonTr } from '@src/composables/useTranslate';
|
|
53
57
|
import { useSnapshot } from '../../../composables/useSnapshot';
|
|
54
58
|
import { usePageNavigationStore } from '@src/shared/page-navigation/store/page-navigation-store';
|
|
@@ -460,6 +464,10 @@ export default defineComponent({
|
|
|
460
464
|
this.$emit(EVENTS.Back);
|
|
461
465
|
},
|
|
462
466
|
onPreview() {
|
|
467
|
+
// Every click is counted, before the gating below — the modal-open event
|
|
468
|
+
// fires separately, so click → open reads as a funnel.
|
|
469
|
+
useEventTracker().sendPreviewClickEvent(PREVIEW_CTA_LOCATIONS.MAKER);
|
|
470
|
+
|
|
463
471
|
// The preview frames a SAVED, owned draft. A signed-out customer can't have
|
|
464
472
|
// one, and the save needs an owner — so sign-in comes first, via the host's
|
|
465
473
|
// Save Progress flow.
|
package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.preview-gate.test.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { describe, test, expect, vi } from 'vitest';
|
|
1
|
+
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import BrandPageMakerMixin from './BrandPageMaker.mixin';
|
|
3
3
|
import { EVENTS } from '../../../constants/brand-page-maker';
|
|
4
|
+
import {
|
|
5
|
+
useEventTracker,
|
|
6
|
+
EVENTS as TRACKER_EVENTS,
|
|
7
|
+
PREVIEW_CTA_LOCATIONS,
|
|
8
|
+
} from '@brand-page-maker/composables/useEventTracker';
|
|
4
9
|
|
|
5
10
|
// The preview frames a SAVED, owned draft, so who saves it decides when the modal
|
|
6
11
|
// may open. Signed out: nobody owns a draft, so hand off to the host's Save Progress
|
|
@@ -68,6 +73,38 @@ describe('BrandPageMaker.onPreview', () => {
|
|
|
68
73
|
});
|
|
69
74
|
});
|
|
70
75
|
|
|
76
|
+
describe('BrandPageMaker.onPreview click analytics', () => {
|
|
77
|
+
const received: { name: string; props: any }[] = [];
|
|
78
|
+
|
|
79
|
+
useEventTracker().subscribe((event) =>
|
|
80
|
+
received.push({ name: event.eventName, props: event.eventProps }),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
received.length = 0;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// The click is counted before the sign-in/save gating, so every branch —
|
|
88
|
+
// including ones where the modal never opens — reports the same event.
|
|
89
|
+
test.each([
|
|
90
|
+
['signed out', { isUserAuthenticated: false }],
|
|
91
|
+
[
|
|
92
|
+
'pre-purchase',
|
|
93
|
+
{ containerStore: { isPrePurchase: true, livePreviewUrl: undefined } },
|
|
94
|
+
],
|
|
95
|
+
['post-purchase', {}],
|
|
96
|
+
])('every click is counted: %s', (_, overrides) => {
|
|
97
|
+
onPreview.call(createContext(overrides as any));
|
|
98
|
+
|
|
99
|
+
expect(received).toEqual([
|
|
100
|
+
{
|
|
101
|
+
name: TRACKER_EVENTS.WEBSITE_MAKER_LIVE_PREVIEW_CLICK,
|
|
102
|
+
props: { cta_location: PREVIEW_CTA_LOCATIONS.MAKER },
|
|
103
|
+
},
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
71
108
|
describe('BrandPageMaker livePreviewUrl watcher', () => {
|
|
72
109
|
test('opens the preview once the host save hands back a URL', () => {
|
|
73
110
|
const context = createContext({
|
|
@@ -7,6 +7,12 @@ import {
|
|
|
7
7
|
useBrandPageContainerStore,
|
|
8
8
|
useBrandPageMakerStore,
|
|
9
9
|
} from '@brand-page-maker/stores';
|
|
10
|
+
import {
|
|
11
|
+
useEventTracker,
|
|
12
|
+
EVENTS,
|
|
13
|
+
PREVIEW_CTA_LOCATIONS,
|
|
14
|
+
PREVIEW_MODAL_CONTINUATION_EVENTS,
|
|
15
|
+
} from '@brand-page-maker/composables/useEventTracker';
|
|
10
16
|
|
|
11
17
|
const PREVIEW_URL = 'https://brand.site/_p/request?token=signed';
|
|
12
18
|
|
|
@@ -243,4 +249,121 @@ describe('PreviewModalWithState', () => {
|
|
|
243
249
|
|
|
244
250
|
expect(wrapper.emitted('edit')).toHaveLength(1);
|
|
245
251
|
});
|
|
252
|
+
|
|
253
|
+
describe('analytics', () => {
|
|
254
|
+
const received: { name: string; props: any }[] = [];
|
|
255
|
+
|
|
256
|
+
useEventTracker().subscribe((event) =>
|
|
257
|
+
received.push({ name: event.eventName, props: event.eventProps }),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const openModal = async (props: Record<string, unknown> = {}) => {
|
|
261
|
+
vi.spyOn(containerStore, 'saveBrandPage').mockResolvedValue({
|
|
262
|
+
saved: true,
|
|
263
|
+
canPublish: true,
|
|
264
|
+
} as any);
|
|
265
|
+
vi.spyOn(containerStore, 'showSaveSuccessMessage').mockResolvedValue(
|
|
266
|
+
undefined,
|
|
267
|
+
);
|
|
268
|
+
vi.spyOn(
|
|
269
|
+
useBrandPageMakerStore(),
|
|
270
|
+
'showNextNotificationMessage',
|
|
271
|
+
).mockResolvedValue(undefined);
|
|
272
|
+
|
|
273
|
+
const wrapper = shallowMount(PreviewModalWithState, {
|
|
274
|
+
props: { visible: false, ...props },
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await wrapper.setProps({ visible: true });
|
|
278
|
+
await flushPromises();
|
|
279
|
+
|
|
280
|
+
return wrapper;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
beforeEach(() => {
|
|
284
|
+
received.length = 0;
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('opening fires the modal event, stamped with the editor surface by default', async () => {
|
|
288
|
+
await openModal();
|
|
289
|
+
|
|
290
|
+
expect(received).toEqual([
|
|
291
|
+
{
|
|
292
|
+
name: EVENTS.WEBSITE_MAKER_LIVE_PREVIEW_MODAL,
|
|
293
|
+
props: {
|
|
294
|
+
cta_location: PREVIEW_CTA_LOCATIONS.MAKER,
|
|
295
|
+
continuation: null,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
]);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test('the hosting surface stamps its own cta_location', async () => {
|
|
302
|
+
await openModal({ ctaLocation: PREVIEW_CTA_LOCATIONS.MANAGE_WEBSITE });
|
|
303
|
+
|
|
304
|
+
expect(received).toEqual([
|
|
305
|
+
{
|
|
306
|
+
name: EVENTS.WEBSITE_MAKER_LIVE_PREVIEW_MODAL,
|
|
307
|
+
props: {
|
|
308
|
+
cta_location: PREVIEW_CTA_LOCATIONS.MANAGE_WEBSITE,
|
|
309
|
+
continuation: null,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
]);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test.each([
|
|
316
|
+
['close', PREVIEW_MODAL_CONTINUATION_EVENTS.EXIT],
|
|
317
|
+
// On the editor (cta=publish, the default) the button reads "Back to Editor".
|
|
318
|
+
['edit', PREVIEW_MODAL_CONTINUATION_EVENTS.BACK_TO_EDITOR],
|
|
319
|
+
['publish', PREVIEW_MODAL_CONTINUATION_EVENTS.PUBLISH],
|
|
320
|
+
])('%s continues the modal event as %s', async (emitName, continuation) => {
|
|
321
|
+
const wrapper = await openModal();
|
|
322
|
+
|
|
323
|
+
received.length = 0;
|
|
324
|
+
await wrapper.findComponent(LivePreviewModal).vm.$emit(emitName);
|
|
325
|
+
await flushPromises();
|
|
326
|
+
|
|
327
|
+
expect(received).toEqual([
|
|
328
|
+
{
|
|
329
|
+
name: EVENTS.WEBSITE_MAKER_LIVE_PREVIEW_MODAL,
|
|
330
|
+
props: { cta_location: PREVIEW_CTA_LOCATIONS.MAKER, continuation },
|
|
331
|
+
},
|
|
332
|
+
]);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// The manage page's button reads "Edit" (cta=edit), so its continuation is
|
|
336
|
+
// edit — the editor's back_to_editor case is covered above.
|
|
337
|
+
test('continuations carry the hosting surface and its label', async () => {
|
|
338
|
+
const wrapper = await openModal({
|
|
339
|
+
cta: 'edit',
|
|
340
|
+
ctaLocation: PREVIEW_CTA_LOCATIONS.MANAGE_WEBSITE,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
received.length = 0;
|
|
344
|
+
await wrapper.findComponent(LivePreviewModal).vm.$emit('edit');
|
|
345
|
+
await flushPromises();
|
|
346
|
+
|
|
347
|
+
expect(received).toEqual([
|
|
348
|
+
{
|
|
349
|
+
name: EVENTS.WEBSITE_MAKER_LIVE_PREVIEW_MODAL,
|
|
350
|
+
props: {
|
|
351
|
+
cta_location: PREVIEW_CTA_LOCATIONS.MANAGE_WEBSITE,
|
|
352
|
+
continuation: PREVIEW_MODAL_CONTINUATION_EVENTS.EDIT,
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
]);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Retry re-runs the save, it isn't a decision about the preview — not tracked.
|
|
359
|
+
test('retry is not tracked', async () => {
|
|
360
|
+
const wrapper = await openModal();
|
|
361
|
+
|
|
362
|
+
received.length = 0;
|
|
363
|
+
await wrapper.findComponent(LivePreviewModal).vm.$emit('retry');
|
|
364
|
+
await flushPromises();
|
|
365
|
+
|
|
366
|
+
expect(received).toEqual([]);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
246
369
|
});
|
|
@@ -27,18 +27,26 @@ import {
|
|
|
27
27
|
useBrandPageContainerStore,
|
|
28
28
|
useBrandPageMakerStore,
|
|
29
29
|
} from '@brand-page-maker/stores';
|
|
30
|
+
import {
|
|
31
|
+
useEventTracker,
|
|
32
|
+
PREVIEW_CTA_LOCATIONS,
|
|
33
|
+
PREVIEW_MODAL_CONTINUATION_EVENTS,
|
|
34
|
+
} from '@brand-page-maker/composables/useEventTracker';
|
|
30
35
|
import type { UiDeviceMode } from '@shared/models';
|
|
31
36
|
|
|
32
37
|
const props = defineProps({
|
|
33
38
|
visible: { type: Boolean, default: false },
|
|
34
39
|
// Per-surface header CTA; defaults to 'publish' so the editor is unchanged.
|
|
35
40
|
cta: { type: String as PropType<LivePreviewCta>, default: 'publish' },
|
|
41
|
+
// Which surface hosts the preview — stamped on its analytics as cta_location.
|
|
42
|
+
ctaLocation: { type: String, default: PREVIEW_CTA_LOCATIONS.MAKER },
|
|
36
43
|
});
|
|
37
44
|
|
|
38
45
|
const emit = defineEmits<{ close: []; publish: []; edit: [] }>();
|
|
39
46
|
|
|
40
47
|
const containerStore = useBrandPageContainerStore();
|
|
41
48
|
const makerStore = useBrandPageMakerStore();
|
|
49
|
+
const eventTracker = useEventTracker();
|
|
42
50
|
|
|
43
51
|
// The preview must step back for any modal opening over the editor.
|
|
44
52
|
const isFlowModalOpen = computed(() => containerStore.isAnyModalVisible);
|
|
@@ -96,18 +104,34 @@ const announceExit = async (): Promise<void> => {
|
|
|
96
104
|
};
|
|
97
105
|
|
|
98
106
|
const onClose = async (): Promise<void> => {
|
|
107
|
+
// Escape funnels through the shell's close emit too, so it counts as an exit.
|
|
108
|
+
eventTracker.sendPreviewModalContinuationEvent(
|
|
109
|
+
PREVIEW_MODAL_CONTINUATION_EVENTS.EXIT,
|
|
110
|
+
props.ctaLocation,
|
|
111
|
+
);
|
|
99
112
|
emit('close');
|
|
100
113
|
|
|
101
114
|
await announceExit();
|
|
102
115
|
};
|
|
103
116
|
|
|
104
117
|
const onEdit = async (): Promise<void> => {
|
|
118
|
+
// Mirror the rendered label: cta=publish shows "Back to Editor", else "Edit".
|
|
119
|
+
eventTracker.sendPreviewModalContinuationEvent(
|
|
120
|
+
props.cta === 'publish'
|
|
121
|
+
? PREVIEW_MODAL_CONTINUATION_EVENTS.BACK_TO_EDITOR
|
|
122
|
+
: PREVIEW_MODAL_CONTINUATION_EVENTS.EDIT,
|
|
123
|
+
props.ctaLocation,
|
|
124
|
+
);
|
|
105
125
|
emit('edit');
|
|
106
126
|
|
|
107
127
|
await announceExit();
|
|
108
128
|
};
|
|
109
129
|
|
|
110
130
|
const onPublish = (): void => {
|
|
131
|
+
eventTracker.sendPreviewModalContinuationEvent(
|
|
132
|
+
PREVIEW_MODAL_CONTINUATION_EVENTS.PUBLISH,
|
|
133
|
+
props.ctaLocation,
|
|
134
|
+
);
|
|
111
135
|
// Route through the host's publish flow so pre-purchase still reaches checkout.
|
|
112
136
|
publishing.value = true;
|
|
113
137
|
emit('publish');
|
|
@@ -117,6 +141,7 @@ watch(
|
|
|
117
141
|
() => props.visible,
|
|
118
142
|
(isVisible) => {
|
|
119
143
|
if (isVisible) {
|
|
144
|
+
eventTracker.sendPreviewModalOpenEvent(props.ctaLocation);
|
|
120
145
|
openPreview();
|
|
121
146
|
} else {
|
|
122
147
|
previewUrl.value = '';
|
|
@@ -74,6 +74,24 @@ export const EVENTS = {
|
|
|
74
74
|
WEBSITE_MAKER_REDO: 'website_maker_redo',
|
|
75
75
|
WEBSITE_MAKER_HISTORY_CAPPED: 'website_maker_history_capped',
|
|
76
76
|
WEBSITE_MAKER_HISTORY_OVERSIZED: 'website_maker_history_oversized',
|
|
77
|
+
WEBSITE_MAKER_LIVE_PREVIEW_CLICK: 'website_maker_live_preview_click',
|
|
78
|
+
WEBSITE_MAKER_LIVE_PREVIEW_MODAL: 'website_maker_live_preview_modal',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// The global `location` prop already means pre/post purchase, so the surface a
|
|
82
|
+
// preview was launched from rides on cta_location, reusing existing values.
|
|
83
|
+
export const PREVIEW_CTA_LOCATIONS = {
|
|
84
|
+
MAKER: 'maker',
|
|
85
|
+
MANAGE_WEBSITE: 'manage_website',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const PREVIEW_MODAL_CONTINUATION_EVENTS = {
|
|
89
|
+
EXIT: 'exit',
|
|
90
|
+
// The same button reads "Back to Editor" on the editor and "Edit" on other
|
|
91
|
+
// surfaces — the continuation mirrors the label the user actually clicked.
|
|
92
|
+
BACK_TO_EDITOR: 'back_to_editor',
|
|
93
|
+
EDIT: 'edit',
|
|
94
|
+
PUBLISH: 'publish',
|
|
77
95
|
};
|
|
78
96
|
|
|
79
97
|
export const SELECT_LOCATIONS = {
|
|
@@ -426,6 +444,29 @@ const useEventTracker = () => {
|
|
|
426
444
|
is_enabled: isEnabled,
|
|
427
445
|
});
|
|
428
446
|
},
|
|
447
|
+
// Live preview: the click is tracked separately from the modal opening —
|
|
448
|
+
// sign-in/save gating means a click doesn't always open the modal, and the
|
|
449
|
+
// post-sign-in return trip opens it without a click.
|
|
450
|
+
sendPreviewClickEvent: (ctaLocation: string) => {
|
|
451
|
+
_publishEvent(EVENTS.WEBSITE_MAKER_LIVE_PREVIEW_CLICK, {
|
|
452
|
+
cta_location: ctaLocation,
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
sendPreviewModalOpenEvent: (ctaLocation: string) => {
|
|
456
|
+
_publishEvent(EVENTS.WEBSITE_MAKER_LIVE_PREVIEW_MODAL, {
|
|
457
|
+
cta_location: ctaLocation,
|
|
458
|
+
continuation: null,
|
|
459
|
+
});
|
|
460
|
+
},
|
|
461
|
+
sendPreviewModalContinuationEvent: (
|
|
462
|
+
continuation: string,
|
|
463
|
+
ctaLocation: string,
|
|
464
|
+
) => {
|
|
465
|
+
_publishEvent(EVENTS.WEBSITE_MAKER_LIVE_PREVIEW_MODAL, {
|
|
466
|
+
cta_location: ctaLocation,
|
|
467
|
+
continuation,
|
|
468
|
+
});
|
|
469
|
+
},
|
|
429
470
|
// Undo / Redo (BP-5506)
|
|
430
471
|
sendUndoEvent: (props: {
|
|
431
472
|
entries_count: number;
|
|
@@ -1841,6 +1841,7 @@
|
|
|
1841
1841
|
"headerSettings": "Kopfzeilen-Einstellungen",
|
|
1842
1842
|
"navigationSectionTitle": "Navigation",
|
|
1843
1843
|
"headerSectionTitle": "Header",
|
|
1844
|
+
"backgroundOpacity": "Hintergrunddeckkraft",
|
|
1844
1845
|
"buttonSectionTitle": "Schaltfläche",
|
|
1845
1846
|
"footerSettings": "Fußzeilen-Einstellungen",
|
|
1846
1847
|
"managePages": "Seiten verwalten",
|
|
@@ -1841,6 +1841,7 @@
|
|
|
1841
1841
|
"headerSettings": "Configuración del encabezado",
|
|
1842
1842
|
"navigationSectionTitle": "Navegación",
|
|
1843
1843
|
"headerSectionTitle": "Encabezado",
|
|
1844
|
+
"backgroundOpacity": "Opacidad del fondo",
|
|
1844
1845
|
"buttonSectionTitle": "Botón",
|
|
1845
1846
|
"footerSettings": "Configuración del pie de página",
|
|
1846
1847
|
"managePages": "Gestionar páginas",
|
|
@@ -1841,6 +1841,7 @@
|
|
|
1841
1841
|
"headerSettings": "Paramètres d'en-tête",
|
|
1842
1842
|
"navigationSectionTitle": "Navigation",
|
|
1843
1843
|
"headerSectionTitle": "En-tête",
|
|
1844
|
+
"backgroundOpacity": "Opacité de l'arrière-plan",
|
|
1844
1845
|
"buttonSectionTitle": "Bouton",
|
|
1845
1846
|
"footerSettings": "Paramètres de pied de page",
|
|
1846
1847
|
"managePages": "Gérer les pages",
|
|
@@ -1841,6 +1841,7 @@
|
|
|
1841
1841
|
"headerSettings": "Paramètres d'en-tête",
|
|
1842
1842
|
"navigationSectionTitle": "Navigation",
|
|
1843
1843
|
"headerSectionTitle": "En-tête",
|
|
1844
|
+
"backgroundOpacity": "Opacité de l'arrière-plan",
|
|
1844
1845
|
"buttonSectionTitle": "Bouton",
|
|
1845
1846
|
"footerSettings": "Paramètres de pied de page",
|
|
1846
1847
|
"managePages": "Gérer les pages",
|
|
@@ -1841,6 +1841,7 @@
|
|
|
1841
1841
|
"headerSettings": "Header Settings",
|
|
1842
1842
|
"navigationSectionTitle": "Navigation",
|
|
1843
1843
|
"headerSectionTitle": "Header",
|
|
1844
|
+
"backgroundOpacity": "Background opacity",
|
|
1844
1845
|
"buttonSectionTitle": "Button",
|
|
1845
1846
|
"footerSettings": "Footer Settings",
|
|
1846
1847
|
"managePages": "Manage Pages",
|
|
@@ -1841,6 +1841,7 @@
|
|
|
1841
1841
|
"headerSettings": "Configurações do cabeçalho",
|
|
1842
1842
|
"navigationSectionTitle": "Navegação",
|
|
1843
1843
|
"headerSectionTitle": "Cabeçalho",
|
|
1844
|
+
"backgroundOpacity": "Opacidade do plano de fundo",
|
|
1844
1845
|
"buttonSectionTitle": "Botão",
|
|
1845
1846
|
"footerSettings": "Definições de rodapé",
|
|
1846
1847
|
"managePages": "Gerenciar páginas",
|
|
@@ -1841,6 +1841,7 @@
|
|
|
1841
1841
|
"headerSettings": "Definições de cabeçalho",
|
|
1842
1842
|
"navigationSectionTitle": "Navegação",
|
|
1843
1843
|
"headerSectionTitle": "Cabeçalho",
|
|
1844
|
+
"backgroundOpacity": "Opacidade do fundo",
|
|
1844
1845
|
"buttonSectionTitle": "Botão",
|
|
1845
1846
|
"footerSettings": "Definições de rodapé",
|
|
1846
1847
|
"managePages": "Gerir páginas",
|