mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 23:55:20 +00:00
Show cookies approval toast (#55)
* Show cookies toast when privacy policy is enabled * Flex buttons * Improve design * Fix mobile design
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { CustomizationThemeMode } from '@gitbook/api';
|
||||
import { Metadata, Viewport } from 'next';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
import { CookiesToast } from '@/components/Cookies';
|
||||
import { SpaceContent } from '@/components/SpaceContent';
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { PageHrefContext, absoluteHref, baseUrl, pageHref } from '@/lib/links';
|
||||
@@ -29,6 +31,7 @@ export default async function Page(props: { params: PagePathParams }) {
|
||||
collectionSpaces,
|
||||
ancestors,
|
||||
document,
|
||||
scripts,
|
||||
} = await fetchPageData(params);
|
||||
const linksContext: PageHrefContext = {};
|
||||
|
||||
@@ -53,6 +56,11 @@ export default async function Page(props: { params: PagePathParams }) {
|
||||
collection={collection}
|
||||
collectionSpaces={collectionSpaces}
|
||||
/>
|
||||
{scripts.some((script) => script.cookies) || customization.privacyPolicy.url ? (
|
||||
<React.Suspense fallback={null}>
|
||||
<CookiesToast privacyPolicy={customization.privacyPolicy.url} />
|
||||
</React.Suspense>
|
||||
) : null}
|
||||
</ClientContexts>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import { useLanguage } from '@/intl/client';
|
||||
import { t } from '@/intl/translate';
|
||||
import { isCookiesTrackingDisabled, setCookiesTracking } from '@/lib/analytics';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
/**
|
||||
* Toast to accept or reject the use of cookies.
|
||||
*/
|
||||
export function CookiesToast(props: { privacyPolicy?: string }) {
|
||||
const { privacyPolicy = 'https://policies.gitbook.com/privacy/cookies' } = props;
|
||||
const [show, setShow] = React.useState(false);
|
||||
const language = useLanguage();
|
||||
|
||||
React.useEffect(() => {
|
||||
setShow(isCookiesTrackingDisabled() === undefined);
|
||||
}, []);
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onUpdateState = (enabled: boolean) => {
|
||||
setCookiesTracking(enabled);
|
||||
|
||||
// Reload the page to take the change in consideration
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={tcls(
|
||||
'fixed',
|
||||
'z-10',
|
||||
'bg-white',
|
||||
'dark:bg-slate-800',
|
||||
'rounded',
|
||||
'border-slate-400',
|
||||
'dark:border-slate-700',
|
||||
'shadow-md',
|
||||
'p-4',
|
||||
'bottom-4',
|
||||
'right-4',
|
||||
'left-4',
|
||||
'max-w-sm',
|
||||
'sm:left-auto',
|
||||
)}
|
||||
>
|
||||
<p className={tcls('text-sm')}>
|
||||
{t(
|
||||
language,
|
||||
'cookies_prompt',
|
||||
<a
|
||||
href={privacyPolicy}
|
||||
className={tcls('text-primary-500', 'hover:text-primary-700', 'underline')}
|
||||
>
|
||||
{t(language, 'cookies_prompt_privacy')}
|
||||
</a>,
|
||||
)}
|
||||
</p>
|
||||
<div className={tcls('mt-3', 'flex', 'flex-row', 'gap-4')}>
|
||||
<ToastButton
|
||||
onClick={() => {
|
||||
onUpdateState(true);
|
||||
}}
|
||||
>
|
||||
{t(language, 'cookies_accept')}
|
||||
</ToastButton>
|
||||
<ToastButton
|
||||
onClick={() => {
|
||||
onUpdateState(false);
|
||||
}}
|
||||
>
|
||||
{t(language, 'cookies_reject')}
|
||||
</ToastButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ToastButton(props: { onClick: () => void; children: React.ReactNode }) {
|
||||
const { onClick, children } = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={tcls(
|
||||
'bg-white',
|
||||
'dark:bg-slate-800',
|
||||
'text-xs',
|
||||
'text-slate-800',
|
||||
'dark:text-slate-200',
|
||||
'rounded',
|
||||
'border',
|
||||
'border-slate-300',
|
||||
'dark:border-slate-700',
|
||||
'px-2',
|
||||
'py-1',
|
||||
'shadow-md',
|
||||
'hover:bg-slate-100',
|
||||
'dark:hover:bg-slate-700',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './CookiesToast';
|
||||
@@ -18,5 +18,9 @@
|
||||
"annotation_button_label": "Open annotation",
|
||||
"code_copied": "Copied!",
|
||||
"code_copy": "Copy",
|
||||
"table_of_contents_button_label": "Open table of contents"
|
||||
"table_of_contents_button_label": "Open table of contents",
|
||||
"cookies_prompt": "This site uses cookies to deliver its service and to analyse traffic. By browsing this site, you accept the ${1}.",
|
||||
"cookies_prompt_privacy": "privacy policy",
|
||||
"cookies_accept": "Accept",
|
||||
"cookies_reject": "Reject"
|
||||
}
|
||||
|
||||
+38
-1
@@ -3,6 +3,7 @@
|
||||
import cookies from 'js-cookie';
|
||||
|
||||
const VISITORID_COOKIE = '__session';
|
||||
const GRANTED_COOKIE = '__gitbook_cookie_granted';
|
||||
|
||||
let visitorId: string | null = null;
|
||||
let pendingVisitorId: Promise<string> | null = null;
|
||||
@@ -28,6 +29,12 @@ export async function getVisitorId(): Promise<string> {
|
||||
* Propose a visitor identifier to the GitBook.com server and get the devideId back.
|
||||
*/
|
||||
async function fetchVisitorID(): Promise<string> {
|
||||
const withoutCookies = isCookiesTrackingDisabled();
|
||||
|
||||
if (withoutCookies) {
|
||||
return getNewVisitorId();
|
||||
}
|
||||
|
||||
const existingTrackingCookie = cookies.get(VISITORID_COOKIE);
|
||||
|
||||
if (existingTrackingCookie) {
|
||||
@@ -35,7 +42,7 @@ async function fetchVisitorID(): Promise<string> {
|
||||
return existingTrackingCookie;
|
||||
} else {
|
||||
// No tracking deviceId set, we'll need to consolidate with the server.
|
||||
const proposed = `${crypto.randomUUID()}R`;
|
||||
const proposed = getNewVisitorId();
|
||||
|
||||
const url = new URL(process.env.NEXT_PUBLIC_GITBOOK_APP_URL ?? `https://app.gitbook.com`);
|
||||
url.pathname = '/__session';
|
||||
@@ -56,3 +63,33 @@ async function fetchVisitorID(): Promise<string> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept or reject cookies.
|
||||
*/
|
||||
export function setCookiesTracking(enabled: boolean) {
|
||||
cookies.set(GRANTED_COOKIE, enabled ? 'yes' : 'no');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if cookies are accepted or not.
|
||||
* Return `undefined` if state is not known.
|
||||
*/
|
||||
export function isCookiesTrackingDisabled() {
|
||||
const state = cookies.get(GRANTED_COOKIE);
|
||||
|
||||
if (state === 'yes') {
|
||||
return false;
|
||||
} else if (state === 'no') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a proposed visitor ID.
|
||||
*/
|
||||
function getNewVisitorId(): string {
|
||||
return `${crypto.randomUUID()}R`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user