Skip to content

Commit 13c05f2

Browse files
authored
[docs] Add analytics and close banner btn (#4005)
## Description Adds a dedicated GTM container for banner tracking and introduces a close option in docs with localStorage persistence. These changes will be replaced by the upcoming banner system in the future.
1 parent 64ba931 commit 13c05f2

3 files changed

Lines changed: 110 additions & 4 deletions

File tree

packages/docs-gesture-handler/src/components/TopPromoRotator/index.tsx

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import clsx from 'clsx';
2-
import React, { type ReactNode, useEffect, useMemo, useState } from 'react';
2+
import { type ReactNode, useEffect, useMemo, useState } from 'react';
33

44
import HandIcon from '../HandIcon';
55
import styles from './styles.module.css';
66

7+
declare global {
8+
interface Window {
9+
dataLayer?: unknown[];
10+
}
11+
}
12+
713
type Promo = {
814
key: string;
915
href: string;
@@ -45,11 +51,44 @@ const PROMOS: readonly Promo[] = [
4551
},
4652
];
4753

48-
export default function TopPromoRotator() {
54+
// bump when adding promos so users who dismissed banner see the new one
55+
export const PROMO_VERSION = 1;
56+
57+
type Props = {
58+
onClose?: () => void;
59+
};
60+
61+
export default function TopPromoRotator({ onClose }: Props) {
4962
const promos = useMemo(() => PROMOS, []);
5063

5164
const [index, setIndex] = useState(0);
5265

66+
useEffect(() => {
67+
if (typeof window === 'undefined' || typeof document === 'undefined') {
68+
return;
69+
}
70+
71+
const existingScript = document.querySelector<HTMLScriptElement>(
72+
'script[src*="www.googletagmanager.com/gtm.js?id=GTM-WV2G3SQL"]'
73+
);
74+
75+
if (existingScript) return;
76+
77+
(function (w: Window, d: Document, s: string, l: string, i: string) {
78+
w.dataLayer = w.dataLayer || [];
79+
w.dataLayer.push({
80+
'gtm.start': new Date().getTime(),
81+
event: 'gtm.js',
82+
});
83+
const f = d.getElementsByTagName(s)[0] as HTMLScriptElement;
84+
const j = d.createElement(s) as HTMLScriptElement;
85+
const dl = l !== 'dataLayer' ? `&l=${l}` : '';
86+
j.async = true;
87+
j.src = `https://www.googletagmanager.com/gtm.js?id=${i}${dl}`;
88+
f.parentNode?.insertBefore(j, f);
89+
})(window, document, 'script', 'dataLayer', 'GTM-WV2G3SQL');
90+
}, []);
91+
5392
useEffect(() => {
5493
const id = window.setInterval(() => {
5594
setIndex(i => (i + 1) % promos.length);
@@ -89,6 +128,15 @@ export default function TopPromoRotator() {
89128
</a>
90129
))}
91130
</div>
131+
{onClose && (
132+
<button
133+
type="button"
134+
className={styles.closeButton}
135+
aria-label="Close promotion banner"
136+
onClick={onClose}>
137+
×
138+
</button>
139+
)}
92140
<span className="sr-only">
93141
{typeof active.label === 'string' ? active.label : ''}
94142
</span>

packages/docs-gesture-handler/src/components/TopPromoRotator/styles.module.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,17 @@
7575
text-decoration: underline;
7676
text-underline-offset: 2px;
7777
}
78+
79+
.closeButton {
80+
position: absolute;
81+
right: 8px;
82+
top: 50%;
83+
transform: translateY(-50%);
84+
border: none;
85+
background: transparent;
86+
color: #001a72;
87+
cursor: pointer;
88+
padding: 4px;
89+
line-height: 1;
90+
font-size: 16px;
91+
}

packages/docs-gesture-handler/src/theme/Navbar/index.js

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,48 @@
11
import React from 'react';
22
import useBaseUrl from '@docusaurus/useBaseUrl';
3+
import { useLocation } from '@docusaurus/router';
34
import { Navbar } from '@swmansion/t-rex-ui';
4-
import TopPromoRotator from '@site/src/components/TopPromoRotator';
5+
import TopPromoRotator, {
6+
PROMO_VERSION,
7+
} from '@site/src/components/TopPromoRotator';
58

69
export default function NavbarWrapper(props) {
10+
const location = useLocation();
11+
const baseUrl = useBaseUrl('/');
12+
const isLanding = location.pathname === baseUrl;
13+
14+
const [showPromo, setShowPromo] = React.useState(true);
15+
16+
React.useEffect(() => {
17+
if (isLanding || typeof globalThis === 'undefined') {
18+
return;
19+
}
20+
21+
try {
22+
const raw = globalThis.localStorage?.getItem('topPromoState');
23+
const state = raw ? JSON.parse(raw) : null;
24+
if (state?.v === PROMO_VERSION && state?.hidden) {
25+
setShowPromo(false);
26+
}
27+
} catch (_) {
28+
// ignore
29+
}
30+
}, [isLanding]);
31+
32+
const handleClosePromo = React.useCallback(() => {
33+
setShowPromo(false);
34+
if (typeof globalThis !== 'undefined') {
35+
try {
36+
globalThis.localStorage?.setItem(
37+
'topPromoState',
38+
JSON.stringify({ v: PROMO_VERSION, hidden: true })
39+
);
40+
} catch {
41+
// ignore
42+
}
43+
}
44+
}, []);
45+
746
const titleImages = {
847
light: useBaseUrl('/img/title.svg'),
948
dark: useBaseUrl('/img/title-dark.svg'),
@@ -12,9 +51,14 @@ export default function NavbarWrapper(props) {
1251
const heroImages = {
1352
logo: useBaseUrl('/img/logo-hero.svg'),
1453
};
54+
1555
return (
1656
<div style={{ display: 'flex', flexDirection: 'column', flexShrink: 0 }}>
17-
<TopPromoRotator />
57+
{isLanding ? (
58+
<TopPromoRotator />
59+
) : (
60+
showPromo && <TopPromoRotator onClose={handleClosePromo} />
61+
)}
1862
<Navbar heroImages={heroImages} titleImages={titleImages} {...props} />
1963
</div>
2064
);

0 commit comments

Comments
 (0)