diff --git a/src/pages/Upload.tsx b/src/pages/Upload.tsx index a21a822..641a229 100644 --- a/src/pages/Upload.tsx +++ b/src/pages/Upload.tsx @@ -1,28 +1,124 @@ import { useState, useRef, useCallback } from 'react' import { useNavigate } from 'react-router-dom' -import { Upload as UploadIcon, Music, X, Image } from 'lucide-react' +import { Upload as UploadIcon, Music, X, Image, Mic, Link2, Loader2, Sparkles } from 'lucide-react' import { supabase } from '@/lib/supabase' import { useAuthStore } from '@/stores/auth' import { Button } from '@/components/ui/Button' import { Input } from '@/components/ui/Input' +type Mode = 'choose' | 'original' | 'external' + +interface OEmbedData { + title: string + description: string + thumbnail: string + duration: number + audioUrl: string + platform: string +} + +function extractVideoId(url: string): { platform: string; id: string } | null { + // YouTube + const ytMatch = url.match( + /(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/, + ) + if (ytMatch) return { platform: 'youtube', id: ytMatch[1] } + + // Dailymotion + const dmMatch = url.match( + /(?:dailymotion\.com\/video\/|dai\.ly\/)([a-zA-Z0-9]+)/, + ) + if (dmMatch) return { platform: 'dailymotion', id: dmMatch[1] } + + // SoundCloud (handled via oEmbed, no ID extraction needed) + if (url.includes('soundcloud.com/')) return { platform: 'soundcloud', id: url } + + return null +} + +async function fetchVideoMeta(url: string): Promise { + const info = extractVideoId(url) + if (!info) return null + + try { + if (info.platform === 'youtube') { + const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${info.id}&format=json` + const res = await fetch(oembedUrl) + if (!res.ok) return null + const data = await res.json() + return { + title: data.title || '', + description: '', + thumbnail: `https://img.youtube.com/vi/${info.id}/hqdefault.jpg`, + duration: 0, + audioUrl: url, + platform: 'YouTube', + } + } + + if (info.platform === 'dailymotion') { + const oembedUrl = `https://www.dailymotion.com/services/oembed?url=https://www.dailymotion.com/video/${info.id}&format=json` + const res = await fetch(oembedUrl) + if (!res.ok) return null + const data = await res.json() + return { + title: data.title || '', + description: '', + thumbnail: data.thumbnail_url || `https://www.dailymotion.com/thumbnail/video/${info.id}`, + duration: 0, + audioUrl: url, + platform: 'Dailymotion', + } + } + + if (info.platform === 'soundcloud') { + const oembedUrl = `https://soundcloud.com/oembed?url=${encodeURIComponent(url)}&format=json` + const res = await fetch(oembedUrl) + if (!res.ok) return null + const data = await res.json() + return { + title: data.title || '', + description: data.description || '', + thumbnail: data.thumbnail_url || '', + duration: 0, + audioUrl: url, + platform: 'SoundCloud', + } + } + } catch { + return null + } + + return null +} + export function Upload() { const { user } = useAuthStore() const navigate = useNavigate() + const [mode, setMode] = useState('choose') + + // Shared fields const [title, setTitle] = useState('') const [description, setDescription] = useState('') - const [audioFile, setAudioFile] = useState(null) - const [coverFile, setCoverFile] = useState(null) const [coverPreview, setCoverPreview] = useState(null) - const [duration, setDuration] = useState(0) + const [coverFile, setCoverFile] = useState(null) const [uploading, setUploading] = useState(false) const [error, setError] = useState('') + + // Original mode + const [audioFile, setAudioFile] = useState(null) + const [duration, setDuration] = useState(0) const [dragActive, setDragActive] = useState(false) const audioInputRef = useRef(null) + // External mode + const [externalUrl, setExternalUrl] = useState('') + const [fetching, setFetching] = useState(false) + const [platform, setPlatform] = useState('') + const handleAudioSelect = useCallback((file: File) => { if (!file.type.startsWith('audio/')) { - setError('Veuillez sélectionner un fichier audio.') + setError('Veuillez selectionner un fichier audio.') return } setAudioFile(file) @@ -44,28 +140,56 @@ export function Upload() { setCoverPreview(URL.createObjectURL(file)) } - async function handleSubmit(e: React.FormEvent) { - e.preventDefault() - if (!user || !audioFile) return - setUploading(true) + async function handleFetchUrl() { + if (!externalUrl.trim()) return + setFetching(true) setError('') - const timestamp = Date.now() - const audioPath = `${user.id}/${timestamp}-${audioFile.name}` - - const { error: audioErr } = await supabase.storage - .from('podcasts') - .upload(audioPath, audioFile) - - if (audioErr) { - setError('Erreur lors de l\'upload audio: ' + audioErr.message) - setUploading(false) + const meta = await fetchVideoMeta(externalUrl.trim()) + if (!meta) { + setError('URL non reconnue. Formats supportes : YouTube, Dailymotion, SoundCloud.') + setFetching(false) return } - const { data: audioUrl } = supabase.storage.from('podcasts').getPublicUrl(audioPath) + if (meta.title && !title) setTitle(meta.title) + if (meta.description && !description) setDescription(meta.description) + if (meta.thumbnail) setCoverPreview(meta.thumbnail) + if (meta.duration) setDuration(meta.duration) + setPlatform(meta.platform) + setFetching(false) + } - let cover_url: string | null = null + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + if (!user) return + setUploading(true) + setError('') + + let audio_url = '' + const timestamp = Date.now() + + if (mode === 'original') { + if (!audioFile) return + + const audioPath = `${user.id}/${timestamp}-${audioFile.name}` + const { error: audioErr } = await supabase.storage + .from('podcasts') + .upload(audioPath, audioFile) + + if (audioErr) { + setError("Erreur lors de l'upload audio: " + audioErr.message) + setUploading(false) + return + } + + const { data: audioUrlData } = supabase.storage.from('podcasts').getPublicUrl(audioPath) + audio_url = audioUrlData.publicUrl + } else { + audio_url = externalUrl.trim() + } + + let cover_url: string | null = coverPreview if (coverFile) { const coverPath = `${user.id}/${timestamp}-cover-${coverFile.name}` const { error: coverErr } = await supabase.storage @@ -83,7 +207,7 @@ export function Upload() { creator_id: user.id, title, description, - audio_url: audioUrl.publicUrl, + audio_url, duration_seconds: duration, cover_url, }) @@ -108,69 +232,195 @@ export function Upload() { ) } + if (mode === 'choose') { + return ( +
+

Publier un podcast

+

Choisissez comment vous souhaitez partager votre contenu

+ +
+ {/* Original content card */} + + + {/* External content card */} + +
+
+ ) + } + return (
-

Publier un podcast

+
+ +

+ {mode === 'original' ? 'Uploader un fichier audio' : 'Partager un contenu existant'} +

+
- {!audioFile ? ( -
audioInputRef.current?.click()} - onDragOver={(e) => { e.preventDefault(); setDragActive(true) }} - onDragLeave={() => setDragActive(false)} - onDrop={(e) => { - e.preventDefault() - setDragActive(false) - const file = e.dataTransfer.files[0] - if (file) handleAudioSelect(file) - }} - > - -

Glissez votre fichier audio ici

-

ou cliquez pour sélectionner (MP3, WAV, M4A)

- e.target.files?.[0] && handleAudioSelect(e.target.files[0])} - /> -
- ) : ( -
- -
-

{audioFile.name}

-

- {(audioFile.size / 1024 / 1024).toFixed(1)} Mo - {duration > 0 && ` — ${Math.floor(duration / 60)}min ${duration % 60}s`} -

+ {mode === 'original' ? ( + /* ---- ORIGINAL: Audio file picker ---- */ + !audioFile ? ( +
audioInputRef.current?.click()} + onDragOver={(e) => { e.preventDefault(); setDragActive(true) }} + onDragLeave={() => setDragActive(false)} + onDrop={(e) => { + e.preventDefault() + setDragActive(false) + const file = e.dataTransfer.files[0] + if (file) handleAudioSelect(file) + }} + > + +

Glissez votre fichier audio ici

+

ou cliquez pour selectionner (MP3, WAV, M4A)

+ e.target.files?.[0] && handleAudioSelect(e.target.files[0])} + />
- + ) : ( +
+ +
+

{audioFile.name}

+

+ {(audioFile.size / 1024 / 1024).toFixed(1)} Mo + {duration > 0 && ` — ${Math.floor(duration / 60)}min ${duration % 60}s`} +

+
+ +
+ ) + ) : ( + /* ---- EXTERNAL: URL input ---- */ +
+
+
+ setExternalUrl(e.target.value)} + placeholder="https://youtube.com/watch?v=... ou https://dai.ly/..." + /> +
+
+ +
+
+ + {platform && coverPreview && ( +
+ +
+

{title || 'Sans titre'}

+

via {platform}

+
+ + Auto-detecte + +
+ )}
)} + {/* Shared fields */}
setTitle(e.target.value)} required placeholder="Le titre de votre podcast" />
- +