feat/analytics #9
44
app/components/common/Toggle.tsx
Normal file
44
app/components/common/Toggle.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
type ToggleProps = {
|
||||
id: string;
|
||||
label: string;
|
||||
onChange?: (checked: boolean) => void;
|
||||
checked: boolean;
|
||||
};
|
||||
|
||||
export function Toggle(props: ToggleProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={props.id}
|
||||
className="sr-only"
|
||||
onChange={(e) => props.onChange?.(e.target.checked)}
|
||||
checked={props.checked}
|
||||
aria-checked={props.checked}
|
||||
role="switch"
|
||||
aria-label={props.label}
|
||||
/>
|
||||
|
||||
<label htmlFor={props.id} className="flex cursor-pointer items-center justify-between rounded-full">
|
||||
<span className="relative flex h-6 w-10 items-center">
|
||||
<span
|
||||
className={clsx(
|
||||
"h-4 w-4 rounded-full bg-white shadow-md transition-transform duration-200 ease-in-out z-10",
|
||||
props.checked ? "translate-x-full" : "translate-x-0",
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={clsx(
|
||||
"absolute top-1/2 left-1/2 h-full w-full -translate-x-1/2 -translate-y-1/2 rounded-full transition duration-200 ease-in-out z-0",
|
||||
props.checked ? "bg-violet-500" : "bg-slate-300",
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span className="ml-2 text-sm text-slate-700 dark:text-slate-300">{props.label}</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -7,6 +7,8 @@ const variantStyles = {
|
||||
"bg-violet-300 font-semibold text-slate-900 hover:bg-violet-200 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-300/50 active:bg-violet-500",
|
||||
secondary:
|
||||
"bg-slate-800 font-medium text-white hover:bg-slate-700 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-slate-400",
|
||||
ghost:
|
||||
"bg-transparent font-medium text-slate-900 dark:text-slate-400 hover:bg-slate-100 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-300/50 active:bg-slate-200",
|
||||
};
|
||||
|
||||
const sizeStyles = {
|
||||
|
||||
@ -2,11 +2,14 @@ import { MobileNavigation } from "@syntax/MobileNavigation";
|
||||
import { usePageContext } from "vike-react/usePageContext";
|
||||
import { ThemeProvider } from "@/providers/ThemeProvider";
|
||||
import { ThemeSelector } from "@syntax/ThemeSelector";
|
||||
import { Button } from "@/components/syntax/Button";
|
||||
import { Toggle } from "@/components/common/Toggle";
|
||||
import { clientOnly } from "vike-react/clientOnly";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import { Navigation } from "@syntax/Navigation";
|
||||
import { Link } from "@/components/common/Link";
|
||||
import { reload } from "vike/client/router";
|
||||
import { Hero } from "@syntax/Hero";
|
||||
import { Logo } from "@syntax/Logo";
|
||||
import clsx from "clsx";
|
||||
@ -74,12 +77,107 @@ function Header() {
|
||||
);
|
||||
}
|
||||
|
||||
function CookieModal() {
|
||||
const { cookies } = usePageContext();
|
||||
|
||||
const [consentCookies, setConsentCookies] = useState(cookies.consent);
|
||||
const [isSelectionOpen, setIsSelectionOpen] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(() => {
|
||||
return Object.keys(cookies.consent).every((value) => value);
|
||||
});
|
||||
|
||||
if (isSelectionOpen) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/50">
|
||||
<div className="flex flex-col gap-2 bg-slate-50 dark:bg-slate-800 rounded-md shadow-xl w-full max-w-sm p-4">
|
||||
<p className="font-display dark:text-slate-300 font-bold text-lg">Personnalisation des cookies 🍪</p>
|
||||
|
||||
<div className="flex flex-col gap-2 w-full items-start">
|
||||
<Toggle
|
||||
id="cookies-analytics"
|
||||
label="Cookies d'analyse (Umami et Google Analytics)"
|
||||
checked={cookies.consent.analytics}
|
||||
onChange={(checked) => {
|
||||
setConsentCookies((prev) => ({ ...prev, analytics: checked }));
|
||||
reload();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Toggle
|
||||
id="cookies-customization"
|
||||
label="Cookie de personnalisation (thème)"
|
||||
checked={cookies.consent.customization}
|
||||
onChange={(checked) => {
|
||||
setConsentCookies((prev) => ({ ...prev, customization: checked }));
|
||||
reload();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col fixed bottom-4 left-4 bg-slate-50 dark:bg-slate-800 z-50 rounded-md shadow-xl w-full max-w-sm overflow-hidden">
|
||||
<div className="flex flex-col gap-2 p-4">
|
||||
<p className="font-display dark:text-slate-300">
|
||||
<span className="text-sm">Coucou c'est nous...</span>
|
||||
<br />
|
||||
<span className="font-bold text-lg">les cookies ! 🍪</span>
|
||||
</p>
|
||||
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
On ne t'embête pas longtemps, on te laisse même le choix <em>(si ça c'est pas la classe 😎)</em>.
|
||||
</p>
|
||||
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
Si tu veux en savoir plus, tu peux consulter la page{" "}
|
||||
<Link href="/politique-de-confidentialite" className="font-bold">
|
||||
Politique de confidentialité
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid items-center grid-cols-3 justify-between bg-slate-100 dark:bg-slate-700">
|
||||
<button
|
||||
className="cursor-pointer px-2 py-1 text-slate-600 dark:text-slate-300"
|
||||
onClick={async () => {
|
||||
// TODO
|
||||
}}
|
||||
>
|
||||
Non merci
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="cursor-pointer px-2 py-1 text-slate-600 dark:text-slate-300"
|
||||
onClick={() => setIsSelectionOpen(true)}
|
||||
>
|
||||
Je choisis
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="cursor-pointer px-2 py-1 font-bold text-violet-600 dark:text-violet-300"
|
||||
onClick={async () => {
|
||||
// TODO
|
||||
}}
|
||||
>
|
||||
Oui, j'ai faim !
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DefaultLayout({ children }: { children: React.ReactNode }) {
|
||||
const { urlPathname, cookies } = usePageContext();
|
||||
const isHomePage = urlPathname === "/";
|
||||
|
||||
return (
|
||||
<ThemeProvider defaultTheme={cookies.theme}>
|
||||
<CookieModal />
|
||||
|
||||
<div className="flex w-full flex-col font-sans">
|
||||
<Header />
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user