feat(campaigns): add frontend campaign API module and types
Adds the typed API client for campaign management (Task 7): campaign.ts types, campaignApi module wired to the backend's /campaigns endpoints, and registration in the aggregated api index. This is the API layer only; the list page and wizard land in Tasks 8/9.
This commit is contained in:
@@ -22,6 +22,7 @@ import { diagnosticsApi } from "./modules/diagnostics";
|
||||
import { personaApi } from "./modules/persona";
|
||||
import { brandApi } from "./modules/brand";
|
||||
import { crmApi } from "./modules/crm";
|
||||
import { campaignApi } from "./modules/campaign";
|
||||
|
||||
export const api = {
|
||||
// Root
|
||||
@@ -80,6 +81,9 @@ export const api = {
|
||||
|
||||
// First-party CRM
|
||||
...crmApi,
|
||||
|
||||
// Campaigns
|
||||
...campaignApi,
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { request } from "../request";
|
||||
import type {
|
||||
AdAccount,
|
||||
Campaign,
|
||||
CampaignEvent,
|
||||
CampaignPayload,
|
||||
PreviewResponse,
|
||||
} from "../types/campaign";
|
||||
|
||||
export const campaignApi = {
|
||||
listCampaigns: (params?: { company_id?: string; status_filter?: string }) => {
|
||||
const search = new URLSearchParams();
|
||||
if (params?.company_id) search.append("company_id", params.company_id);
|
||||
if (params?.status_filter) search.append("status_filter", params.status_filter);
|
||||
const query = search.toString();
|
||||
return request<Campaign[]>(`/campaigns${query ? `?${query}` : ""}`);
|
||||
},
|
||||
|
||||
getCampaign: (id: string) =>
|
||||
request<Campaign>(`/campaigns/${encodeURIComponent(id)}`),
|
||||
|
||||
createCampaign: (payload: CampaignPayload) =>
|
||||
request<Campaign>("/campaigns", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
|
||||
updateCampaign: (id: string, payload: CampaignPayload) =>
|
||||
request<Campaign>(`/campaigns/${encodeURIComponent(id)}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
|
||||
deleteCampaign: (id: string) =>
|
||||
request<void>(`/campaigns/${encodeURIComponent(id)}`, { method: "DELETE" }),
|
||||
|
||||
submitCampaign: (id: string, actor?: string) =>
|
||||
request<Campaign>(`/campaigns/${encodeURIComponent(id)}/submit`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ actor: actor ?? null }),
|
||||
}),
|
||||
|
||||
approveCampaign: (id: string, actor?: string) =>
|
||||
request<Campaign>(`/campaigns/${encodeURIComponent(id)}/approve`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ actor: actor ?? null }),
|
||||
}),
|
||||
|
||||
rejectCampaign: (id: string, reason?: string) =>
|
||||
request<Campaign>(`/campaigns/${encodeURIComponent(id)}/reject`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ reason: reason ?? null }),
|
||||
}),
|
||||
|
||||
previewCampaign: (payload: {
|
||||
ad_account_id: string;
|
||||
creative: Record<string, unknown>;
|
||||
ad_formats?: string[];
|
||||
}) =>
|
||||
request<PreviewResponse>("/campaigns/preview", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
|
||||
getAdAccount: (adAccountId: string) =>
|
||||
request<AdAccount>(`/campaigns/account/${encodeURIComponent(adAccountId)}`),
|
||||
|
||||
listCampaignEvents: (id: string) =>
|
||||
request<CampaignEvent[]>(`/campaigns/${encodeURIComponent(id)}/events`),
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
export type CampaignStatus =
|
||||
| "draft"
|
||||
| "pending_approval"
|
||||
| "approved"
|
||||
| "synced"
|
||||
| "live"
|
||||
| "paused"
|
||||
| "stopped"
|
||||
| "archived";
|
||||
|
||||
export interface CampaignBudget {
|
||||
daily_budget?: number;
|
||||
lifetime_budget?: number;
|
||||
currency?: string;
|
||||
bid_strategy?: string;
|
||||
bid_amount?: number;
|
||||
}
|
||||
|
||||
export interface CampaignGuardrails {
|
||||
daily_budget_limit?: number;
|
||||
lifetime_budget_limit?: number;
|
||||
max_campaign_spend?: number;
|
||||
max_cost_per_lead?: number;
|
||||
max_cost_per_click?: number;
|
||||
auto_pause_if_spend_reaches?: number;
|
||||
require_approval_for_budget_increase?: boolean;
|
||||
stop_loss_enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface Campaign {
|
||||
id: string;
|
||||
company_id: string | null;
|
||||
name: string;
|
||||
status: CampaignStatus;
|
||||
origin: "maskanx" | "imported";
|
||||
objective: string | null;
|
||||
ad_account_id: string | null;
|
||||
budget: CampaignBudget;
|
||||
guardrails: CampaignGuardrails;
|
||||
targeting: Record<string, unknown>;
|
||||
advanced: Record<string, unknown>;
|
||||
channels: string[];
|
||||
schedule: Record<string, unknown>;
|
||||
meta_campaign_id: string | null;
|
||||
sync_status: string;
|
||||
sync_error: string | null;
|
||||
approved_by: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface CampaignPayload {
|
||||
name: string;
|
||||
company_id?: string | null;
|
||||
objective?: string | null;
|
||||
ad_account_id?: string | null;
|
||||
budget: CampaignBudget;
|
||||
guardrails: CampaignGuardrails;
|
||||
targeting?: Record<string, unknown>;
|
||||
advanced?: Record<string, unknown>;
|
||||
channels?: string[];
|
||||
schedule?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface AdAccount {
|
||||
id: string;
|
||||
name?: string;
|
||||
currency?: string;
|
||||
balance?: string;
|
||||
amount_spent?: string;
|
||||
spend_cap?: string;
|
||||
min_daily_budget?: number;
|
||||
is_prepay_account?: boolean;
|
||||
funding_source?: string;
|
||||
account_status?: number;
|
||||
}
|
||||
|
||||
export interface PreviewResponse {
|
||||
previews: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface CampaignEvent {
|
||||
id: string;
|
||||
campaign_id: string;
|
||||
event_type: string;
|
||||
actor: string | null;
|
||||
reason: string | null;
|
||||
payload: Record<string, unknown>;
|
||||
occurred_at: string;
|
||||
}
|
||||
Reference in New Issue
Block a user