import { useEffect, useState } from "react"; import { Link, NavLink } from "react-router-dom"; import { useAuth } from "../context/AuthContext"; import { useDocumentTitle } from "../hooks/useDocumentTitle"; import { fetchCreatorDashboard, exportCreatorData, type CreatorDashboardResponse, } from "../api/creator-dashboard"; import { ApiError } from "../api/client"; import styles from "./CreatorDashboard.module.css"; function SidebarNav() { const linkClass = ({ isActive }: { isActive: boolean }) => `${styles.sidebarLink}${isActive ? ` ${styles.sidebarLinkActive}` : ""}`; return ( ); } export { SidebarNav }; /* ── Stat card ─────────────────────────────────────────────────────────────── */ function StatCard({ value, label }: { value: number; label: string }) { return (
{value.toLocaleString()} {label}
); } /* ── Helpers ────────────────────────────────────────────────────────────────── */ function formatDate(iso: string): string { return new Date(iso).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }); } /** Map processing_status to a badge CSS class name (module-scoped). */ function statusBadgeClass(status: string): string { switch (status.toLowerCase()) { case "complete": case "completed": return styles.badgeComplete ?? ""; case "processing": return styles.badgeProcessing ?? ""; case "error": case "failed": return styles.badgeError ?? ""; default: return styles.badgePending ?? ""; } } /** Map topic_category to a badge CSS class name. */ function categoryBadgeClass(cat: string): string { const slug = cat.toLowerCase().replace(/[\s_]+/g, "-"); const map: Record = { "sound-design": styles.badgeCatSoundDesign, mixing: styles.badgeCatMixing, synthesis: styles.badgeCatSynthesis, arrangement: styles.badgeCatArrangement, workflow: styles.badgeCatWorkflow, mastering: styles.badgeCatMastering, "music-theory": styles.badgeCatMusicTheory, }; return map[slug] ?? styles.badgeCatDefault ?? ""; } /* ── Main component ────────────────────────────────────────────────────────── */ export default function CreatorDashboard() { useDocumentTitle("Creator Dashboard"); const { user } = useAuth(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [exporting, setExporting] = useState(false); const [exportError, setExportError] = useState(null); useEffect(() => { let cancelled = false; setLoading(true); setError(null); fetchCreatorDashboard() .then((res) => { if (!cancelled) setData(res); }) .catch((err) => { if (cancelled) return; if (err instanceof ApiError && err.status === 404) { // No creator profile linked — show friendly empty state setError("not_linked"); } else { setError(err instanceof ApiError ? err.detail : "Failed to load dashboard"); } }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, []); async function handleExport() { setExporting(true); setExportError(null); try { const { blob, filename } = await exportCreatorData(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } catch (err) { setExportError( err instanceof ApiError ? err.detail : "Export failed — please try again." ); } finally { setExporting(false); } } return (

Welcome back{user?.display_name ? `, ${user.display_name}` : ""}

{loading && } {!loading && error === "not_linked" && (

No Creator Profile

Your account isn't linked to a creator profile yet. Contact an admin to get set up.

)} {!loading && error && error !== "not_linked" && (

Could not load dashboard: {error}

)} {!loading && !error && data && ( <> {/* ── Stats row ──────────────────────────────────────────── */}
{/* ── Export ─────────────────────────────────────────────── */}
{exportError && ( {exportError} )}
{/* ── Techniques ─────────────────────────────────────────── */}

Technique Pages

{data.techniques.length === 0 ? (

No technique pages yet.

) : ( <> {/* Desktop table */}
{data.techniques.map((t) => ( ))}
Title Category Moments Created
{t.title} {t.topic_category} {t.key_moment_count} {formatDate(t.created_at)}
{/* Mobile cards */}
{data.techniques.map((t) => (
{t.title}
{t.topic_category} {t.key_moment_count} moments {formatDate(t.created_at)}
))}
)}
{/* ── Videos ──────────────────────────────────────────────── */}

Source Videos

{data.videos.length === 0 ? (

No videos uploaded yet.

) : ( <>
{data.videos.map((v) => ( ))}
Filename Status Uploaded
{v.filename} {v.processing_status} {formatDate(v.created_at)}
{data.videos.map((v) => (
{v.filename}
{v.processing_status} {formatDate(v.created_at)}
))}
)}
)}
); } /* ── Loading skeleton ──────────────────────────────────────────────────────── */ function DashboardSkeleton() { return (
{[1, 2, 3, 4].map((i) => (
))}
); }