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";
|
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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user