First-party, consent-based funnel tracking: a cookie notice asks once, and only visitors who allow it get an anonymous visit id. Event page views and registration starts are tracked from the browser; submit and paid come from the registration. Adds the Registration funnel report (conversion per stage, daily trend, by source/device/ticket option), a conversion tile on the supervisor/admin dashboards, short-link source tagging, and a daily rollup of raw rows older than 90 days. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CP62L9JxHomA36aYP1ekCe
33 lines
1.6 KiB
TypeScript
33 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { Cookie } from "lucide-react";
|
|
import { getConsent, setConsent } from "@/lib/analytics";
|
|
|
|
/**
|
|
* Asks once whether we may use a cookie-like identifier to count visits (our own
|
|
* registration-funnel stats). Declining changes nothing else about the site.
|
|
*/
|
|
export function CookieConsent() {
|
|
const [show, setShow] = useState(false);
|
|
useEffect(() => { setShow(getConsent() === null); }, []);
|
|
if (!show) return null;
|
|
const choose = (v: "accepted" | "declined") => { setConsent(v); setShow(false); };
|
|
return (
|
|
<div className="fixed bottom-0 inset-x-0 z-40 p-3 sm:p-4 pointer-events-none" role="region" aria-label="Cookie consent">
|
|
<div className="pointer-events-auto max-w-3xl mx-auto bg-white border shadow-lg rounded-xl p-4 flex flex-col sm:flex-row sm:items-center gap-3">
|
|
<Cookie className="w-6 h-6 text-brand-600 shrink-0 hidden sm:block" />
|
|
<p className="text-sm text-gray-700 flex-1">
|
|
May we count your visit? We use our own anonymous statistics (no third-party analytics) to see how people find and register for events.{" "}
|
|
<Link href="/legal/privacy#cookies" className="text-brand-700 underline">Privacy policy</Link>
|
|
</p>
|
|
<div className="flex gap-2 shrink-0">
|
|
<button onClick={() => choose("declined")} className="px-3 py-1.5 text-sm rounded-lg border hover:bg-gray-50">No thanks</button>
|
|
<button onClick={() => choose("accepted")} className="px-3 py-1.5 text-sm rounded-lg bg-brand-600 text-white hover:bg-brand-700">Allow</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|