mirror of
https://github.com/netbirdio/docs.git
synced 2026-10-26 22:49:04 +01:00
Change cookie popup behavior (#849)
This commit is contained in:
@@ -6,6 +6,7 @@ import { Transition } from '@headlessui/react'
|
|||||||
import { Button } from '@/components/Button'
|
import { Button } from '@/components/Button'
|
||||||
import {apiNavigation, flattenNavItems} from '@/components/NavigationAPI'
|
import {apiNavigation, flattenNavItems} from '@/components/NavigationAPI'
|
||||||
import {docsNavigation} from "@/components/NavigationDocs";
|
import {docsNavigation} from "@/components/NavigationDocs";
|
||||||
|
import {useCookieConsent} from '@/components/cookie-consent/CookieConsentProvider'
|
||||||
|
|
||||||
function CheckIcon(props) {
|
function CheckIcon(props) {
|
||||||
return (
|
return (
|
||||||
@@ -227,11 +228,20 @@ function SocialLink({ href, icon: Icon, children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SmallPrint() {
|
function SmallPrint() {
|
||||||
|
const { openCookieSettings } = useCookieConsent()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-between gap-5 border-t border-zinc-900/5 pt-8 dark:border-white/5 sm:flex-row">
|
<div className="flex flex-col items-center justify-between gap-5 border-t border-zinc-900/5 pt-8 dark:border-white/5 sm:flex-row">
|
||||||
<p className="text-xs text-zinc-600 dark:text-zinc-400">
|
<div className="flex items-center gap-4 text-xs text-zinc-600 dark:text-zinc-400">
|
||||||
© Copyright {new Date().getFullYear()}. All rights reserved.
|
<p>© Copyright {new Date().getFullYear()}. All rights reserved.</p>
|
||||||
</p>
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={openCookieSettings}
|
||||||
|
className="underline underline-offset-4 transition hover:text-zinc-900 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
Cookie Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<SocialLink href="https://x.com/netbird" icon={TwitterIcon}>
|
<SocialLink href="https://x.com/netbird" icon={TwitterIcon}>
|
||||||
Follow us on X
|
Follow us on X
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ import Script from "next/script";
|
|||||||
// Google Tag Manager ID
|
// Google Tag Manager ID
|
||||||
const GTM_ID = "GTM-PGWDPDN3";
|
const GTM_ID = "GTM-PGWDPDN3";
|
||||||
|
|
||||||
export const GoogleTagManagerHeadScript = () => {
|
export const GoogleTagManager = ({ consentGiven }) => {
|
||||||
|
if (!consentGiven) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Script id="gtm-script" strategy="afterInteractive">
|
<Script id="gtm-script" strategy="afterInteractive">
|
||||||
{`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
{`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||||
@@ -15,17 +19,3 @@ export const GoogleTagManagerHeadScript = () => {
|
|||||||
</Script>
|
</Script>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GoogleTageManagerBodyScript = () => {
|
|
||||||
return (
|
|
||||||
<noscript>
|
|
||||||
<iframe
|
|
||||||
title={"Google Tag Manager"}
|
|
||||||
src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
|
|
||||||
height="0"
|
|
||||||
width="0"
|
|
||||||
style={{ display: "none", visibility: "hidden" }}
|
|
||||||
/>
|
|
||||||
</noscript>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,16 +1,50 @@
|
|||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
export function MatomoTagManager({ consentGiven }) {
|
export function MatomoTagManager({ consentGiven }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const isFirstNavigation = useRef(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!consentGiven) return
|
||||||
|
|
||||||
|
// The container tracks the initial hard load. Subsequent Next.js routes
|
||||||
|
// are emitted after the new document title has committed so Matomo does
|
||||||
|
// not associate the previous page's title with the new URL.
|
||||||
|
if (isFirstNavigation.current) {
|
||||||
|
isFirstNavigation.current = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = window.setTimeout(() => {
|
||||||
|
window._mtm = window._mtm || []
|
||||||
|
window._mtm.push({
|
||||||
|
event: 'mtm.SpaPageView',
|
||||||
|
'mtm.pageUrl': window.location.href,
|
||||||
|
'mtm.pageTitle': document.title,
|
||||||
|
})
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return () => window.clearTimeout(id)
|
||||||
|
}, [consentGiven, router.asPath])
|
||||||
|
|
||||||
|
if (!consentGiven) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Script id="matomo-tag-manager" strategy="afterInteractive">
|
<Script id="matomo-tag-manager" strategy="afterInteractive">
|
||||||
{`var _paq = window._paq = window._paq || [];
|
{`var _paq = window._paq = window._paq || [];
|
||||||
_paq.push(['requireCookieConsent']);
|
_paq.push(['requireConsent']);
|
||||||
${consentGiven ? "_paq.push(['setCookieConsentGiven']);" : ""}
|
|
||||||
var _mtm = window._mtm = window._mtm || [];
|
var _mtm = window._mtm = window._mtm || [];
|
||||||
_mtm.push({'mtm.startTime': (new Date().getTime()), 'event': 'mtm.Start'});
|
_mtm.push({'mtm.startTime': (new Date().getTime()), 'event': 'mtm.Start'});
|
||||||
(function() {
|
(function() {
|
||||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||||
g.async=true; g.src='https://cdn.matomo.cloud/netbird.matomo.cloud/container_hvVzPZGH.js'; s.parentNode.insertBefore(g,s);
|
g.async=true;
|
||||||
|
g.src='https://cdn.matomo.cloud/netbird.matomo.cloud/container_hvVzPZGH.js';
|
||||||
|
g.onload=function(){window._paq.push(['setConsentGiven']);};
|
||||||
|
s.parentNode.insertBefore(g,s);
|
||||||
})();`}
|
})();`}
|
||||||
</Script>
|
</Script>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,14 +2,28 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'rea
|
|||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
const STORAGE_KEY = 'cookie-consent'
|
const STORAGE_KEY = 'cookie-consent'
|
||||||
const ACCEPT_EXPIRY_DAYS = 90
|
const CONSENT_EXPIRY_DAYS = 180
|
||||||
const DECLINE_EXPIRY_DAYS = 1
|
|
||||||
|
const TRACKING_COOKIE_PREFIXES = [
|
||||||
|
'_ga',
|
||||||
|
'_gid',
|
||||||
|
'_hj',
|
||||||
|
'_clck',
|
||||||
|
'_clsk',
|
||||||
|
'_gcl_',
|
||||||
|
'_pk_',
|
||||||
|
'__hst',
|
||||||
|
'hubspotutk',
|
||||||
|
'messagesUtk',
|
||||||
|
]
|
||||||
|
|
||||||
const CookieConsentContext = createContext({
|
const CookieConsentContext = createContext({
|
||||||
isAccepted: false,
|
isAccepted: false,
|
||||||
|
isDeclined: false,
|
||||||
showConsent: false,
|
showConsent: false,
|
||||||
acceptCookies: () => {},
|
acceptCookies: () => {},
|
||||||
declineCookies: () => {},
|
declineCookies: () => {},
|
||||||
|
openCookieSettings: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
function getStoredConsent() {
|
function getStoredConsent() {
|
||||||
@@ -40,9 +54,34 @@ function storeConsent(value, days) {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeTrackingCookies() {
|
||||||
|
const cookieNames = document.cookie
|
||||||
|
.split(';')
|
||||||
|
.map((cookie) => cookie.split('=')[0].trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
for (const name of cookieNames) {
|
||||||
|
if (!TRACKING_COOKIE_PREFIXES.some((prefix) => name.startsWith(prefix))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiredCookie = `${name}=; Max-Age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax`
|
||||||
|
document.cookie = expiredCookie
|
||||||
|
document.cookie = `${expiredCookie}; domain=${window.location.hostname}`
|
||||||
|
if (
|
||||||
|
window.location.hostname === 'netbird.io' ||
|
||||||
|
window.location.hostname.endsWith('.netbird.io')
|
||||||
|
) {
|
||||||
|
document.cookie = `${expiredCookie}; domain=.netbird.io`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function CookieConsentProvider({ children }) {
|
export function CookieConsentProvider({ children }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [consent, setConsent] = useState(() => getStoredConsent())
|
// Resolve browser storage after hydration so the server and the first client
|
||||||
|
// render agree. Analytics remains disabled while the choice is loading.
|
||||||
|
const [consent, setConsent] = useState(null)
|
||||||
const [showConsent, setShowConsent] = useState(false)
|
const [showConsent, setShowConsent] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -59,32 +98,44 @@ export function CookieConsentProvider({ children }) {
|
|||||||
}, [router.pathname])
|
}, [router.pathname])
|
||||||
|
|
||||||
const acceptCookies = useCallback(() => {
|
const acceptCookies = useCallback(() => {
|
||||||
storeConsent('accepted', ACCEPT_EXPIRY_DAYS)
|
storeConsent('accepted', CONSENT_EXPIRY_DAYS)
|
||||||
setConsent('accepted')
|
setConsent('accepted')
|
||||||
setShowConsent(false)
|
setShowConsent(false)
|
||||||
|
|
||||||
// Enable Matomo cookies
|
|
||||||
window._paq = window._paq || []
|
|
||||||
window._paq.push(['setCookieConsentGiven'])
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const declineCookies = useCallback(() => {
|
const declineCookies = useCallback(() => {
|
||||||
storeConsent('declined', DECLINE_EXPIRY_DAYS)
|
const trackersWereActive = consent === 'accepted'
|
||||||
|
|
||||||
|
storeConsent('declined', CONSENT_EXPIRY_DAYS)
|
||||||
setConsent('declined')
|
setConsent('declined')
|
||||||
setShowConsent(false)
|
setShowConsent(false)
|
||||||
|
|
||||||
// Tell Matomo to forget consent and delete its cookies
|
removeTrackingCookies()
|
||||||
window._paq = window._paq || []
|
|
||||||
window._paq.push(['forgetCookieConsentGiven'])
|
// Scripts already loaded by an accepted visitor cannot be reliably
|
||||||
|
// unloaded. Reload into the persisted declined state so no optional
|
||||||
|
// analytics script is mounted again.
|
||||||
|
if (trackersWereActive) {
|
||||||
|
window._paq = window._paq || []
|
||||||
|
window._paq.push(['forgetConsentGiven'])
|
||||||
|
window._paq.push(['deleteCookies'])
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
}, [consent])
|
||||||
|
|
||||||
|
const openCookieSettings = useCallback(() => {
|
||||||
|
setShowConsent(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CookieConsentContext.Provider
|
<CookieConsentContext.Provider
|
||||||
value={{
|
value={{
|
||||||
isAccepted: consent === 'accepted',
|
isAccepted: consent === 'accepted',
|
||||||
|
isDeclined: consent === 'declined',
|
||||||
showConsent,
|
showConsent,
|
||||||
acceptCookies,
|
acceptCookies,
|
||||||
declineCookies,
|
declineCookies,
|
||||||
|
openCookieSettings,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {dom} from "@fortawesome/fontawesome-svg-core";
|
|||||||
import {AnnouncementBannerProvider} from "@/components/announcement-banner/AnnouncementBannerProvider";
|
import {AnnouncementBannerProvider} from "@/components/announcement-banner/AnnouncementBannerProvider";
|
||||||
import {ImageZoom} from "@/components/ImageZoom";
|
import {ImageZoom} from "@/components/ImageZoom";
|
||||||
import {MatomoTagManager} from "@/components/Matomo";
|
import {MatomoTagManager} from "@/components/Matomo";
|
||||||
|
import {GoogleTagManager} from "@/components/GoogleTagManager";
|
||||||
import {CookieConsentProvider, useCookieConsent} from "@/components/cookie-consent/CookieConsentProvider";
|
import {CookieConsentProvider, useCookieConsent} from "@/components/cookie-consent/CookieConsentProvider";
|
||||||
import {CookieConsent} from "@/components/cookie-consent/CookieConsent";
|
import {CookieConsent} from "@/components/cookie-consent/CookieConsent";
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ function AppInner({ Component, pageProps }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MatomoTagManager consentGiven={isAccepted} />
|
<MatomoTagManager consentGiven={isAccepted} />
|
||||||
|
<GoogleTagManager consentGiven={isAccepted} />
|
||||||
<Head>
|
<Head>
|
||||||
<style>{dom.css()}</style>
|
<style>{dom.css()}</style>
|
||||||
{router.route.startsWith('/ipa') ?
|
{router.route.startsWith('/ipa') ?
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Head, Html, Main, NextScript } from 'next/document'
|
import { Head, Html, Main, NextScript } from 'next/document'
|
||||||
import {GoogleTageManagerBodyScript, GoogleTagManagerHeadScript} from "@/components/GoogleTagManager";
|
|
||||||
|
|
||||||
const modeScript = `
|
const modeScript = `
|
||||||
let darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
let darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
@@ -40,12 +39,10 @@ export default function Document() {
|
|||||||
return (
|
return (
|
||||||
<Html lang="en">
|
<Html lang="en">
|
||||||
<Head>
|
<Head>
|
||||||
<GoogleTagManagerHeadScript />
|
|
||||||
<script dangerouslySetInnerHTML={{ __html: modeScript }} />
|
<script dangerouslySetInnerHTML={{ __html: modeScript }} />
|
||||||
<link rel="shortcut icon" href="/docs-static/img/favicon.ico" />
|
<link rel="shortcut icon" href="/docs-static/img/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
<body className="bg-white antialiased dark:bg-[#181A1D]">
|
<body className="bg-white antialiased dark:bg-[#181A1D]">
|
||||||
<GoogleTageManagerBodyScript />
|
|
||||||
<Main />
|
<Main />
|
||||||
<NextScript />
|
<NextScript />
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user