mirror of
https://github.com/netbirdio/docs.git
synced 2026-08-25 01:01:27 +02: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 {apiNavigation, flattenNavItems} from '@/components/NavigationAPI'
|
||||
import {docsNavigation} from "@/components/NavigationDocs";
|
||||
import {useCookieConsent} from '@/components/cookie-consent/CookieConsentProvider'
|
||||
|
||||
function CheckIcon(props) {
|
||||
return (
|
||||
@@ -227,11 +228,20 @@ function SocialLink({ href, icon: Icon, children }) {
|
||||
}
|
||||
|
||||
function SmallPrint() {
|
||||
const { openCookieSettings } = useCookieConsent()
|
||||
|
||||
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">
|
||||
<p className="text-xs text-zinc-600 dark:text-zinc-400">
|
||||
© Copyright {new Date().getFullYear()}. All rights reserved.
|
||||
</p>
|
||||
<div className="flex items-center gap-4 text-xs text-zinc-600 dark:text-zinc-400">
|
||||
<p>© Copyright {new Date().getFullYear()}. All rights reserved.</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">
|
||||
<SocialLink href="https://x.com/netbird" icon={TwitterIcon}>
|
||||
Follow us on X
|
||||
|
||||
@@ -4,10 +4,14 @@ import Script from "next/script";
|
||||
// Google Tag Manager ID
|
||||
const GTM_ID = "GTM-PGWDPDN3";
|
||||
|
||||
export const GoogleTagManagerHeadScript = () => {
|
||||
export const GoogleTagManager = ({ consentGiven }) => {
|
||||
if (!consentGiven) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<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],
|
||||
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);
|
||||
@@ -15,17 +19,3 @@ export const GoogleTagManagerHeadScript = () => {
|
||||
</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 { useEffect, useRef } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
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 (
|
||||
<Script id="matomo-tag-manager" strategy="afterInteractive">
|
||||
{`var _paq = window._paq = window._paq || [];
|
||||
_paq.push(['requireCookieConsent']);
|
||||
${consentGiven ? "_paq.push(['setCookieConsentGiven']);" : ""}
|
||||
_paq.push(['requireConsent']);
|
||||
var _mtm = window._mtm = window._mtm || [];
|
||||
_mtm.push({'mtm.startTime': (new Date().getTime()), 'event': 'mtm.Start'});
|
||||
(function() {
|
||||
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>
|
||||
);
|
||||
|
||||
@@ -2,14 +2,28 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'rea
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
const STORAGE_KEY = 'cookie-consent'
|
||||
const ACCEPT_EXPIRY_DAYS = 90
|
||||
const DECLINE_EXPIRY_DAYS = 1
|
||||
const CONSENT_EXPIRY_DAYS = 180
|
||||
|
||||
const TRACKING_COOKIE_PREFIXES = [
|
||||
'_ga',
|
||||
'_gid',
|
||||
'_hj',
|
||||
'_clck',
|
||||
'_clsk',
|
||||
'_gcl_',
|
||||
'_pk_',
|
||||
'__hst',
|
||||
'hubspotutk',
|
||||
'messagesUtk',
|
||||
]
|
||||
|
||||
const CookieConsentContext = createContext({
|
||||
isAccepted: false,
|
||||
isDeclined: false,
|
||||
showConsent: false,
|
||||
acceptCookies: () => {},
|
||||
declineCookies: () => {},
|
||||
openCookieSettings: () => {},
|
||||
})
|
||||
|
||||
function getStoredConsent() {
|
||||
@@ -40,9 +54,34 @@ function storeConsent(value, days) {
|
||||
} 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 }) {
|
||||
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)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -59,32 +98,44 @@ export function CookieConsentProvider({ children }) {
|
||||
}, [router.pathname])
|
||||
|
||||
const acceptCookies = useCallback(() => {
|
||||
storeConsent('accepted', ACCEPT_EXPIRY_DAYS)
|
||||
storeConsent('accepted', CONSENT_EXPIRY_DAYS)
|
||||
setConsent('accepted')
|
||||
setShowConsent(false)
|
||||
|
||||
// Enable Matomo cookies
|
||||
window._paq = window._paq || []
|
||||
window._paq.push(['setCookieConsentGiven'])
|
||||
}, [])
|
||||
|
||||
const declineCookies = useCallback(() => {
|
||||
storeConsent('declined', DECLINE_EXPIRY_DAYS)
|
||||
const trackersWereActive = consent === 'accepted'
|
||||
|
||||
storeConsent('declined', CONSENT_EXPIRY_DAYS)
|
||||
setConsent('declined')
|
||||
setShowConsent(false)
|
||||
|
||||
// Tell Matomo to forget consent and delete its cookies
|
||||
window._paq = window._paq || []
|
||||
window._paq.push(['forgetCookieConsentGiven'])
|
||||
removeTrackingCookies()
|
||||
|
||||
// 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 (
|
||||
<CookieConsentContext.Provider
|
||||
value={{
|
||||
isAccepted: consent === 'accepted',
|
||||
isDeclined: consent === 'declined',
|
||||
showConsent,
|
||||
acceptCookies,
|
||||
declineCookies,
|
||||
openCookieSettings,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {dom} from "@fortawesome/fontawesome-svg-core";
|
||||
import {AnnouncementBannerProvider} from "@/components/announcement-banner/AnnouncementBannerProvider";
|
||||
import {ImageZoom} from "@/components/ImageZoom";
|
||||
import {MatomoTagManager} from "@/components/Matomo";
|
||||
import {GoogleTagManager} from "@/components/GoogleTagManager";
|
||||
import {CookieConsentProvider, useCookieConsent} from "@/components/cookie-consent/CookieConsentProvider";
|
||||
import {CookieConsent} from "@/components/cookie-consent/CookieConsent";
|
||||
|
||||
@@ -33,6 +34,7 @@ function AppInner({ Component, pageProps }) {
|
||||
return (
|
||||
<>
|
||||
<MatomoTagManager consentGiven={isAccepted} />
|
||||
<GoogleTagManager consentGiven={isAccepted} />
|
||||
<Head>
|
||||
<style>{dom.css()}</style>
|
||||
{router.route.startsWith('/ipa') ?
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Head, Html, Main, NextScript } from 'next/document'
|
||||
import {GoogleTageManagerBodyScript, GoogleTagManagerHeadScript} from "@/components/GoogleTagManager";
|
||||
|
||||
const modeScript = `
|
||||
let darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
@@ -40,12 +39,10 @@ export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<GoogleTagManagerHeadScript />
|
||||
<script dangerouslySetInnerHTML={{ __html: modeScript }} />
|
||||
<link rel="shortcut icon" href="/docs-static/img/favicon.ico" />
|
||||
</Head>
|
||||
<body className="bg-white antialiased dark:bg-[#181A1D]">
|
||||
<GoogleTageManagerBodyScript />
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user