← back to Wine Finder Next
lib/affiliates/urlBuilder.ts
170 lines
import { AffiliatePartner } from '../types/affiliate';
import { Wine } from '../types/wine';
import { getPartnerById } from './partners';
/**
* Build affiliate URL with tracking parameters
*/
export function buildAffiliateUrl(
wine: Wine,
partnerId: string,
additionalParams?: Record<string, string>
): string {
const partner = getPartnerById(partnerId);
if (!partner) {
console.error(`Unknown affiliate partner: ${partnerId}`);
return wine.url || '#';
}
// If partner not configured, return original URL
if (!partner.affiliateId || partner.affiliateId === 'PENDING') {
console.warn(`Affiliate not configured for ${partnerId}, using direct link`);
return wine.url || partner.baseUrl;
}
// Start with original URL or partner base URL
let baseUrl = wine.url || partner.baseUrl;
// Create URL object for parameter manipulation
let url: URL;
try {
url = new URL(baseUrl);
} catch (error) {
console.error(`Invalid URL: ${baseUrl}`);
return baseUrl;
}
// Add affiliate parameter
url.searchParams.set(partner.affiliateParam, partner.affiliateId);
// Add additional tracking parameters
if (additionalParams) {
Object.entries(additionalParams).forEach(([key, value]) => {
url.searchParams.set(key, value);
});
}
// Add source tracking
url.searchParams.set('utm_source', 'wine-finder');
url.searchParams.set('utm_medium', 'affiliate');
url.searchParams.set('utm_campaign', 'wine-search');
return url.toString();
}
/**
* Build deeplink URL for specific wine
*/
export function buildDeeplinkUrl(
wine: Wine,
partnerId: string
): string {
const partner = getPartnerById(partnerId);
if (!partner || !partner.deeplinkPattern) {
return buildAffiliateUrl(wine, partnerId);
}
// Replace pattern variables
let deeplink = partner.deeplinkPattern;
deeplink = deeplink.replace('{wineName}', encodeURIComponent(wine.name));
deeplink = deeplink.replace('{vintage}', wine.vintage || '');
deeplink = deeplink.replace('{winery}', encodeURIComponent(wine.winery || ''));
return buildAffiliateUrl({ ...wine, url: deeplink }, partnerId);
}
/**
* Build search URL for partner
*/
export function buildSearchUrl(
searchQuery: string,
partnerId: string
): string {
const partner = getPartnerById(partnerId);
if (!partner) {
return '#';
}
const searchUrls: Record<string, string> = {
'vivino': `${partner.baseUrl}/search/wines?q=${encodeURIComponent(searchQuery)}`,
'wine-com': `${partner.baseUrl}/list/wine/${encodeURIComponent(searchQuery)}/7155`,
'total-wine': `${partner.baseUrl}/search/all?text=${encodeURIComponent(searchQuery)}`,
'kl-wines': `${partner.baseUrl}/Products?searchText=${encodeURIComponent(searchQuery)}`,
'wine-searcher': `${partner.baseUrl}/find/${encodeURIComponent(searchQuery)}`,
'wine-access': `${partner.baseUrl}/wine?query=${encodeURIComponent(searchQuery)}`,
};
const searchUrl = searchUrls[partnerId] || `${partner.baseUrl}?q=${encodeURIComponent(searchQuery)}`;
return buildAffiliateUrl({ name: searchQuery, url: searchUrl, source: partner.name, price: '0' }, partnerId);
}
/**
* Get multiple affiliate options for a wine
*/
export function getAffiliateOptions(wine: Wine): Array<{
partnerId: string;
partnerName: string;
url: string;
commissionRate?: string;
type: string;
}> {
const partners = getPartnerById('vivino') ? [
'vivino',
'wine-com',
'total-wine',
'kl-wines',
'wine-access'
] : [];
return partners
.map(partnerId => {
const partner = getPartnerById(partnerId);
if (!partner) return null;
return {
partnerId: partner.id,
partnerName: partner.name,
url: buildAffiliateUrl(wine, partnerId),
commissionRate: partner.commissionRate,
type: partner.type
};
})
.filter(Boolean) as Array<{
partnerId: string;
partnerName: string;
url: string;
commissionRate?: string;
type: string;
}>;
}
/**
* Extract tracking info from URL
*/
export function extractTrackingInfo(url: string): {
partnerId?: string;
affiliateId?: string;
utmParams?: Record<string, string>;
} {
try {
const urlObj = new URL(url);
const utmParams: Record<string, string> = {};
urlObj.searchParams.forEach((value, key) => {
if (key.startsWith('utm_')) {
utmParams[key] = value;
}
});
return {
utmParams: Object.keys(utmParams).length > 0 ? utmParams : undefined
};
} catch (error) {
return {};
}
}