Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

51 linesCodeBlameRaw
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 */
7import { useEffect, useState, useSyncExternalStore } from "react";
8import { Link } from "react-router";
9
10import { analyticsHere, choice, choose, consentRequired, onChoice } from "../lib/analytics-consent";
11import { Button } from "./ui/button";
12
13/** Whether this visitor is asked at all, known only once the page is in the browser. */
14export function useAsksFirst(): boolean {
15 const [asks, setAsks] = useState(false);
16 useEffect(() => setAsks(analyticsHere() && consentRequired()), []);
17 return asks;
18}
19
20export function AnalyticsConsent() {
21 const asks = useAsksFirst();
22 const chosen = useSyncExternalStore(onChoice, choice, () => null);
23 if (!asks || chosen) return null;
24 return (
25 <section
26 aria-label="Analytics cookie"
27 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"
28 >
29 <p className="leading-6 text-fg-soft">
30 May g1t count how its pages are used? It sets one analytics cookie, and names inside your workspaces are 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 type="button" onClick={() => choose("yes")} size="inline" className="flex-1 rounded-lg px-3 py-2">
37 Allow
38 </Button>
39 <Button
40 type="button"
41 onClick={() => choose("no")}
42 variant="ghost"
43 size="inline"
44 className="flex-1 rounded-lg px-3 py-2 text-fg ring-1 ring-line-strong"
45 >
46 No thanks
47 </Button>
48 </div>
49 </section>
50 );
51}