feat: implement sponsored ads module end-to-end

This commit is contained in:
CycroftX
2026-02-10 15:44:35 +05:30
parent 3e1641d281
commit 04e2db6571
17 changed files with 2368 additions and 2 deletions

View File

@@ -0,0 +1,193 @@
// Sponsored Ads — Mock Data: Campaigns, Tracking Events, Daily Stats
import type { Campaign, AdTrackingEvent, PlacementDailyStats } from '@/lib/types/ads';
// ===== MOCK CAMPAIGNS =====
export const MOCK_CAMPAIGNS: Campaign[] = [
{
id: 'camp-001',
partnerId: 'partner-sw',
partnerName: 'SoundWave Productions',
name: 'Mumbai Music Festival Premium Push',
objective: 'AWARENESS',
status: 'ACTIVE',
startAt: '2026-02-01T00:00:00Z',
endAt: '2026-03-15T23:59:59Z',
billingModel: 'CPM',
totalBudget: 50000,
dailyCap: 2500,
spent: 18750,
targeting: { cityIds: ['mumbai', 'pune'], categoryIds: ['music'], countryCodes: ['IN'] },
surfaceKeys: ['HOME_FEATURED_CAROUSEL', 'CITY_TRENDING'],
eventIds: ['evt-101'],
frequencyCap: 5,
approvedBy: 'admin-1',
rejectedReason: null,
createdBy: 'admin-1',
createdAt: '2026-01-25T10:00:00Z',
updatedAt: '2026-02-10T08:00:00Z',
},
{
id: 'camp-002',
partnerId: 'partner-sb',
partnerName: 'Sunburn Events',
name: 'Goa Sunburn Early Bird Blitz',
objective: 'SALES',
status: 'IN_REVIEW',
startAt: '2026-03-01T00:00:00Z',
endAt: '2026-04-25T23:59:59Z',
billingModel: 'CPC',
totalBudget: 75000,
dailyCap: 5000,
spent: 0,
targeting: { cityIds: [], categoryIds: ['music', 'nightlife'], countryCodes: ['IN'] },
surfaceKeys: ['HOME_FEATURED_CAROUSEL', 'HOME_TOP_EVENTS', 'SEARCH_BOOSTED'],
eventIds: ['evt-107'],
frequencyCap: 3,
approvedBy: null,
rejectedReason: null,
createdBy: 'admin-1',
createdAt: '2026-02-08T14:00:00Z',
updatedAt: '2026-02-08T14:00:00Z',
},
{
id: 'camp-003',
partnerId: 'partner-tc',
partnerName: 'TechConf India',
name: 'Delhi Tech Summit Sponsor Package',
objective: 'AWARENESS',
status: 'DRAFT',
startAt: '2026-03-10T00:00:00Z',
endAt: '2026-03-21T23:59:59Z',
billingModel: 'FIXED',
totalBudget: 30000,
dailyCap: null,
spent: 0,
targeting: { cityIds: ['delhi'], categoryIds: ['technology', 'business'], countryCodes: ['IN'] },
surfaceKeys: ['HOME_TOP_EVENTS', 'CATEGORY_FEATURED'],
eventIds: ['evt-102'],
frequencyCap: 0,
approvedBy: null,
rejectedReason: null,
createdBy: 'admin-1',
createdAt: '2026-02-09T11:00:00Z',
updatedAt: '2026-02-09T11:00:00Z',
},
{
id: 'camp-004',
partnerId: 'partner-ri',
partnerName: 'RunIndia',
name: 'Pune Marathon Registration Drive',
objective: 'SALES',
status: 'ENDED',
startAt: '2026-01-15T00:00:00Z',
endAt: '2026-02-05T23:59:59Z',
billingModel: 'CPM',
totalBudget: 20000,
dailyCap: 1500,
spent: 19800,
targeting: { cityIds: ['pune', 'mumbai'], categoryIds: ['sports'], countryCodes: ['IN'] },
surfaceKeys: ['HOME_TOP_EVENTS', 'CITY_TRENDING'],
eventIds: ['evt-106'],
frequencyCap: 4,
approvedBy: 'admin-1',
rejectedReason: null,
createdBy: 'admin-1',
createdAt: '2026-01-10T09:00:00Z',
updatedAt: '2026-02-05T23:59:59Z',
},
];
// ===== MOCK TRACKING EVENTS (for camp-001) =====
function genTrackingEvents(): AdTrackingEvent[] {
const events: AdTrackingEvent[] = [];
const surfaces = ['HOME_FEATURED_CAROUSEL', 'CITY_TRENDING'] as const;
const devices = ['mobile-ios', 'mobile-android', 'web-desktop', 'web-mobile'];
const cities = ['mumbai', 'pune'];
const baseTime = new Date('2026-02-03T00:00:00Z');
for (let day = 0; day < 7; day++) {
const impressionsPerDay = 80 + Math.floor(Math.random() * 40);
for (let i = 0; i < impressionsPerDay; i++) {
const ts = new Date(baseTime.getTime() + day * 86400000 + Math.floor(Math.random() * 86400000));
const surface = surfaces[Math.floor(Math.random() * surfaces.length)];
events.push({
id: `te-imp-${day}-${i}`,
type: 'IMPRESSION',
placementId: `splc-camp001-${surface}`,
campaignId: 'camp-001',
surfaceKey: surface,
eventId: 'evt-101',
userId: Math.random() > 0.4 ? `user-${100 + Math.floor(Math.random() * 50)}` : null,
anonId: `anon-${Math.floor(Math.random() * 200)}`,
sessionId: `sess-${day}-${Math.floor(Math.random() * 100)}`,
timestamp: ts.toISOString(),
device: devices[Math.floor(Math.random() * devices.length)],
cityId: cities[Math.floor(Math.random() * cities.length)],
});
}
// Clicks (~8-15% of impressions)
const clicksPerDay = Math.floor(impressionsPerDay * (0.08 + Math.random() * 0.07));
for (let c = 0; c < clicksPerDay; c++) {
const ts = new Date(baseTime.getTime() + day * 86400000 + Math.floor(Math.random() * 86400000));
const surface = surfaces[Math.floor(Math.random() * surfaces.length)];
events.push({
id: `te-clk-${day}-${c}`,
type: 'CLICK',
placementId: `splc-camp001-${surface}`,
campaignId: 'camp-001',
surfaceKey: surface,
eventId: 'evt-101',
userId: Math.random() > 0.3 ? `user-${100 + Math.floor(Math.random() * 50)}` : null,
anonId: `anon-${Math.floor(Math.random() * 200)}`,
sessionId: `sess-${day}-${Math.floor(Math.random() * 100)}`,
timestamp: ts.toISOString(),
device: devices[Math.floor(Math.random() * devices.length)],
cityId: cities[Math.floor(Math.random() * cities.length)],
});
}
}
return events;
}
export const MOCK_TRACKING_EVENTS = genTrackingEvents();
// ===== MOCK DAILY STATS =====
export function generateMockDailyStats(): PlacementDailyStats[] {
const stats: PlacementDailyStats[] = [];
const baseDate = new Date('2026-02-03');
const cpmRate = 50000 / (7 * 100); // simplified: budget / (days * avg impressions per batch)
for (let day = 0; day < 7; day++) {
const date = new Date(baseDate.getTime() + day * 86400000).toISOString().slice(0, 10);
const surfaces = ['HOME_FEATURED_CAROUSEL', 'CITY_TRENDING'] as const;
for (const surface of surfaces) {
const impressions = 40 + Math.floor(Math.random() * 25);
const clicks = Math.floor(impressions * (0.08 + Math.random() * 0.07));
const ctr = impressions > 0 ? Number((clicks / impressions).toFixed(4)) : 0;
const spend = Number(((impressions / 1000) * 250).toFixed(2)); // ₹250 CPM
stats.push({
id: `ds-${day}-${surface}`,
campaignId: 'camp-001',
placementId: `splc-camp001-${surface}`,
surfaceKey: surface,
date,
impressions,
clicks,
ctr,
spend,
});
}
}
return stats;
}
export const MOCK_DAILY_STATS = generateMockDailyStats();