Skip to content

Commit

Permalink
Web: Add CookieConsentBanner
Browse files Browse the repository at this point in the history
  • Loading branch information
inaki-amatria committed Jan 20, 2025
1 parent 0f24ecc commit e33c52f
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ const config: Config = {
label: 'Privacy policy',
href: '/privacy-policy',
},
{
label: 'Manage cookies',
href: '#',
},
],
},
],
Expand Down
64 changes: 64 additions & 0 deletions src/components/CookieConsentBanner/CookieConsentBanner.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
:root {
--cookie-consent-banner-container-background-color: #000000ff;
--cookie-consent-banner-heading-color: #eeeeeeff;
--cookie-consent-banner-text-color: #94a1b2;
--cookie-consent-banner-button-accept-color: #0be0a6ff;
--cookie-consent-banner-button-accept-text-color: #000000;
--cookie-consent-banner-button-decline-color: #eeeeeeff;
--cookie-consent-banner-button-decline-text-color: #000000;
}

.cookie-consent-banner-container {
font-family: inherit;
background: var(--cookie-consent-banner-container-background-color);
color: var(--cookie-consent-banner-text-color);
padding: 25px;
width: calc(100% - 60px);
max-width: 400px;
border-radius: 8px;
box-shadow: 0px 8px 12px rgba(0, 0, 0, 0.45);
position: fixed;
bottom: 30px;
right: 30px;
z-index: 9999;
display: flex;
flex-direction: column;
}

.cookie-consent-banner-buttons {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 10px;
}

@media (max-width: 768px) {
.cookie-consent-banner-buttons {
flex-direction: column;
}
}

.cookie-consent-banner-button {
font-family: inherit;
font-weight: bold;
text-align: center;
user-select: text;
padding: 10px 0px;
border: none;
border-radius: 4px;
flex: 1;
}

.cookie-consent-banner-button:hover {
cursor: pointer;
}

.cookie-consent-banner-button-accept {
background: var(--cookie-consent-banner-button-accept-color);
color: var(--cookie-consent-banner-button-accept-text-color);
}

.cookie-consent-banner-button-decline {
background: var(--cookie-consent-banner-button-decline-color);
color: var(--cookie-consent-banner-button-decline-text-color);
}
136 changes: 136 additions & 0 deletions src/components/CookieConsentBanner/CookieConsentBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, { useEffect, useState } from 'react';
import CookieConsent, { getCookieConsentValue, OPTIONS, VISIBLE_OPTIONS } from 'react-cookie-consent';
import ReactGA from 'react-ga4';
import Link from '@docusaurus/Link';
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';

import './CookieConsentBanner.css';

const CookieConsentBanner = () => {
const cookieName: string = 'CookieConsent';
const googleAnalyticsTrackingId: string = 'G-178ZNT1Z63';
// Store the banner state in memory to maintain consistency across route
// changes. For example, if the banner is visible, ensure it remains visible
// when navigating between pages.
const isMinimizedValueInMemoryKey: string = 'CookieConsentIsMinimized';

if (!ExecutionEnvironment.canUseDOM) {
// Avoid rendering this component during Server-Side Rendering (SSR)
// to prevent errors caused by accessing the DOM in a non-browser
// environment.
return null;
}

const getIsMinimizedValueFromMemory = (): undefined | boolean => {
const isMinimizedValueInMemory: null | string =
window.sessionStorage.getItem(isMinimizedValueInMemoryKey);
if (isMinimizedValueInMemory === null) {
return undefined;
}
return JSON.parse(isMinimizedValueInMemory) === true;
};
const setIsMinimizedValueInMemory = (value: boolean): void => {
window.sessionStorage.setItem(isMinimizedValueInMemoryKey, JSON.stringify(value));
};
const initializeGoogleAnalytics = (): void => {
ReactGA.initialize([{ trackingId: googleAnalyticsTrackingId }]);
};
const resetGoogleAnalytics = (): void => {
ReactGA.reset();
};
const getCookieValue = (): undefined | boolean => {
const cookieValue: undefined | string = getCookieConsentValue(cookieName);
if (cookieValue === undefined) {
return undefined;
}
return cookieValue === 'true';
};

const [isMinimized, setIsMinimized] = useState(() => {
const isMinimizedValueInMemory: undefined | boolean = getIsMinimizedValueFromMemory();
if (isMinimizedValueInMemory === undefined) {
// If `isMinimizedValueInMemory` is undefined, we decide whether to show
// the banner by checking if the value of the cookie is also undefined. If
// the cookie is undefined, it means it is the first time the user visits
// the website; therefore, prompt the banner
return getCookieValue() !== undefined;
}
return isMinimizedValueInMemory;
});

const handleAccept = () => {
initializeGoogleAnalytics();
setIsMinimized(true);
};
const handleDecline = () => {
resetGoogleAnalytics();
setIsMinimized(true);
};
const handleExpand = () => {
setIsMinimized(false);
};

useEffect(() => {
setIsMinimizedValueInMemory(isMinimized);
}, [isMinimized]);
useEffect(() => {
if (getCookieValue() === true) {
initializeGoogleAnalytics();
}
}, []);

return (
<>
{!isMinimized && (
<CookieConsent
cookieName={cookieName}

buttonText='Accept'
declineButtonText='Decline'

onAccept={handleAccept}
onDecline={handleDecline}
enableDeclineButton={true}
acceptOnScroll={false}

location={OPTIONS.NONE}
visible={VISIBLE_OPTIONS.SHOW}

disableStyles={true}

containerClasses='cookie-consent-banner-container'
buttonWrapperClasses='cookie-consent-banner-buttons'
buttonClasses='cookie-consent-banner-button cookie-consent-banner-button-accept'
declineButtonClasses='cookie-consent-banner-button cookie-consent-banner-button-decline'
>
<h3 style={{ color: 'var(--cookie-consent-banner-heading-color)' }}>
🍪 We use cookies!
</h3>
<p>
We only use cookies to enhance your experience and improve our site. Is
that okay with you?
</p>
<p>
<Link
to='/cookie-policy'
style={{
color: 'var(--cookie-consent-banner-heading-color)',
textDecoration: 'underline',
}}
>
Read more
</Link>
</p>
</CookieConsent>
)}
<Link to='#' className='footer__link-item' onClick={(e) => {
// Prevent the default anchor behavior to avoid scrolling the page to
// the top when the link is clicked.
e.preventDefault();
handleExpand();
}}>Manage cookies</Link>
</>
);
};

export default CookieConsentBanner;
6 changes: 6 additions & 0 deletions src/theme/Footer/LinkItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ import isInternalUrl from '@docusaurus/isInternalUrl';
import IconExternalLink from '@theme/Icon/ExternalLink';
import type {Props} from '@theme/Footer/LinkItem';
import { Icon } from '@iconify/react';
import CookieConsentBanner from '@site/src/components/CookieConsentBanner/CookieConsentBanner';

export default function FooterLinkItem({item}: Props): JSX.Element {
const {to, href, label, prependBaseUrlToHref, ...props} = item;
const toUrl = useBaseUrl(to);
const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
const icon = props['icon'] as string;

if (label == 'Manage cookies') {
// Render the CookieConsentBanner directly instead of a regular footer link
// to allow interactive cookie management without navigating away.
return <CookieConsentBanner />;
}
return (
<Link
className="footer__link-item"
Expand Down

0 comments on commit e33c52f

Please sign in to comment.