← 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);
  }
}