/* global React, Icon, Eyebrow, Badge, Button, api */ const { useState: useStateEO, useEffect: useEffectEO } = React; function EarningsOverview({ onSubmit }){ const [clips, setClips] = useStateEO([]); const [balance, setBalance] = useStateEO(null); const [payouts, setPayouts] = useStateEO([]); const [loading, setLoading] = useStateEO(true); useEffectEO(() => { let mounted = true; (async () => { const [c, b, p] = await Promise.all([api.listMyClips(), api.getMyBalance(), api.listMyPayouts()]); if (!mounted) return; setClips(c.error ? [] : c.data); setBalance(b.error ? null : b.data); setPayouts(p.error ? [] : p.data); setLoading(false); })(); return () => { mounted = false; }; }, []); const approved = clips.filter(c => c.status === "approved"); const pending = clips.filter(c => c.status === "pending"); const totalViews = clips.reduce((s,c) => s + (c.views || 0), 0); const earnedThisWeek = approved .filter(c => c.reviewed_at && (Date.now() - new Date(c.reviewed_at).getTime()) < 7*86400_000) .reduce((s,c) => s + Number(c.earned||0), 0); const lifetimeEarned = approved.reduce((s,c) => s + Number(c.earned||0), 0); const available = balance ? Number(balance.available_balance) : 0; const profile_method = balance && balance.method; const lastPaid = payouts.find(p => p.status === "paid"); const nextPayoutLine = available >= 20 ? "Withdrawable now — request below" : `$${(20 - available).toFixed(2)} to your $20 cashout floor`; if (loading) return ; return (
0 ? `+$${earnedThisWeek.toFixed(2)} this week` : "Approved earnings"} positive/> 0 ? `${pending.length} pending review` : "Up to date"}/>
EARNINGS · ROLLING
${lifetimeEarned.toFixed(2)}
PAYOUT · NEXT FRIDAY
${available.toFixed(2)}
{available >= 20 ? "Available — request payout from the Payouts tab" : `Need $${(20-available).toFixed(2)} more to cash out`}
{clips.length === 0 ? ( ) : (
My clips
{["URL","CAMPAIGN","STATUS","VIEWS","EARNED",""].map(h=>( ))} {clips.slice(0,12).map((c,i) => { const last = i === Math.min(clips.length, 12) - 1; const camp = c.campaigns || {}; const tone = c.status === "approved" ? "approved" : c.status === "rejected" ? "rejected" : "pending"; const tint = camp.tint || "#6366f1"; return ( ); })}
{h}
{shortUrl(c.url)}
{c.platform || "other"} · {timeAgo(c.submitted_at)}
{camp.name || "—"} {c.status} {c.status === "rejected" && c.rejection_reason && (
{c.rejection_reason}
)}
{(c.views||0).toLocaleString()} 0?"#047857":"#6E6D66"}}>{c.earned>0?`$${Number(c.earned).toFixed(2)}`:"—"}
)}
); } function shortUrl(u){ try { const url = new URL(u); return url.hostname.replace(/^www\./,"") + url.pathname.slice(0, 36) + (url.pathname.length > 36 ? "…" : ""); } catch { return u; } } function timeAgo(s){ if (!s) return ""; const ms = Date.now() - new Date(s).getTime(); const m = Math.floor(ms/60000), h = Math.floor(m/60), d = Math.floor(h/24); if (d > 0) return `${d}d ago`; if (h > 0) return `${h}h ago`; if (m > 0) return `${m}m ago`; return "just now"; } function Kpi({label,value,sub,positive}){ return (
{label}
{value}
{sub}
); } function EarningsChart({clips}){ const approvedSorted = (clips || []).filter(c => c.status === "approved" && c.reviewed_at).sort((a,b) => new Date(a.reviewed_at) - new Date(b.reviewed_at)); let cum = 0; const pts = approvedSorted.map(c => { cum += Number(c.earned||0); return cum; }); if (pts.length < 2) { pts.unshift(0); pts.push(0); } const max = Math.max(...pts, 1); const W = 640, H = 160; const step = W / (pts.length-1); const coords = pts.map((v,i) => [i*step, H - (v/max)*H*0.85 - 10]); const d = coords.map((c,i)=>`${i===0?"M":"L"}${c[0]} ${c[1]}`).join(" "); const area = d + ` L${W} ${H} L0 ${H} Z`; return ( {coords.length > 0 && } ); } function EmptyState({onSubmit}){ return (
No clips yet — let's change that.
Browse the live campaigns, grab the brief, and submit your first TikTok / Reels / Shorts URL. First clip past 1,000 views starts earning.
); } function LoadingPanel(){ return
Loading…
; } window.EarningsOverview = EarningsOverview;