← back to Wine Finder Next
lib/affiliates/tracker.ts
131 lines
import { AffiliateClick } from '../types/affiliate';
import { Wine } from '../types/wine';
/**
* Track affiliate click
*/
export async function trackAffiliateClick(
wine: Wine,
partnerId: string,
affiliateUrl: string
): Promise<void> {
const clickData: AffiliateClick = {
timestamp: new Date().toISOString(),
wineId: wine.id,
wineName: wine.name,
partnerId,
partnerName: wine.source,
affiliateUrl,
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
referrer: typeof document !== 'undefined' ? document.referrer : undefined
};
try {
// Send to tracking API
await fetch('/api/affiliate/track', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(clickData),
});
// Log to console in development
if (process.env.NODE_ENV === 'development') {
console.log('Affiliate click tracked:', clickData);
}
} catch (error) {
console.error('Failed to track affiliate click:', error);
}
}
/**
* Track multiple affiliate clicks (for comparison views)
*/
export async function trackBulkClicks(clicks: Array<{
wine: Wine;
partnerId: string;
affiliateUrl: string;
}>): Promise<void> {
const clicksData = clicks.map(({ wine, partnerId, affiliateUrl }) => ({
timestamp: new Date().toISOString(),
wineId: wine.id,
wineName: wine.name,
partnerId,
partnerName: wine.source,
affiliateUrl,
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
referrer: typeof document !== 'undefined' ? document.referrer : undefined
}));
try {
await fetch('/api/affiliate/track', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ bulk: true, clicks: clicksData }),
});
} catch (error) {
console.error('Failed to track bulk clicks:', error);
}
}
/**
* Get affiliate click stats
*/
export async function getAffiliateStats(
partnerId?: string,
startDate?: string,
endDate?: string
): Promise<any> {
try {
const params = new URLSearchParams();
if (partnerId) params.set('partnerId', partnerId);
if (startDate) params.set('startDate', startDate);
if (endDate) params.set('endDate', endDate);
const response = await fetch(`/api/affiliate/stats?${params.toString()}`);
return await response.json();
} catch (error) {
console.error('Failed to get affiliate stats:', error);
return null;
}
}
/**
* Track wine view (impression)
*/
export function trackWineImpression(wine: Wine): void {
if (typeof window === 'undefined') return;
// Use Intersection Observer for accurate impression tracking
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
fetch('/api/affiliate/impression', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
wineId: wine.id,
wineName: wine.name,
source: wine.source,
timestamp: new Date().toISOString()
})
}).catch(err => console.error('Failed to track impression:', err));
observer.disconnect();
}
});
},
{ threshold: 0.5 }
);
// Find the wine card element and observe it
const element = document.querySelector(`[data-wine-id="${wine.id}"]`);
if (element) {
observer.observe(element);
}
}