206 lines
5.9 KiB
TypeScript
206 lines
5.9 KiB
TypeScript
// ============================================================================
|
|
// Demo API - Mock responses for demo mode
|
|
// ============================================================================
|
|
|
|
import type {
|
|
PaginatedResponse,
|
|
Workspace,
|
|
Project,
|
|
Channel,
|
|
Creative,
|
|
Placement,
|
|
PurchasePlanChannel,
|
|
WorkspaceMember,
|
|
SpendingAnalytics,
|
|
CreativeAnalyticsItem,
|
|
ChannelAnalyticsItem,
|
|
OverviewAnalytics,
|
|
} from "@/lib/types/api";
|
|
import {
|
|
demoWorkspace,
|
|
demoMember,
|
|
demoProjects,
|
|
demoChannels,
|
|
demoCreatives,
|
|
demoPlacements,
|
|
demoPurchasePlanChannels,
|
|
demoSpendingAnalytics,
|
|
demoCreativeAnalytics,
|
|
demoChannelAnalytics,
|
|
demoOverviewAnalytics,
|
|
} from "./mock-data";
|
|
import { DEMO_USER } from "./constants";
|
|
|
|
// ============================================================================
|
|
// Helper to create paginated response
|
|
// ============================================================================
|
|
|
|
const paginate = <T>(items: T[], page = 1, size = 50): PaginatedResponse<T> => ({
|
|
items: items.slice((page - 1) * size, page * size),
|
|
total: items.length,
|
|
page,
|
|
size,
|
|
pages: Math.ceil(items.length / size),
|
|
});
|
|
|
|
// ============================================================================
|
|
// Demo API Methods
|
|
// ============================================================================
|
|
|
|
export const demoApi = {
|
|
// Auth
|
|
auth: {
|
|
me: () => Promise.resolve(DEMO_USER),
|
|
},
|
|
|
|
// Workspaces
|
|
workspaces: {
|
|
list: () => Promise.resolve(paginate([demoWorkspace])),
|
|
get: () => Promise.resolve(demoWorkspace),
|
|
},
|
|
|
|
// Members
|
|
members: {
|
|
list: () => Promise.resolve(paginate([demoMember])),
|
|
},
|
|
|
|
// Projects
|
|
projects: {
|
|
list: (params?: { project_id?: string }) => {
|
|
let items = demoProjects;
|
|
if (params?.project_id) {
|
|
items = items.filter((p) => p.id === params.project_id);
|
|
}
|
|
return Promise.resolve(paginate(items));
|
|
},
|
|
},
|
|
|
|
// Channels
|
|
channels: {
|
|
list: () => Promise.resolve(paginate(demoChannels)),
|
|
},
|
|
|
|
// Creatives
|
|
creatives: {
|
|
list: (params?: { project_id?: string; include_archived?: boolean }) => {
|
|
let items = demoCreatives;
|
|
if (params?.project_id) {
|
|
items = items.filter((c) => c.project_id === params.project_id);
|
|
}
|
|
if (!params?.include_archived) {
|
|
items = items.filter((c) => c.status === "active");
|
|
}
|
|
return Promise.resolve(paginate(items));
|
|
},
|
|
get: (id: string) => {
|
|
const creative = demoCreatives.find((c) => c.id === id);
|
|
if (!creative) throw new Error("Creative not found");
|
|
return Promise.resolve(creative);
|
|
},
|
|
},
|
|
|
|
// Placements
|
|
placements: {
|
|
list: (params?: {
|
|
project_id?: string;
|
|
placement_channel_id?: string;
|
|
creative_id?: string;
|
|
include_archived?: boolean;
|
|
}) => {
|
|
let items = demoPlacements;
|
|
if (params?.project_id) {
|
|
items = items.filter((p) => p.project_id === params.project_id);
|
|
}
|
|
if (params?.placement_channel_id) {
|
|
items = items.filter(
|
|
(p) => p.placement_channel_id === params.placement_channel_id
|
|
);
|
|
}
|
|
if (params?.creative_id) {
|
|
items = items.filter((p) => p.creative_id === params.creative_id);
|
|
}
|
|
if (!params?.include_archived) {
|
|
items = items.filter((p) => p.status === "active");
|
|
}
|
|
return Promise.resolve(paginate(items));
|
|
},
|
|
get: (id: string) => {
|
|
const placement = demoPlacements.find((p) => p.id === id);
|
|
if (!placement) throw new Error("Placement not found");
|
|
return Promise.resolve(placement);
|
|
},
|
|
},
|
|
|
|
// Purchase Plan
|
|
purchasePlan: {
|
|
list: (projectId: string) => {
|
|
const items = demoPurchasePlanChannels[projectId] || [];
|
|
return Promise.resolve(paginate(items));
|
|
},
|
|
},
|
|
|
|
// Analytics
|
|
analytics: {
|
|
spending: (params?: { project_id?: string; grouping?: string }) => {
|
|
// Filter by project if specified
|
|
if (params?.project_id) {
|
|
const projectPlacements = demoPlacements.filter(
|
|
(p) => p.project_id === params.project_id && p.status === "active"
|
|
);
|
|
const totalCost = projectPlacements.reduce(
|
|
(sum, p) => sum + (p.cost || 0),
|
|
0
|
|
);
|
|
const totalSubs = projectPlacements.reduce(
|
|
(sum, p) => sum + (p.subscriptions_count || 0),
|
|
0
|
|
);
|
|
const totalViews = projectPlacements.reduce(
|
|
(sum, p) => sum + (p.views_count || 0),
|
|
0
|
|
);
|
|
|
|
return Promise.resolve({
|
|
total_cost: totalCost,
|
|
total_subscriptions: totalSubs,
|
|
total_views: totalViews,
|
|
avg_cpf: totalSubs > 0 ? totalCost / totalSubs : null,
|
|
avg_cpm: totalViews > 0 ? (totalCost / totalViews) * 1000 : null,
|
|
chart_data: demoSpendingAnalytics.chart_data.slice(0, 3),
|
|
} as SpendingAnalytics);
|
|
}
|
|
return Promise.resolve(demoSpendingAnalytics);
|
|
},
|
|
creatives: (params?: { project_id?: string }) => {
|
|
let items = demoCreativeAnalytics;
|
|
if (params?.project_id) {
|
|
const projectCreativeIds = demoCreatives
|
|
.filter((c) => c.project_id === params.project_id)
|
|
.map((c) => c.id);
|
|
items = items.filter((a) => projectCreativeIds.includes(a.id));
|
|
}
|
|
return Promise.resolve(paginate(items));
|
|
},
|
|
channels: (params?: { project_id?: string }) => {
|
|
let items = demoChannelAnalytics;
|
|
if (params?.project_id) {
|
|
const projectPlacementChannelIds = new Set(
|
|
demoPlacements
|
|
.filter((p) => p.project_id === params.project_id)
|
|
.map((p) => p.placement_channel_id)
|
|
);
|
|
items = items.filter((a) => projectPlacementChannelIds.has(a.channel_id));
|
|
}
|
|
return Promise.resolve(paginate(items));
|
|
},
|
|
overview: (_params?: {
|
|
project_id?: string;
|
|
date_from?: string;
|
|
date_to?: string;
|
|
}): Promise<OverviewAnalytics> => {
|
|
return Promise.resolve(demoOverviewAnalytics);
|
|
},
|
|
},
|
|
};
|
|
|