feat: login provider selection, logout, playlists pages
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 22s

- LoginPage : sélection Discord/GitHub/Google/Twitch via SuperOAuth
- Header : bouton Connexion → /login, logout ↩ quand connecté, nav Playlists conditionnelle
- useAuth : expose setUser pour logout côté Layout
- PlaylistsPage : liste owned/shared, création inline
- PlaylistPage : détail playlist + liste vidéos ordonnées
- Fix : Video.id number → string (UUID)
- Routes : /login, /playlists, /playlists/:id
This commit is contained in:
2026-03-14 09:32:45 +01:00
parent fcd9867670
commit 4265d21c8b
8 changed files with 331 additions and 28 deletions

View File

@@ -1,19 +1,19 @@
import { Link } from 'react-router-dom';
import { apiFetch } from '../../lib/api';
import type { User } from '../../hooks/useAuth';
interface HeaderProps {
theme: 'dark' | 'light';
onToggleTheme: () => void;
user: User | null;
onLogout: () => void;
}
export default function Header({ theme, onToggleTheme, user }: HeaderProps) {
const callbackUrl = encodeURIComponent(
`${window.location.origin}/callback`
);
// Redirige vers SuperOAuth — l'utilisateur choisit son provider (Discord, GitHub, Google, Twitch)
// SuperOAuth redirige ensuite vers /callback?token=JWT&refresh=...
const loginUrl = `${import.meta.env.VITE_SUPEROAUTH_URL}/api/v1/auth/oauth/discord?redirectUrl=${callbackUrl}`;
export default function Header({ theme, onToggleTheme, user, onLogout }: HeaderProps) {
async function handleLogout() {
await apiFetch('/auth/logout', { method: 'POST' }).catch(() => {});
onLogout();
}
return (
<header className="border-b border-od-border bg-od-surface">
@@ -31,18 +31,14 @@ export default function Header({ theme, onToggleTheme, user }: HeaderProps) {
{/* Navigation */}
<nav className="flex gap-6">
<Link
to="/"
className="text-sm text-od-muted hover:text-od-text transition-colors"
>
<Link to="/" className="text-sm text-od-muted hover:text-od-text transition-colors">
Accueil
</Link>
<Link
to="/videos"
className="text-sm text-od-muted hover:text-od-text transition-colors"
>
Vidéos
</Link>
{user && (
<Link to="/playlists" className="text-sm text-od-muted hover:text-od-text transition-colors">
Playlists
</Link>
)}
</nav>
{/* Right — thème + auth */}
@@ -56,16 +52,22 @@ export default function Header({ theme, onToggleTheme, user }: HeaderProps) {
</button>
{user ? (
<span className="font-mono text-xs text-od-accent">
{user.nickname}
</span>
<div className="flex items-center gap-3">
<span className="font-mono text-xs text-od-accent">{user.nickname}</span>
<button
onClick={handleLogout}
className="font-mono text-xs text-od-muted hover:text-od-crit transition-colors"
>
</button>
</div>
) : (
<a
href={loginUrl}
<Link
to="/login"
className="rounded border border-od-border px-3 py-1 font-mono text-xs text-od-muted hover:border-od-accent hover:text-od-accent transition-colors"
>
Connexion
</a>
</Link>
)}
</div>

View File

@@ -8,7 +8,7 @@ interface LayoutProps {
}
export default function Layout({ theme, onToggleTheme }: LayoutProps) {
const { user, loading } = useAuth();
const { user, loading, setUser } = useAuth();
return (
<div className="min-h-screen bg-od-bg text-od-text">
@@ -16,6 +16,7 @@ export default function Layout({ theme, onToggleTheme }: LayoutProps) {
theme={theme}
onToggleTheme={onToggleTheme}
user={loading ? null : user}
onLogout={() => setUser(null)}
/>
<main className="mx-auto max-w-5xl px-4 py-8">
<Outlet />