feat: VideoPage — ajouter à une playlist (owned + edit-permitted)
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 23s

This commit is contained in:
2026-03-15 02:53:34 +01:00
parent 8e78ce50b5
commit 3eb791d4a1

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom'; import { useParams, Link } from 'react-router-dom';
import { apiFetch, ApiError } from '../lib/api'; import { apiFetch, ApiError } from '../lib/api';
import { useAuthContext } from '../context/AuthContext';
import VideoPlayer from '../components/VideoPlayer'; import VideoPlayer from '../components/VideoPlayer';
interface Video { interface Video {
@@ -20,8 +21,14 @@ interface VideoResponse {
data: { video: Video }; data: { video: Video };
} }
interface Playlist {
id: string;
title: string;
}
export default function VideoPage() { export default function VideoPage() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const { user } = useAuthContext();
const [video, setVideo] = useState<Video | null>(null); const [video, setVideo] = useState<Video | null>(null);
const [error, setError] = useState<'forbidden' | 'not_found' | 'unknown' | null>(null); const [error, setError] = useState<'forbidden' | 'not_found' | 'unknown' | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -106,6 +113,8 @@ export default function VideoPage() {
</div> </div>
</div> </div>
{user && <AddToPlaylist videoId={video.id} />}
<Link to="/" className="self-start font-mono text-xs text-od-muted hover:text-od-text transition-colors"> <Link to="/" className="self-start font-mono text-xs text-od-muted hover:text-od-text transition-colors">
Retour aux vidéos Retour aux vidéos
</Link> </Link>
@@ -113,3 +122,70 @@ export default function VideoPage() {
</div> </div>
); );
} }
// ── Ajouter à une playlist ────────────────────────────────────────────────────
function AddToPlaylist({ videoId }: { videoId: string }) {
const [playlists, setPlaylists] = useState<Playlist[]>([]);
const [selected, setSelected] = useState('');
const [adding, setAdding] = useState(false);
const [status, setStatus] = useState<'idle' | 'ok' | 'already' | 'error'>('idle');
useEffect(() => {
apiFetch<{ success: boolean; data: { owned: Playlist[]; shared: (Playlist & { permission: string })[] } }>(
'/playlists'
).then((res) => {
const editable = [
...res.data.owned,
...res.data.shared.filter((p) => p.permission === 'edit'),
];
setPlaylists(editable);
if (editable.length > 0) setSelected(editable[0].id);
}).catch(() => {});
}, []);
async function handleAdd() {
if (!selected || adding) return;
setAdding(true);
setStatus('idle');
try {
await apiFetch(`/playlists/${selected}/videos`, {
method: 'POST',
body: JSON.stringify({ videoId }),
});
setStatus('ok');
} catch (e) {
setStatus(e instanceof ApiError && e.status === 409 ? 'already' : 'error');
}
setAdding(false);
}
if (playlists.length === 0) return null;
return (
<div className="flex flex-col gap-2 rounded border border-od-border bg-od-surface p-4">
<p className="font-mono text-xs text-od-muted uppercase tracking-widest">Ajouter à une playlist</p>
<div className="flex items-center gap-2">
<select
value={selected}
onChange={(e) => { setSelected(e.target.value); setStatus('idle'); }}
className="flex-1 rounded border border-od-border bg-od-bg px-3 py-2 text-sm text-od-text outline-none focus:border-od-accent"
>
{playlists.map((p) => (
<option key={p.id} value={p.id}>{p.title}</option>
))}
</select>
<button
onClick={handleAdd}
disabled={adding}
className="rounded border border-od-accent px-4 py-2 font-mono text-xs text-od-accent hover:bg-od-accent hover:text-od-bg transition-colors disabled:opacity-40"
>
{adding ? '…' : '+ Ajouter'}
</button>
</div>
{status === 'ok' && <p className="font-mono text-xs text-od-ok">Vidéo ajoutée.</p>}
{status === 'already'&& <p className="font-mono text-xs text-od-muted">Déjà dans cette playlist.</p>}
{status === 'error' && <p className="font-mono text-xs text-od-crit">Erreur réessaie.</p>}
</div>
);
}