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";
|
||||
|
||||
export function MatomoTagManager() {
|
||||
export function MatomoTagManager({ consentGiven }) {
|
||||
return (
|
||||
<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'});
|
||||
(function() {
|
||||
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 {ImageZoom} from "@/components/ImageZoom";
|
||||
import {MatomoTagManager} from "@/components/Matomo";
|
||||
import {CookieConsentProvider, useCookieConsent} from "@/components/cookie-consent/CookieConsentProvider";
|
||||
import {CookieConsent} from "@/components/cookie-consent/CookieConsent";
|
||||
|
||||
function onRouteChange() {
|
||||
useMobileNavigationStore.getState().close()
|
||||
@@ -23,12 +25,14 @@ function onRouteChange() {
|
||||
Router.events.on('routeChangeStart', onRouteChange)
|
||||
Router.events.on('hashChangeStart', onRouteChange)
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
function AppInner({ Component, pageProps }) {
|
||||
let router = useRouter()
|
||||
let tableOfContents = collectHeadings(pageProps.sections)
|
||||
const { isAccepted } = useCookieConsent()
|
||||
|
||||
return (
|
||||
<>
|
||||
<MatomoTagManager />
|
||||
<MatomoTagManager consentGiven={isAccepted} />
|
||||
<Head>
|
||||
<style>{dom.css()}</style>
|
||||
{router.route.startsWith('/ipa') ?
|
||||
@@ -45,10 +49,19 @@ export default function App({ Component, pageProps }) {
|
||||
</AnnouncementBannerProvider>
|
||||
<ToastContainer />
|
||||
<ImageZoom />
|
||||
<CookieConsent />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function App(props) {
|
||||
return (
|
||||
<CookieConsentProvider>
|
||||
<AppInner {...props} />
|
||||
</CookieConsentProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function collectHeadings(sections, slugify = slugifyWithCounter()) {
|
||||
let output = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user