import { useState, useEffect } from 'react'; import { apiFetch } from '../lib/api'; // ── Types ──────────────────────────────────────────────────────────────────── interface Video { id: string; title: string; storageType: string; storageKey: string; requiredLevel: number; isPublished: boolean; createdAt: string; } interface Plan { id: string; slug: string; name: string; level: number; priceInCents: number; isActive: boolean; } interface AdminUser { id: string; email: string | null; nickname: string; isActive: boolean; createdAt: string; roles: { id: string; slug: string; name: string }[]; activeSubscription: { id: string; status: string; endsAt: string | null; plan: Plan; } | null; } // ── Tabs ───────────────────────────────────────────────────────────────────── type Tab = 'videos' | 'users' | 'plans'; export default function AdminPage() { const [tab, setTab] = useState('videos'); return (
{(['videos', 'users', 'plans'] as Tab[]).map((t) => ( ))}
{tab === 'videos' && } {tab === 'users' && } {tab === 'plans' && }
); } // ── Videos tab ─────────────────────────────────────────────────────────────── function VideosTab() { const [videos, setVideos] = useState([]); const [loading, setLoading] = useState(true); const [form, setForm] = useState({ title: '', storageType: 'youtube', storageKey: '', requiredLevel: 0, isPublished: false, }); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); useEffect(() => { apiFetch<{ success: boolean; data: { videos: Video[] } }>('/admin/videos') .then((r) => setVideos(r.data.videos)) .catch(() => {}) .finally(() => setLoading(false)); }, []); async function handleCreate(e: React.FormEvent) { e.preventDefault(); if (!form.title || !form.storageKey || saving) return; setSaving(true); setError(null); try { const r = await apiFetch<{ success: boolean; data: { video: Video } }>( '/admin/videos', { method: 'POST', body: JSON.stringify(form) } ); setVideos((v) => [r.data.video, ...v]); setForm({ title: '', storageType: 'youtube', storageKey: '', requiredLevel: 0, isPublished: false }); } catch { setError('Erreur lors de la création.'); } setSaving(false); } async function togglePublish(video: Video) { try { const r = await apiFetch<{ success: boolean; data: { video: Video } }>( `/admin/videos/${video.id}`, { method: 'PATCH', body: JSON.stringify({ isPublished: !video.isPublished }) } ); setVideos((v) => v.map((x) => x.id === video.id ? r.data.video : x)); } catch {} } async function handleDelete(id: string) { if (!confirm('Supprimer cette vidéo ?')) return; try { await apiFetch(`/admin/videos/${id}`, { method: 'DELETE' }); setVideos((v) => v.filter((x) => x.id !== id)); } catch {} } return (
{/* Formulaire création */}

Nouvelle vidéo

setForm((f) => ({ ...f, title: e.target.value }))} placeholder="Titre" required className="rounded border border-od-border bg-od-bg px-3 py-2 text-sm text-od-text placeholder-od-muted outline-none focus:border-od-accent" />
setForm((f) => ({ ...f, storageKey: e.target.value }))} placeholder={form.storageType === 'youtube' ? 'ID YouTube (ex: dQw4w9WgXcQ)' : 'Chemin / URL'} required className="flex-1 rounded border border-od-border bg-od-bg px-3 py-2 text-sm text-od-text placeholder-od-muted outline-none focus:border-od-accent" />
{error &&

{error}

}
{/* Liste */} {loading ? (
{[...Array(3)].map((_, i) =>
)}
) : (
{videos.map((v) => (

{v.title}

{v.storageType} · niveau {v.requiredLevel}

))} {videos.length === 0 &&

Aucune vidéo.

}
)}
); } // ── Users tab ──────────────────────────────────────────────────────────────── function UsersTab() { const [users, setUsers] = useState([]); const [plans, setPlans] = useState([]); const [loading, setLoading] = useState(true); const [assigning, setAssigning] = useState(null); const [selectedPlan, setSelectedPlan] = useState>({}); useEffect(() => { Promise.all([ apiFetch<{ success: boolean; data: { users: AdminUser[] } }>('/admin/users'), apiFetch<{ success: boolean; data: { plans: Plan[] } }>('/admin/plans'), ]) .then(([ur, pr]) => { setUsers(ur.data.users); setPlans(pr.data.plans); }) .catch(() => {}) .finally(() => setLoading(false)); }, []); async function assignPlan(userId: string) { const planId = selectedPlan[userId]; if (!planId || assigning) return; setAssigning(userId); try { await apiFetch(`/admin/users/${userId}/subscriptions`, { method: 'POST', body: JSON.stringify({ planId }), }); // Rafraîchir la liste users const r = await apiFetch<{ success: boolean; data: { users: AdminUser[] } }>('/admin/users'); setUsers(r.data.users); } catch {} setAssigning(null); } if (loading) return
; return (
{users.map((u) => (

{u.nickname}

{u.email ?? '—'}

{u.activeSubscription ? ( {u.activeSubscription.plan.name} {u.activeSubscription.endsAt && ` · ${new Date(u.activeSubscription.endsAt).toLocaleDateString()}`} ) : ( free )}
))} {users.length === 0 &&

Aucun utilisateur.

}
); } // ── Plans tab ───────────────────────────────────────────────────────────────── function PlansTab() { const [plans, setPlans] = useState([]); const [loading, setLoading] = useState(true); const [form, setForm] = useState({ slug: '', name: '', level: 1, priceInCents: 0 }); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); useEffect(() => { apiFetch<{ success: boolean; data: { plans: Plan[] } }>('/admin/plans') .then((r) => setPlans(r.data.plans)) .catch(() => {}) .finally(() => setLoading(false)); }, []); async function handleCreate(e: React.FormEvent) { e.preventDefault(); if (!form.slug || !form.name || saving) return; setSaving(true); setError(null); try { const r = await apiFetch<{ success: boolean; data: { plan: Plan } }>( '/admin/plans', { method: 'POST', body: JSON.stringify(form) } ); setPlans((p) => [...p, r.data.plan]); setForm({ slug: '', name: '', level: 1, priceInCents: 0 }); } catch { setError('Erreur lors de la création.'); } setSaving(false); } async function toggleActive(plan: Plan) { try { const r = await apiFetch<{ success: boolean; data: { plan: Plan } }>( `/admin/plans/${plan.id}`, { method: 'PATCH', body: JSON.stringify({ isActive: !plan.isActive }) } ); setPlans((p) => p.map((x) => x.id === plan.id ? r.data.plan : x)); } catch {} } if (loading) return
; return (

Nouveau plan

setForm((f) => ({ ...f, slug: e.target.value }))} placeholder="slug (ex: premium)" required className="flex-1 rounded border border-od-border bg-od-bg px-3 py-2 text-sm text-od-text placeholder-od-muted outline-none focus:border-od-accent" /> setForm((f) => ({ ...f, name: e.target.value }))} placeholder="Nom" required className="flex-1 rounded border border-od-border bg-od-bg px-3 py-2 text-sm text-od-text placeholder-od-muted outline-none focus:border-od-accent" />
{error &&

{error}

}
{plans.map((p) => (

{p.name}

{p.slug} · niv. {p.level} · {(p.priceInCents / 100).toFixed(2)} €

))} {plans.length === 0 &&

Aucun plan.

}
); }