From c0584092eb3fe77edee7a397f3e4e346fe238d49 Mon Sep 17 00:00:00 2001 From: "Claude (Phase B)" Date: Fri, 15 May 2026 01:45:33 +0000 Subject: [PATCH] ad analytics: impressions + clicks + admin stats + /r/[slug] redirect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tables (Phase B+): bb.ad_impressions — every render = a row. is_bot + viewport flags for filtering. No unique constraints — gross counts per Ry an spec. bb.ad_clicks — every /r/[slug] hit = a row. bb.ad_campaign_clients — schema only; future client reporting. FK target is bb.banner_creatives (the renderer table from 5/15 banner refresh). bb.ad_campaigns is the AdOps billing schema and unrelated. Routes: GET /r/[slug] — log click, 302 to bb.banner_creatives.click_url POST /api/track/impression — record impression (slug | campaign_id), used by client beacon AdImage rewrite: - link href is /r/{slug} target=_blank rel=sponsored noopener noreferrer - sendBeacon on mount fires impression (viewport=false) - additional sendBeacon on IntersectionObserver ≥0.5 (viewport=true) - removed direct supabase.from('banner_analytics').insert path Ad type carries slug now; FALLBACK list + DB mapper both populate it. /admin/banners: per-row stats — 24h, 7d, lifetime impressions/clicks/CTR link to /admin/banners/[id]/analytics /admin/banners/[id]/analytics (new): recharts line charts for impressions + clicks (30d), top page paths, top referrer domains, bot/human toggle. Tower Products orphan image file removed; banner was not in any active table or in code (already off rotation). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Broadcast-Beat-Ad-March-2023_2f1904b5.png | Bin 89023 -> 0 bytes src/app/admin/banners/BannerRows.tsx | 85 +++++++- .../[id]/analytics/AnalyticsCharts.tsx | 132 +++++++++++++ src/app/admin/banners/[id]/analytics/page.tsx | 187 ++++++++++++++++++ src/app/admin/banners/page.tsx | 60 +++++- src/app/api/track/impression/route.ts | 71 +++++++ src/app/r/[slug]/route.ts | 47 +++++ src/components/AdImage.tsx | 137 ++++++++----- src/lib/ad-tracking.ts | 71 +++++++ src/lib/ads.ts | 34 ++-- 10 files changed, 751 insertions(+), 73 deletions(-) delete mode 100644 public/legacy/ads/Broadcast-Beat-Ad-March-2023_2f1904b5.png create mode 100644 src/app/admin/banners/[id]/analytics/AnalyticsCharts.tsx create mode 100644 src/app/admin/banners/[id]/analytics/page.tsx create mode 100644 src/app/api/track/impression/route.ts create mode 100644 src/app/r/[slug]/route.ts create mode 100644 src/lib/ad-tracking.ts diff --git a/public/legacy/ads/Broadcast-Beat-Ad-March-2023_2f1904b5.png b/public/legacy/ads/Broadcast-Beat-Ad-March-2023_2f1904b5.png deleted file mode 100644 index 5a7a9a3ee2731fb24ecd9a2ff1f0daa559733487..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89023 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 diff --git a/src/app/admin/banners/BannerRows.tsx b/src/app/admin/banners/BannerRows.tsx index 0a32952..a12607f 100644 --- a/src/app/admin/banners/BannerRows.tsx +++ b/src/app/admin/banners/BannerRows.tsx @@ -2,6 +2,7 @@ import { useState, useTransition } from "react"; import { useRouter } from "next/navigation"; +import Link from "next/link"; interface BannerRow { id: string; @@ -18,12 +19,54 @@ interface BannerRow { updated_at: string; } +interface StatBucket { + impressions: number; + clicks: number; +} +interface BannerStats { + d1: StatBucket; + d7: StatBucket; + lifetime: StatBucket; +} + function fmtDate(iso: string | null): string { if (!iso) return ""; return iso.slice(0, 10); } -export default function BannerRows({ banners }: { banners: BannerRow[] }) { +function pct(n: number, d: number): string { + if (!d) return "—"; + return ((n / d) * 100).toFixed(2) + "%"; +} + +function fmtNum(n: number): string { + return n.toLocaleString(); +} + +function StatBlock({ label, b }: { label: string; b: StatBucket }) { + return ( +

+ ); +} + +export default function BannerRows({ + banners, + stats, +}: { + banners: BannerRow[]; + stats: Record; +}) { const router = useRouter(); const [pending, startTransition] = useTransition(); const [edits, setEdits] = useState>>({}); @@ -61,6 +104,12 @@ export default function BannerRows({ banners }: { banners: BannerRow[] }) { startTransition(() => router.refresh()); } + const empty: BannerStats = { + d1: { impressions: 0, clicks: 0 }, + d7: { impressions: 0, clicks: 0 }, + lifetime: { impressions: 0, clicks: 0 }, + }; + return (
{err &&
{err}
} @@ -68,6 +117,7 @@ export default function BannerRows({ banners }: { banners: BannerRow[] }) { {banners.map((b) => { const dirty = !!edits[b.id]; const current: BannerRow = { ...b, ...(edits[b.id] || {}) }; + const s = stats[b.id] || empty; return (
@@ -135,6 +185,7 @@ export default function BannerRows({ banners }: { banners: BannerRow[] }) { + @@ -148,16 +199,30 @@ export default function BannerRows({ banners }: { banners: BannerRow[] }) {
-
- + {saved === b.id && Saved} + {dirty && saved !== b.id && Unsaved} +
+ - Save changes - - {saved === b.id && Saved} - {dirty && saved !== b.id && Unsaved} + View detailed analytics → +
); diff --git a/src/app/admin/banners/[id]/analytics/AnalyticsCharts.tsx b/src/app/admin/banners/[id]/analytics/AnalyticsCharts.tsx new file mode 100644 index 0000000..723ccab --- /dev/null +++ b/src/app/admin/banners/[id]/analytics/AnalyticsCharts.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { useState } from "react"; +import { + LineChart, + Line, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + CartesianGrid, + Legend, +} from "recharts"; + +interface Series { + date: string; + count: number; +} +interface TopRow { + label: string; + count: number; +} + +export default function AnalyticsCharts({ + impSeries, + clkSeries, + impSeriesBot, + clkSeriesBot, + topPages, + topReferrers, + totals, +}: { + impSeries: Series[]; + clkSeries: Series[]; + impSeriesBot: Series[]; + clkSeriesBot: Series[]; + topPages: TopRow[]; + topReferrers: TopRow[]; + totals: { impHuman: number; impBot: number; clkHuman: number; clkBot: number }; +}) { + const [includeBots, setIncludeBots] = useState(false); + + const imp = includeBots ? impSeriesBot : impSeries; + const clk = includeBots ? clkSeriesBot : clkSeries; + + const totImp = includeBots ? totals.impHuman + totals.impBot : totals.impHuman; + const totClk = includeBots ? totals.clkHuman + totals.clkBot : totals.clkHuman; + const ctr = totImp ? ((totClk / totImp) * 100).toFixed(2) + "%" : "—"; + + return ( + <> +
+ + + + +
+ +
+ setIncludeBots(e.target.checked)} + className="rounded" + /> + +
+ +
+ + +
+ +
+ + +
+ + ); +} + +function Tile({ label, value, small }: { label: string; value: string; small?: boolean }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +function ChartCard({ title, data, stroke }: { title: string; data: Series[]; stroke: string }) { + return ( +
+

{title}

+
+ + + + d.slice(5)} /> + + + + + + +
+
+ ); +} + +function TopListCard({ title, rows }: { title: string; rows: TopRow[] }) { + return ( +
+

{title}

+ {rows.length === 0 ? ( +

No data yet.

+ ) : ( + + + {rows.map((r) => ( + + + + + ))} + +
{r.label}{r.count.toLocaleString()}
+ )} +
+ ); +} diff --git a/src/app/admin/banners/[id]/analytics/page.tsx b/src/app/admin/banners/[id]/analytics/page.tsx new file mode 100644 index 0000000..6b3ad6f --- /dev/null +++ b/src/app/admin/banners/[id]/analytics/page.tsx @@ -0,0 +1,187 @@ +import Link from "next/link"; +import { notFound, redirect } from "next/navigation"; +import { createAdminClient } from "@/lib/supabase/admin"; +import { createClient } from "@/lib/supabase/server"; +import AnalyticsCharts from "./AnalyticsCharts"; + +export const dynamic = "force-dynamic"; +export const revalidate = 0; + +interface BannerRow { + id: string; + slug: string; + name: string; + image_path: string; + click_url: string | null; + size: string; + status: string; +} + +interface Row { + campaign_id: string; + occurred_at: string; + page_path: string | null; + referrer: string | null; + is_bot: boolean; + viewport?: boolean; +} + +async function requireAdmin() { + const sb = await createClient(); + const { data: { user } } = await sb.auth.getUser(); + if (!user) return { ok: false as const }; + const { data: profile } = await sb + .from("user_profiles") + .select("role,is_active") + .eq("id", user.id) + .maybeSingle(); + if (!profile?.is_active) return { ok: false as const }; + if (!["administrator", "admin"].includes((profile as any).role)) return { ok: false as const }; + return { ok: true as const }; +} + +function dayBuckets(rows: Row[], days = 30): { date: string; count: number }[] { + const today = new Date(); + today.setUTCHours(0, 0, 0, 0); + const out: { date: string; count: number }[] = []; + const map = new Map(); + for (const r of rows) { + const d = r.occurred_at.slice(0, 10); + map.set(d, (map.get(d) || 0) + 1); + } + for (let i = days - 1; i >= 0; i--) { + const d = new Date(today.getTime() - i * 86400000); + const key = d.toISOString().slice(0, 10); + out.push({ date: key, count: map.get(key) || 0 }); + } + return out; +} + +function topGroup(rows: Row[], key: keyof Row, limit = 8): { label: string; count: number }[] { + const m = new Map(); + for (const r of rows) { + const v = (r[key] || "(none)") as string; + m.set(v, (m.get(v) || 0) + 1); + } + return [...m.entries()] + .sort((a, b) => b[1] - a[1]) + .slice(0, limit) + .map(([label, count]) => ({ label, count })); +} + +function referrerDomain(r: string | null): string { + if (!r) return "(direct)"; + try { + const u = new URL(r); + return u.hostname || "(direct)"; + } catch { + return "(invalid)"; + } +} + +export default async function BannerAnalyticsPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const guard = await requireAdmin(); + if (!guard.ok) redirect("/admin"); + + const { id } = await params; + const admin = createAdminClient(); + + const { data: banner } = await admin + .from("banner_creatives") + .select("id, slug, name, image_path, click_url, size, status") + .eq("id", id) + .maybeSingle(); + if (!banner) notFound(); + const b = banner as BannerRow; + + const sinceIso = new Date(Date.now() - 30 * 86400000).toISOString(); + + const [imp, clk] = await Promise.all([ + admin + .from("ad_impressions") + .select("campaign_id, occurred_at, page_path, referrer, is_bot, viewport") + .eq("campaign_id", id) + .gte("occurred_at", sinceIso) + .order("occurred_at", { ascending: false }) + .limit(50000), + admin + .from("ad_clicks") + .select("campaign_id, occurred_at, page_url, referrer, is_bot") + .eq("campaign_id", id) + .gte("occurred_at", sinceIso) + .order("occurred_at", { ascending: false }) + .limit(50000), + ]); + + const impRows = (imp.data || []) as Row[]; + const clkRows = (clk.data || []).map((r: any) => ({ + campaign_id: r.campaign_id, + occurred_at: r.occurred_at, + page_path: r.page_url || null, + referrer: r.referrer, + is_bot: r.is_bot, + })) as Row[]; + + const impHuman = impRows.filter((r) => !r.is_bot); + const clkHuman = clkRows.filter((r) => !r.is_bot); + + const impSeries = dayBuckets(impHuman, 30); + const clkSeries = dayBuckets(clkHuman, 30); + const impSeriesBot = dayBuckets(impRows, 30); + const clkSeriesBot = dayBuckets(clkRows, 30); + + const topPages = topGroup(impHuman, "page_path"); + const topReferrers = topGroup( + impHuman.map((r) => ({ ...r, referrer: referrerDomain(r.referrer) })), + "referrer", + ); + + return ( +
+ + ← Back to banners + + +
+ +
+ + +
+ ); +} diff --git a/src/app/admin/banners/page.tsx b/src/app/admin/banners/page.tsx index b27eff4..7bbc719 100644 --- a/src/app/admin/banners/page.tsx +++ b/src/app/admin/banners/page.tsx @@ -22,6 +22,16 @@ interface BannerRow { updated_at: string; } +interface StatBucket { + impressions: number; + clicks: number; +} +interface BannerStats { + d1: StatBucket; + d7: StatBucket; + lifetime: StatBucket; +} + async function requireAdmin() { const sb = await createClient(); const { data: { user } } = await sb.auth.getUser(); @@ -36,6 +46,47 @@ async function requireAdmin() { return { ok: true as const }; } +async function loadStats(): Promise> { + const admin = createAdminClient(); + const now = new Date(); + const d1Iso = new Date(now.getTime() - 24 * 3600 * 1000).toISOString(); + const d7Iso = new Date(now.getTime() - 7 * 24 * 3600 * 1000).toISOString(); + + async function bucket(table: "ad_impressions" | "ad_clicks", since: string | null) { + let q = admin.from(table).select("campaign_id"); + if (since) q = q.gte("occurred_at", since); + const { data } = await q.limit(100000); + const counts: Record = {}; + for (const r of (data || []) as any[]) { + counts[r.campaign_id] = (counts[r.campaign_id] || 0) + 1; + } + return counts; + } + + const [imp1, imp7, impL, clk1, clk7, clkL] = await Promise.all([ + bucket("ad_impressions", d1Iso), + bucket("ad_impressions", d7Iso), + bucket("ad_impressions", null), + bucket("ad_clicks", d1Iso), + bucket("ad_clicks", d7Iso), + bucket("ad_clicks", null), + ]); + + const ids = new Set([ + ...Object.keys(imp1), ...Object.keys(imp7), ...Object.keys(impL), + ...Object.keys(clk1), ...Object.keys(clk7), ...Object.keys(clkL), + ]); + const out: Record = {}; + for (const id of ids) { + out[id] = { + d1: { impressions: imp1[id] || 0, clicks: clk1[id] || 0 }, + d7: { impressions: imp7[id] || 0, clicks: clk7[id] || 0 }, + lifetime: { impressions: impL[id] || 0, clicks: clkL[id] || 0 }, + }; + } + return out; +} + export default async function AdminBannersPage() { const guard = await requireAdmin(); if (!guard.ok) redirect("/admin"); @@ -48,6 +99,7 @@ export default async function AdminBannersPage() { .order("slug", { ascending: true }); const banners = (rows || []) as BannerRow[]; + const stats = await loadStats(); return (
@@ -55,8 +107,10 @@ export default async function AdminBannersPage() {

Banner creatives

- {banners.length} banners · live data from bb.banner_creatives. Edits - apply within 5 minutes (in-process cache TTL on the site renderer). + {banners.length} banners · live data from bb.banner_creatives. + Stats roll forward in real time from bb.ad_impressions / + bb.ad_clicks. Renderer cache TTL is 5 min — edits surface + after the next refresh.

@@ -64,7 +118,7 @@ export default async function AdminBannersPage() { - +
); } diff --git a/src/app/api/track/impression/route.ts b/src/app/api/track/impression/route.ts new file mode 100644 index 0000000..8c0cbe8 --- /dev/null +++ b/src/app/api/track/impression/route.ts @@ -0,0 +1,71 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createAdminClient } from "@/lib/supabase/admin"; +import { clientIp, isBot, recordImpression, sessionIdFromCookies } from "@/lib/ad-tracking"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +interface Payload { + campaign_id?: string; + slug?: string; + page_url?: string; + page_path?: string; + viewport?: boolean; +} + +let slugCache: Map = new Map(); +let slugCacheLoadedAt = 0; +const SLUG_CACHE_TTL_MS = 5 * 60 * 1000; + +async function resolveCampaignId(body: Payload): Promise { + if (body.campaign_id && /^[0-9a-f-]{36}$/i.test(body.campaign_id)) return body.campaign_id; + if (!body.slug) return null; + + const now = Date.now(); + if (now - slugCacheLoadedAt > SLUG_CACHE_TTL_MS) { + try { + const admin = createAdminClient(); + const { data } = await admin.from("banner_creatives").select("id, slug, status"); + const m = new Map(); + for (const r of (data || []) as any[]) { + if (r.status === "active") m.set(r.slug, r.id); + } + slugCache = m; + slugCacheLoadedAt = now; + } catch { + // keep stale cache on failure + } + } + return slugCache.get(body.slug) || null; +} + +export async function POST(req: NextRequest) { + let body: Payload; + try { + body = (await req.json()) as Payload; + } catch { + return new NextResponse(null, { status: 204 }); + } + + const campaignId = await resolveCampaignId(body); + if (!campaignId) return new NextResponse(null, { status: 204 }); + + const ua = req.headers.get("user-agent"); + const referrer = req.headers.get("referer"); + const ip = clientIp(req.headers); + const sid = sessionIdFromCookies(req.headers.get("cookie")); + + recordImpression({ + campaignId, + pageUrl: body.page_url ?? referrer, + pagePath: body.page_path ?? null, + referrer, + userAgent: ua, + ipAddress: ip, + isBot: isBot(ua), + viewport: !!body.viewport, + sessionId: sid, + }).catch((err) => console.error("[impression] insert failed:", err?.message)); + + return new NextResponse(null, { status: 204 }); +} diff --git a/src/app/r/[slug]/route.ts b/src/app/r/[slug]/route.ts new file mode 100644 index 0000000..8539f73 --- /dev/null +++ b/src/app/r/[slug]/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createAdminClient } from "@/lib/supabase/admin"; +import { clientIp, isBot, recordClick, sessionIdFromCookies } from "@/lib/ad-tracking"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +export async function GET(req: NextRequest, { params }: { params: Promise<{ slug: string }> }) { + const { slug } = await params; + const admin = createAdminClient(); + const nowIso = new Date().toISOString(); + + const { data, error } = await admin + .from("banner_creatives") + .select("id, click_url, status, start_date, end_date") + .eq("slug", slug) + .maybeSingle(); + + if (error || !data) return NextResponse.json({ error: "Not found" }, { status: 404 }); + + const row = data as any; + const active = + row.status === "active" && + (!row.start_date || row.start_date <= nowIso) && + (!row.end_date || row.end_date >= nowIso) && + !!row.click_url; + if (!active) return NextResponse.json({ error: "Not active" }, { status: 410 }); + + const ua = req.headers.get("user-agent"); + const referrer = req.headers.get("referer"); + const ip = clientIp(req.headers); + const sid = sessionIdFromCookies(req.headers.get("cookie")); + + // Fire-and-forget the INSERT — the click target is the most important + // thing on the response path. If the log write throws, swallow it. + recordClick({ + campaignId: row.id, + pageUrl: referrer, + referrer, + userAgent: ua, + ipAddress: ip, + isBot: isBot(ua), + sessionId: sid, + }).catch((err) => console.error("[click] insert failed:", err?.message)); + + return NextResponse.redirect(row.click_url, 302); +} diff --git a/src/components/AdImage.tsx b/src/components/AdImage.tsx index 213de3c..ac79a96 100644 --- a/src/components/AdImage.tsx +++ b/src/components/AdImage.tsx @@ -1,9 +1,10 @@ -'use client'; -import { useEffect, useRef } from 'react'; -import AppImage from '@/components/ui/AppImage'; -import { createClient } from '@/lib/supabase/client'; +"use client"; + +import { useEffect, useRef } from "react"; +import AppImage from "@/components/ui/AppImage"; interface Ad { + slug?: string; label: string; size: string; src?: string; @@ -11,63 +12,103 @@ interface Ad { click_url?: string; } -// Renders a local creative from /public/legacy/ads/... and tracks impressions -// + clicks to bb.banner_analytics. The previous Adsanity iframe path -// (ads.broadcastbeat.com) was removed — that subdomain doesn't resolve and -// caused 30s timeouts. To add a new banner, drop the file in -// public/legacy/ads/ and add an entry to ADS_300X250 / ADS_728X90 in -// src/lib/ads.ts with src + alt + click_url. -export default function AdImage({ ad, page, priority }: { ad: Ad; page?: string; priority?: boolean }) { +// Banner renderer with impression + click tracking. Impressions are logged +// twice per visible render via navigator.sendBeacon (no fetch promise +// awaited, no impact on paint): +// 1. on mount — guarantees a count even if the user never sees the banner +// (gross-count policy, intentional) +// 2. on viewport intersection (≥500ms, threshold 0.5) — viewport=true, +// gives clients who care a "viewable" subset +// +// Clicks always route through /r/{slug} which logs server-side then 302s +// to the click_url stored in bb.banner_creatives. We do NOT also log on +// the link's onClick — the /r/ route is the single source of truth. + +export default function AdImage({ + ad, + page, + priority, +}: { + ad: Ad; + page?: string; + priority?: boolean; +}) { const containerRef = useRef(null); - const trackedRef = useRef(false); - const supabase = createClient(); + const beaconMount = useRef(false); + const beaconViewport = useRef(false); useEffect(() => { - if (!ad.src) return; - const observer = new IntersectionObserver(([entry]) => { - if (entry.isIntersecting && !trackedRef.current) { - trackedRef.current = true; - supabase.from('banner_analytics').insert({ - ad_src: ad.src, - ad_label: ad.label, - ad_size: ad.size, - event_type: 'impression', - page: page || (typeof window !== 'undefined' ? window.location.pathname : null), - session_id: typeof window !== 'undefined' ? (window as { __session_id?: string }).__session_id : null, - referrer: typeof document !== 'undefined' ? document.referrer : null, - }).then(() => undefined, (err: unknown) => console.error('Impression:', err)); - } - }, { threshold: 0.5 }); - if (containerRef.current) observer.observe(containerRef.current); - return () => observer.disconnect(); - }, [ad, page, supabase]); + if (!ad.src || !ad.slug) return; - const handleClick = () => { - supabase.from('banner_analytics').insert({ - ad_src: ad.src, - ad_label: ad.label, - ad_size: ad.size, - event_type: 'click', - page: page || (typeof window !== 'undefined' ? window.location.pathname : null), - session_id: typeof window !== 'undefined' ? (window as { __session_id?: string }).__session_id : null, - referrer: typeof document !== 'undefined' ? document.referrer : null, - }).then(() => undefined, (err: unknown) => console.error('Click:', err)); - }; + if (!beaconMount.current) { + beaconMount.current = true; + sendImpression(ad.slug, page, false); + } + + const el = containerRef.current; + if (!el || beaconViewport.current) return; + const observer = new IntersectionObserver( + (entries) => { + for (const e of entries) { + if (e.isIntersecting && !beaconViewport.current) { + beaconViewport.current = true; + sendImpression(ad.slug!, page, true); + observer.disconnect(); + return; + } + } + }, + { threshold: 0.5 }, + ); + observer.observe(el); + return () => observer.disconnect(); + }, [ad.src, ad.slug, page]); if (!ad.src) return null; - const width = parseInt(ad.size.split('x')[0]); - const height = parseInt(ad.size.split('x')[1]); + const [w, h] = ad.size.split("x").map((n) => parseInt(n, 10)); + const clickHref = ad.slug ? `/r/${ad.slug}` : ad.click_url || ""; return (
- {ad.click_url ? ( - - + {clickHref ? ( + + ) : ( - + )}
); } + +function sendImpression(slug: string, page: string | undefined, viewport: boolean) { + if (typeof window === "undefined") return; + // We need the campaign UUID, not the slug, on the wire — but the + // server-side /api/track/impression endpoint accepts slug-or-uuid and + // resolves it. To keep payload tiny and avoid leaking IDs, we send + // slug + the viewport flag. + const payload = JSON.stringify({ + slug, + page_path: page || window.location.pathname, + page_url: window.location.href, + viewport, + }); + try { + const ok = + typeof navigator.sendBeacon === "function" && + navigator.sendBeacon( + "/api/track/impression", + new Blob([payload], { type: "application/json" }), + ); + if (ok) return; + } catch { + // fall through to fetch + } + fetch("/api/track/impression", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: payload, + keepalive: true, + }).catch(() => undefined); +} diff --git a/src/lib/ad-tracking.ts b/src/lib/ad-tracking.ts new file mode 100644 index 0000000..485a30c --- /dev/null +++ b/src/lib/ad-tracking.ts @@ -0,0 +1,71 @@ +import { createAdminClient } from "@/lib/supabase/admin"; + +const BOT_REGEX = /bot|crawl|spider|googlebot|bingbot|slurp|baidu|yandex|duckduck|facebook|pinterest|whatsapp|twitter|skype|linkedin|preview|fetch|curl|wget|monitor|uptime/i; + +export function isBot(userAgent: string | null | undefined): boolean { + if (!userAgent) return true; + return BOT_REGEX.test(userAgent); +} + +export function clientIp(headers: Headers): string | null { + const xff = headers.get("x-forwarded-for") || ""; + const first = xff.split(",")[0]?.trim(); + if (first) return first; + return headers.get("x-real-ip") || null; +} + +export function sessionIdFromCookies(cookieHeader: string | null): string | null { + if (!cookieHeader) return null; + const m = cookieHeader.match(/bb_sid=([^;]+)/); + return m ? decodeURIComponent(m[1]) : null; +} + +export interface ImpressionInput { + campaignId: string; + pageUrl?: string | null; + pagePath?: string | null; + referrer?: string | null; + userAgent?: string | null; + ipAddress?: string | null; + isBot?: boolean; + viewport?: boolean; + sessionId?: string | null; +} + +export interface ClickInput { + campaignId: string; + pageUrl?: string | null; + referrer?: string | null; + userAgent?: string | null; + ipAddress?: string | null; + isBot?: boolean; + sessionId?: string | null; +} + +export async function recordImpression(i: ImpressionInput): Promise { + const admin = createAdminClient(); + await admin.from("ad_impressions").insert({ + campaign_id: i.campaignId, + page_url: i.pageUrl ?? null, + page_path: i.pagePath ?? null, + referrer: i.referrer ?? null, + user_agent: i.userAgent ?? null, + ip_address: i.ipAddress ?? null, + is_bot: !!i.isBot, + viewport: !!i.viewport, + session_id: i.sessionId ?? null, + }); +} + +export async function recordClick(c: ClickInput): Promise { + const admin = createAdminClient(); + await admin.from("ad_clicks").insert({ + campaign_id: c.campaignId, + page_url: c.pageUrl ?? null, + referrer: c.referrer ?? null, + user_agent: c.userAgent ?? null, + ip_address: c.ipAddress ?? null, + is_bot: !!c.isBot, + session_id: c.sessionId ?? null, + }); +} diff --git a/src/lib/ads.ts b/src/lib/ads.ts index dc7bc33..c636c26 100644 --- a/src/lib/ads.ts +++ b/src/lib/ads.ts @@ -12,6 +12,7 @@ import { createClient } from "@supabase/supabase-js"; export interface Ad { + slug?: string; label: string; size: "300x250" | "300x600" | "728x90"; src: string; @@ -29,27 +30,35 @@ let CACHE: CacheRow | null = null; let REFRESH_IN_FLIGHT: Promise | null = null; const FALLBACK: Ad[] = [ - { label: "LiveU 728x90", size: "728x90", src: "/legacy/ads/728-x-90-pxEN.gif", - alt: "LiveU 728x90", click_url: "https://bit.ly/4ghXVeq" }, - { label: "Blackmagic DaVinci Resolve 20", size: "300x600", + { slug: "liveu-728x90", label: "LiveU 728x90", size: "728x90", + src: "/legacy/ads/728-x-90-pxEN.gif", + alt: "LiveU 728x90", click_url: "https://bit.ly/4ghXVeq" }, + { slug: "blackmagic-davinci-resolve-300x600", + label: "Blackmagic DaVinci Resolve 20", size: "300x600", src: "/legacy/ads/Davinci-Resolve-20_300x600.jpg", alt: "Blackmagic Design DaVinci Resolve 20", click_url: "http://bmd.link/mRNsKu" }, - { label: "Studio Hero", size: "300x250", src: "/legacy/ads/Studio-Hero-for-Broadcast-Beat.png", + { slug: "studio-hero-300x250", label: "Studio Hero", size: "300x250", + src: "/legacy/ads/Studio-Hero-for-Broadcast-Beat.png", alt: "Studio Suite — Studio Hero", click_url: "http://www.TheStudioHero.com" }, - { label: "Magewell Pro-Convert", size: "300x250", + { slug: "magewell-pro-convert-300x250", label: "Magewell Pro-Convert", size: "300x250", src: "/legacy/ads/Magewell_Pro-Convert-IP-to-HDMI_Sports_NAB2026.gif", alt: "Magewell Pro-Convert IP to HDMI", click_url: "https://www.magewell.com" }, - { label: "LiveU PAYG", size: "300x250", src: "/legacy/ads/PAYG-300x250-1.gif", + { slug: "liveu-payg-300x250", label: "LiveU PAYG", size: "300x250", + src: "/legacy/ads/PAYG-300x250-1.gif", alt: "LiveU Pay-As-You-Go", click_url: "https://bit.ly/4ghXVeq" }, - { label: "AJA ColorBox", size: "300x250", src: "/legacy/ads/aja_2025_colorbox_og_colorbox_300x250_en-2.gif", - alt: "AJA ColorBox", click_url: "https://www.aja.com" }, - { label: "Zixi", size: "300x250", src: "/legacy/ads/Zixi_Ads_300x250.png", - alt: "Zixi", click_url: "https://zixi.com/" }, - { label: "Telycam MixOne / ExploreXE", size: "300x250", src: "/legacy/ads/Telycam_BroadcastBeat_300x250_MixOne-ExploreXE_with_NAB2026_2f30157f.gif", + { slug: "aja-colorbox-300x250", label: "AJA ColorBox", size: "300x250", + src: "/legacy/ads/aja_2025_colorbox_og_colorbox_300x250_en-2.gif", + alt: "AJA ColorBox", click_url: "https://www.aja.com" }, + { slug: "zixi-300x250", label: "Zixi", size: "300x250", + src: "/legacy/ads/Zixi_Ads_300x250.png", + alt: "Zixi", click_url: "https://zixi.com/" }, + { slug: "telycam-300x250", label: "Telycam MixOne / ExploreXE", size: "300x250", + src: "/legacy/ads/Telycam_BroadcastBeat_300x250_MixOne-ExploreXE_with_NAB2026_2f30157f.gif", alt: "Telycam MixOne / ExploreXE — NAB 2026", click_url: "https://telycam.com/" }, - { label: "Lectrosonics", size: "300x250", src: "/legacy/ads/300x250-banner-Our-Story-film_resize.jpg", + { slug: "lectrosonics-300x250", label: "Lectrosonics", size: "300x250", + src: "/legacy/ads/300x250-banner-Our-Story-film_resize.jpg", alt: "Lectrosonics Our Story", click_url: "https://www.lectrosonics.com/" }, ]; @@ -82,6 +91,7 @@ async function loadFromDb(): Promise { return FALLBACK; } return (data as DbRow[]).map((r) => ({ + slug: r.slug, label: r.name, size: r.size, src: r.image_path,