feat(ads): dual tracking - AdSanity iframe + bb.banner_analytics with fixed imports and priority prop

This commit is contained in:
Ryan
2026-05-11 02:54:13 +00:00
parent fc003fd285
commit 031ff6f220

View File

@@ -1,103 +1,88 @@
"use client"; 'use client';
import { useEffect, useRef } from 'react';
import AppImage from '@/components/ui/AppImage';
import { createClient } from '@/lib/supabase/client';
import { useEffect, useRef } from "react"; interface Ad {
import { usePathname } from "next/navigation"; src: string;
import AppImage from "@/components/ui/AppImage"; label: string;
import { createClient } from "@/lib/supabase/client"; size: string;
import type { Ad } from "@/lib/ads"; click_url?: string;
adsanity_id?: string;
}
const SIZE_DIM: Record<Ad["size"], { w: number; h: number }> = { const ADSANITY_CAMPAIGN_MAP: Record<string, string> = {
"300x250": { w: 300, h: 250 }, 'riedel': 'ad-210571',
"300x600": { w: 300, h: 600 }, 'look-solutions': 'ad-208185',
"728x90": { w: 728, h: 90 }, 'opengear': 'ad-200174',
'telycam': 'ad-191740',
'livu-728': 'ad-189081',
'aja': 'ad-164285',
'studio-suite': 'ad-162714',
'zixi': 'ad-156737',
'lectrosonics': 'ad-156727',
'livu-300': 'ad-156486',
'magewell': 'ad-154296',
'blackmagic': 'ad-210571',
'magewell-300': 'ad-154296',
'studio-suite-300': 'ad-162714',
'zixi-300': 'ad-156737',
'lectrosonics-300': 'ad-156727',
'aja-300': 'ad-164285',
'livu-300-2': 'ad-156486',
'opengear-300': 'ad-200174',
'look-solutions-300': 'ad-208185',
'riedel-300': 'ad-210571',
'telycam-300': 'ad-191740'
}; };
function getSessionId(): string { export default function AdImage({ ad, page, priority }: { ad: Ad; page?: string; priority?: boolean }) {
if (typeof window === "undefined") return ""; const containerRef = useRef<HTMLDivElement>(null);
let sid = sessionStorage.getItem("bb_session_id"); const trackedRef = useRef(false);
if (!sid) { const supabase = createClient();
sid = crypto.randomUUID(); const adsanityId = ad.adsanity_id || ADSANITY_CAMPAIGN_MAP[ad.label.toLowerCase().replace(/\s+/g, '-')] || '';
sessionStorage.setItem("bb_session_id", sid);
}
return sid;
}
export default function AdImage({
ad,
className = "",
priority = false,
}: {
ad: Ad;
className?: string;
priority?: boolean;
}) {
const { w, h } = SIZE_DIM[ad.size];
const anchorRef = useRef<HTMLAnchorElement | null>(null);
const impressionFiredRef = useRef(false);
const pathname = usePathname();
useEffect(() => { useEffect(() => {
const el = anchorRef.current; const observer = new IntersectionObserver(([entry]) => {
if (!el || impressionFiredRef.current) return; if (entry.isIntersecting && !trackedRef.current) {
trackedRef.current = true;
const observer = new IntersectionObserver( supabase.from('banner_analytics').insert({
(entries) => { ad_src: ad.src,
for (const entry of entries) { ad_label: ad.label,
if (entry.isIntersecting && !impressionFiredRef.current) { ad_size: ad.size,
impressionFiredRef.current = true; event_type: 'impression',
observer.disconnect(); page: page || typeof window !== 'undefined' ? window.location.pathname : null,
void recordEvent("impression"); session_id: typeof window !== 'undefined' ? (window as any).__session_id : null,
break; referrer: typeof document !== 'undefined' ? document.referrer : null
} }).catch(err => console.error('Impression:', err));
} }
}, }, { threshold: 0.5 });
{ threshold: 0.5 } if (containerRef.current) observer.observe(containerRef.current);
);
observer.observe(el);
return () => observer.disconnect(); return () => observer.disconnect();
// eslint-disable-next-line react-hooks/exhaustive-deps }, [ad, page, supabase]);
}, [ad.src]);
async function recordEvent(eventType: "impression" | "click") { const handleClick = () => {
try { supabase.from('banner_analytics').insert({
const supabase = createClient(); ad_src: ad.src,
await supabase.from("banner_analytics").insert({ ad_label: ad.label,
ad_src: ad.src, ad_size: ad.size,
ad_label: ad.label, event_type: 'click',
ad_size: ad.size, page: page || typeof window !== 'undefined' ? window.location.pathname : null,
event_type: eventType, session_id: typeof window !== 'undefined' ? (window as any).__session_id : null,
page: pathname || (typeof window !== "undefined" ? window.location.pathname : null), referrer: typeof document !== 'undefined' ? document.referrer : null
session_id: getSessionId(), }).catch(err => console.error('Click:', err));
referrer: typeof document !== "undefined" ? document.referrer || null : null, };
});
} catch {
/* tracking is best-effort; never block the user */
}
}
function handleClick() {
void recordEvent("click");
}
return ( return (
<a <div ref={containerRef} className="ad-container">
ref={anchorRef} {adsanityId && (
href={ad.click_url} <iframe src={`https://ads.broadcastbeat.com/wp-content/plugins/adsanity/render.php?id=${adsanityId}`} width={parseInt(ad.size.split('x')[0])} height={parseInt(ad.size.split('x')[1])} frameBorder="0" scrolling="no" style={{display:'block'}} onError={() => console.warn('AdSanity iframe failed, bb.banner_analytics tracking active')} />
target="_blank" )}
rel="sponsored noopener noreferrer" {ad.click_url ? (
onClick={handleClick} <a href={ad.click_url} rel="sponsored noopener noreferrer" onClick={handleClick} style={{display:'none'}}><AppImage src={ad.src} alt={ad.label} width={parseInt(ad.size.split('x')[0])} height={parseInt(ad.size.split('x')[1])} priority={priority} /></a>
aria-label={`Advertisement — ${ad.label}`} ) : (
className={`block ad-creative ${className}`} <AppImage src={ad.src} alt={ad.label} width={parseInt(ad.size.split('x')[0])} height={parseInt(ad.size.split('x')[1])} priority={priority} style={{display:'none'}} />
data-ad-size={ad.size} )}
data-ad-label={ad.label}> </div>
<AppImage
src={ad.src}
alt={ad.alt}
width={w}
height={h}
className="max-w-full h-auto"
priority={priority}
/>
</a>
); );
} }