feat(ads): dual tracking - AdSanity iframe + bb.banner_analytics with fixed imports and priority prop
This commit is contained in:
@@ -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";
|
||||
import { usePathname } from "next/navigation";
|
||||
import AppImage from "@/components/ui/AppImage";
|
||||
import { createClient } from "@/lib/supabase/client";
|
||||
import type { Ad } from "@/lib/ads";
|
||||
|
||||
const SIZE_DIM: Record<Ad["size"], { w: number; h: number }> = {
|
||||
"300x250": { w: 300, h: 250 },
|
||||
"300x600": { w: 300, h: 600 },
|
||||
"728x90": { w: 728, h: 90 },
|
||||
};
|
||||
|
||||
function getSessionId(): string {
|
||||
if (typeof window === "undefined") return "";
|
||||
let sid = sessionStorage.getItem("bb_session_id");
|
||||
if (!sid) {
|
||||
sid = crypto.randomUUID();
|
||||
sessionStorage.setItem("bb_session_id", sid);
|
||||
}
|
||||
return sid;
|
||||
interface Ad {
|
||||
src: string;
|
||||
label: string;
|
||||
size: string;
|
||||
click_url?: string;
|
||||
adsanity_id?: string;
|
||||
}
|
||||
|
||||
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();
|
||||
const ADSANITY_CAMPAIGN_MAP: Record<string, string> = {
|
||||
'riedel': 'ad-210571',
|
||||
'look-solutions': 'ad-208185',
|
||||
'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'
|
||||
};
|
||||
|
||||
export default function AdImage({ ad, page, priority }: { ad: Ad; page?: string; priority?: boolean }) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const trackedRef = useRef(false);
|
||||
const supabase = createClient();
|
||||
const adsanityId = ad.adsanity_id || ADSANITY_CAMPAIGN_MAP[ad.label.toLowerCase().replace(/\s+/g, '-')] || '';
|
||||
|
||||
useEffect(() => {
|
||||
const el = anchorRef.current;
|
||||
if (!el || impressionFiredRef.current) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting && !impressionFiredRef.current) {
|
||||
impressionFiredRef.current = true;
|
||||
observer.disconnect();
|
||||
void recordEvent("impression");
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 }
|
||||
);
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [ad.src]);
|
||||
|
||||
async function recordEvent(eventType: "impression" | "click") {
|
||||
try {
|
||||
const supabase = createClient();
|
||||
await supabase.from("banner_analytics").insert({
|
||||
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: eventType,
|
||||
page: pathname || (typeof window !== "undefined" ? window.location.pathname : null),
|
||||
session_id: getSessionId(),
|
||||
referrer: typeof document !== "undefined" ? document.referrer || null : null,
|
||||
});
|
||||
} catch {
|
||||
/* tracking is best-effort; never block the user */
|
||||
}
|
||||
event_type: 'impression',
|
||||
page: page || typeof window !== 'undefined' ? window.location.pathname : null,
|
||||
session_id: typeof window !== 'undefined' ? (window as any).__session_id : null,
|
||||
referrer: typeof document !== 'undefined' ? document.referrer : null
|
||||
}).catch(err => console.error('Impression:', err));
|
||||
}
|
||||
}, { threshold: 0.5 });
|
||||
if (containerRef.current) observer.observe(containerRef.current);
|
||||
return () => observer.disconnect();
|
||||
}, [ad, page, supabase]);
|
||||
|
||||
function handleClick() {
|
||||
void recordEvent("click");
|
||||
}
|
||||
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 any).__session_id : null,
|
||||
referrer: typeof document !== 'undefined' ? document.referrer : null
|
||||
}).catch(err => console.error('Click:', err));
|
||||
};
|
||||
|
||||
return (
|
||||
<a
|
||||
ref={anchorRef}
|
||||
href={ad.click_url}
|
||||
target="_blank"
|
||||
rel="sponsored noopener noreferrer"
|
||||
onClick={handleClick}
|
||||
aria-label={`Advertisement — ${ad.label}`}
|
||||
className={`block ad-creative ${className}`}
|
||||
data-ad-size={ad.size}
|
||||
data-ad-label={ad.label}>
|
||||
<AppImage
|
||||
src={ad.src}
|
||||
alt={ad.alt}
|
||||
width={w}
|
||||
height={h}
|
||||
className="max-w-full h-auto"
|
||||
priority={priority}
|
||||
/>
|
||||
</a>
|
||||
<div ref={containerRef} className="ad-container">
|
||||
{adsanityId && (
|
||||
<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')} />
|
||||
)}
|
||||
{ad.click_url ? (
|
||||
<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>
|
||||
) : (
|
||||
<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'}} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user