mirror of
https://github.com/netbirdio/docs.git
synced 2026-04-16 07:26:35 +00:00
Add cookies popup (#690)
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
|
|
||||||
export function MatomoTagManager() {
|
export function MatomoTagManager({ consentGiven }) {
|
||||||
return (
|
return (
|
||||||
<Script id="matomo-tag-manager" strategy="afterInteractive">
|
<Script id="matomo-tag-manager" strategy="afterInteractive">
|
||||||
{`var _mtm = window._mtm = window._mtm || [];
|
{`var _paq = window._paq = window._paq || [];
|
||||||
|
_paq.push(['requireCookieConsent']);
|
||||||
|
${consentGiven ? "_paq.push(['setCookieConsentGiven']);" : ""}
|
||||||
|
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];
|
||||||
|
|||||||
70
src/components/cookie-consent/CookieConsent.jsx
Normal file
70
src/components/cookie-consent/CookieConsent.jsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { useCookieConsent } from '@/components/cookie-consent/CookieConsentProvider'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export function CookieConsent() {
|
||||||
|
const { showConsent, acceptCookies, declineCookies } = useCookieConsent()
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showConsent) {
|
||||||
|
// Small delay so the CSS transition plays on mount
|
||||||
|
const id = setTimeout(() => setVisible(true), 50)
|
||||||
|
return () => clearTimeout(id)
|
||||||
|
}
|
||||||
|
setVisible(false)
|
||||||
|
}, [showConsent])
|
||||||
|
|
||||||
|
if (!showConsent) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'fixed inset-0 z-[999] flex items-center justify-center px-4 transition-opacity duration-300',
|
||||||
|
visible ? 'opacity-100' : 'opacity-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* Backdrop */}
|
||||||
|
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
||||||
|
|
||||||
|
{/* Banner */}
|
||||||
|
<div className="relative mx-auto max-w-lg rounded-xl border border-neutral-800 bg-black px-8 pb-6 pt-4 shadow-lg">
|
||||||
|
<h3 className="text-base font-semibold text-white">
|
||||||
|
We are using cookies
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-sm leading-relaxed text-white/70">
|
||||||
|
We use our own cookies as well as third-party cookies on our websites
|
||||||
|
to enhance your experience, analyze our traffic, and for security and
|
||||||
|
marketing. View our{' '}
|
||||||
|
<a
|
||||||
|
href="https://netbird.io/privacy"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-white underline underline-offset-4 transition-colors hover:text-netbird"
|
||||||
|
>
|
||||||
|
Privacy Policy
|
||||||
|
</a>{' '}
|
||||||
|
for more information.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-4 flex items-center justify-between gap-8">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={declineCookies}
|
||||||
|
className="cursor-pointer text-xs text-white/70 underline underline-offset-[6px] transition-colors duration-300 hover:text-netbird"
|
||||||
|
>
|
||||||
|
Required only cookies
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={acceptCookies}
|
||||||
|
className="rounded-md bg-netbird px-5 py-2.5 text-sm font-medium text-black transition-colors hover:bg-netbird/90"
|
||||||
|
>
|
||||||
|
Accept all cookies
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
97
src/components/cookie-consent/CookieConsentProvider.jsx
Normal file
97
src/components/cookie-consent/CookieConsentProvider.jsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'cookie-consent'
|
||||||
|
const ACCEPT_EXPIRY_DAYS = 90
|
||||||
|
const DECLINE_EXPIRY_DAYS = 1
|
||||||
|
|
||||||
|
const CookieConsentContext = createContext({
|
||||||
|
isAccepted: false,
|
||||||
|
showConsent: false,
|
||||||
|
acceptCookies: () => {},
|
||||||
|
declineCookies: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
|
function getStoredConsent() {
|
||||||
|
if (typeof window === 'undefined') return null
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(STORAGE_KEY)
|
||||||
|
if (!raw) return null
|
||||||
|
const { value, expires } = JSON.parse(raw)
|
||||||
|
if (Date.now() > expires) {
|
||||||
|
localStorage.removeItem(STORAGE_KEY)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeConsent(value, days) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(
|
||||||
|
STORAGE_KEY,
|
||||||
|
JSON.stringify({
|
||||||
|
value,
|
||||||
|
expires: Date.now() + days * 24 * 60 * 60 * 1000,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CookieConsentProvider({ children }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const [consent, setConsent] = useState(() => getStoredConsent())
|
||||||
|
const [showConsent, setShowConsent] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const stored = getStoredConsent()
|
||||||
|
setConsent(stored)
|
||||||
|
setShowConsent(stored === null)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Hide banner on privacy-related pages
|
||||||
|
if (router.pathname.startsWith('/privacy') || router.pathname.startsWith('/terms')) {
|
||||||
|
setShowConsent(false)
|
||||||
|
}
|
||||||
|
}, [router.pathname])
|
||||||
|
|
||||||
|
const acceptCookies = useCallback(() => {
|
||||||
|
storeConsent('accepted', ACCEPT_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)
|
||||||
|
setConsent('declined')
|
||||||
|
setShowConsent(false)
|
||||||
|
|
||||||
|
// Tell Matomo to forget consent and delete its cookies
|
||||||
|
window._paq = window._paq || []
|
||||||
|
window._paq.push(['forgetCookieConsentGiven'])
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CookieConsentContext.Provider
|
||||||
|
value={{
|
||||||
|
isAccepted: consent === 'accepted',
|
||||||
|
showConsent,
|
||||||
|
acceptCookies,
|
||||||
|
declineCookies,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CookieConsentContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCookieConsent() {
|
||||||
|
return useContext(CookieConsentContext)
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ 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 {CookieConsentProvider, useCookieConsent} from "@/components/cookie-consent/CookieConsentProvider";
|
||||||
|
import {CookieConsent} from "@/components/cookie-consent/CookieConsent";
|
||||||
|
|
||||||
function onRouteChange() {
|
function onRouteChange() {
|
||||||
useMobileNavigationStore.getState().close()
|
useMobileNavigationStore.getState().close()
|
||||||
@@ -23,12 +25,14 @@ function onRouteChange() {
|
|||||||
Router.events.on('routeChangeStart', onRouteChange)
|
Router.events.on('routeChangeStart', onRouteChange)
|
||||||
Router.events.on('hashChangeStart', onRouteChange)
|
Router.events.on('hashChangeStart', onRouteChange)
|
||||||
|
|
||||||
export default function App({ Component, pageProps }) {
|
function AppInner({ Component, pageProps }) {
|
||||||
let router = useRouter()
|
let router = useRouter()
|
||||||
let tableOfContents = collectHeadings(pageProps.sections)
|
let tableOfContents = collectHeadings(pageProps.sections)
|
||||||
|
const { isAccepted } = useCookieConsent()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MatomoTagManager />
|
<MatomoTagManager consentGiven={isAccepted} />
|
||||||
<Head>
|
<Head>
|
||||||
<style>{dom.css()}</style>
|
<style>{dom.css()}</style>
|
||||||
{router.route.startsWith('/ipa') ?
|
{router.route.startsWith('/ipa') ?
|
||||||
@@ -45,10 +49,19 @@ export default function App({ Component, pageProps }) {
|
|||||||
</AnnouncementBannerProvider>
|
</AnnouncementBannerProvider>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
<ImageZoom />
|
<ImageZoom />
|
||||||
|
<CookieConsent />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function App(props) {
|
||||||
|
return (
|
||||||
|
<CookieConsentProvider>
|
||||||
|
<AppInner {...props} />
|
||||||
|
</CookieConsentProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function collectHeadings(sections, slugify = slugifyWithCounter()) {
|
function collectHeadings(sections, slugify = slugifyWithCounter()) {
|
||||||
let output = []
|
let output = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user