feat: VideoPage — ajouter à une playlist (owned + edit-permitted)
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 23s
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 23s
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { apiFetch, ApiError } from '../lib/api';
|
||||
import { useAuthContext } from '../context/AuthContext';
|
||||
import VideoPlayer from '../components/VideoPlayer';
|
||||
|
||||
interface Video {
|
||||
@@ -20,8 +21,14 @@ interface VideoResponse {
|
||||
data: { video: Video };
|
||||
}
|
||||
|
||||
interface Playlist {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default function VideoPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { user } = useAuthContext();
|
||||
const [video, setVideo] = useState<Video | null>(null);
|
||||
const [error, setError] = useState<'forbidden' | 'not_found' | 'unknown' | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -106,6 +113,8 @@ export default function VideoPage() {
|
||||
</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">
|
||||
← Retour aux vidéos
|
||||
</Link>
|
||||
@@ -113,3 +122,70 @@ export default function VideoPage() {
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user