/* global React, Icon, Eyebrow, Badge, Button, api */ const { useState: useStateCE, useEffect: useEffectCE } = React; function CampaignEditor({ onChanged }){ const [campaigns, setCampaigns] = useStateCE([]); const [editing, setEditing] = useStateCE(null); const [loading, setLoading] = useStateCE(true); const [busy, setBusy] = useStateCE(false); const [errMsg, setErrMsg] = useStateCE(""); const reload = async () => { setLoading(true); const r = await api.listAllCampaigns(); setCampaigns(r.error ? [] : r.data); setLoading(false); }; useEffectCE(() => { reload(); }, []); const startNew = () => setEditing({ slug: "", name: "", tag: "", description: "", brief_md: "", rpm: 1.00, min_views: 1000, payout_floor: 20, monthly_budget: 25000, budget_remaining: 25000, status: "draft", tint: "#6366f1", examples: [], assets: [], dos: [], donts: [], discord_url: "", }); const startEdit = (c) => setEditing({ ...c }); const save = async () => { setErrMsg(""); setBusy(true); if (!editing.name || !editing.slug) { setErrMsg("Name and slug required."); setBusy(false); return; } const payload = { ...editing, rpm: Number(editing.rpm) || 0, min_views: parseInt(editing.min_views, 10) || 0, payout_floor: Number(editing.payout_floor) || 0, monthly_budget: Number(editing.monthly_budget) || 0, budget_remaining: Number(editing.budget_remaining) || 0, examples: parseList(editing.examples, "examples"), assets: parseList(editing.assets, "assets"), dos: parseList(editing.dos, "dos"), donts: parseList(editing.donts, "donts"), }; const r = await api.upsertCampaign(payload); setBusy(false); if (r.error) { setErrMsg(r.error.message || "Save failed."); return; } setEditing(null); await reload(); onChanged && onChanged(); }; function parseList(v, kind){ if (Array.isArray(v)) return v; if (typeof v === "string") { const t = v.trim(); if (!t) return []; if (t.startsWith("[")) { try { return JSON.parse(t); } catch { return []; } } // newline-separated simple strings (for dos/donts) if (kind === "dos" || kind === "donts") return t.split("\n").map(x=>x.trim()).filter(Boolean); return []; } return []; } if (editing) return setEditing(null)}/>; return (
CAMPAIGNS
Manage your campaigns
{loading ?
Loading…
: campaigns.length === 0 ? (
No campaigns yet. Create one to start accepting clips.
) : (
{campaigns.map(c => (

{c.name}

{c.status} /{c.slug}
RPM ${Number(c.rpm).toFixed(2)} · Min {(c.min_views||0).toLocaleString()} views · Floor ${Number(c.payout_floor).toFixed(0)} · Budget ${Number(c.budget_remaining||0).toLocaleString()} / ${Number(c.monthly_budget||0).toLocaleString()}
))}
)}
); } function Editor({ editing, setEditing, save, busy, errMsg, onCancel }){ const set = (k,v) => setEditing(e => ({...e, [k]: v})); const isNew = !editing.id; return (
{isNew ? "NEW CAMPAIGN" : "EDIT CAMPAIGN"}
{editing.name || "Untitled campaign"}
{errMsg &&
{errMsg}
} set("name", v)} placeholder="Rizz — AI dating replies"/>set("slug", v.toLowerCase().replace(/[^a-z0-9-]/g,"-"))} placeholder="rizz"/> set("tag", v)} placeholder="Dating · Lifestyle"/> set("description", v)} placeholder="One-sentence summary."/> set("brief_md", v)} placeholder="Hook within 1.5s. Use the POV format. Show the app screen recording…"/> set("tint", v)} placeholder="#6366f1"/> set("status", v)}/> set("discord_url", v)} placeholder="https://discord.gg/xxxxx"/> set("rpm", v)}/>set("min_views", v)}/> set("payout_floor", v)}/>set("monthly_budget", v)}/> set("budget_remaining", v)}/> set("examples", v)}/> set("dos", v)}/> set("donts", v)}/> set("assets", v)}/>
); } function asTextLines(v){ if (Array.isArray(v)) return v.join("\n"); return v || ""; } function asJson(v){ if (typeof v === "string") return v; try { return JSON.stringify(v || [], null, 2); } catch { return "[]"; } } function Card({title, children}){ return (

{title}

{children}
); } function Row({children}){ return
{children}
; } function Field({label, value, onChange, placeholder, type="text", multiline, rows=3, step, select, options}){ return (