| 1 | /** |
| 2 | * Asking a visitor from the EU, EEA, UK or Switzerland before site |
| 3 | * analytics runs (lib/analytics-consent.ts): both answers are one click, |
| 4 | * and nothing is fetched or stored for analytics until they agree. The |
| 5 | * footer's "Cookie choices" asks again (components/footer.tsx). |
| 6 | */ |
| 7 | import { useEffect, useState, useSyncExternalStore } from "react"; |
| 8 | import { Link } from "react-router"; |
| 9 | |
| 10 | import { analyticsHere, choice, choose, consentRequired, onChoice } from "../lib/analytics-consent"; |
| 11 | |
| 12 | /** Whether this visitor is asked at all, known only once the page is in the browser. */ |
| 13 | export function useAsksFirst(): boolean { |
| 14 | const [asks, setAsks] = useState(false); |
| 15 | useEffect(() => setAsks(analyticsHere() && consentRequired()), []); |
| 16 | return asks; |
| 17 | } |
| 18 | |
| 19 | export function AnalyticsConsent() { |
| 20 | const asks = useAsksFirst(); |
| 21 | const chosen = useSyncExternalStore(onChoice, choice, () => null); |
| 22 | if (!asks || chosen) return null; |
| 23 | return ( |
| 24 | <section |
| 25 | aria-label="Analytics cookie" |
| 26 | className="fixed inset-x-3 bottom-3 z-[60] max-md:bottom-[calc(var(--tabbar-h)+0.75rem)] max-md:in-data-[keyboard=open]:bottom-3 mx-auto max-w-lg rounded-2xl bg-surface p-4 text-sm shadow-2xl shadow-black/50 ring-1 ring-line-strong sm:inset-x-auto sm:right-4 sm:bottom-4" |
| 27 | > |
| 28 | <p className="leading-6 text-fg-soft"> |
| 29 | May g1t count how its pages are used? It sets one analytics cookie, and names inside your workspaces are |
| 30 | removed before anything is sent.{" "} |
| 31 | <Link to="/policies/privacy#how-the-site-is-used" className="text-accent hover:underline"> |
| 32 | What is sent |
| 33 | </Link> |
| 34 | </p> |
| 35 | <div className="mt-3 flex gap-2"> |
| 36 | <button |
| 37 | type="button" |
| 38 | onClick={() => choose("yes")} |
| 39 | className="flex-1 rounded-lg bg-fg px-3 py-2 font-medium text-bg transition-colors hover:bg-white" |
| 40 | > |
| 41 | Allow |
| 42 | </button> |
| 43 | <button |
| 44 | type="button" |
| 45 | onClick={() => choose("no")} |
| 46 | className="flex-1 rounded-lg px-3 py-2 font-medium text-fg ring-1 ring-line-strong transition-colors hover:bg-raised" |
| 47 | > |
| 48 | No thanks |
| 49 | </button> |
| 50 | </div> |
| 51 | </section> |
| 52 | ); |
| 53 | } |