// CRITICAL FIX: Import React FIRST to ensure it's available import { useState, useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; // TEMPORARY FIX: Comment out react-hook-form to isolate initialization error // import { useForm } from 'react-hook-form'; import { Dialog, DialogBody, DialogFooter } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Progress } from '@/components/ui/progress'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Alert } from '@/components/ui/alert'; import { Upload, X, FileAudio, AlertCircle, CheckCircle2, RefreshCw, } from 'lucide-react'; import { uploadTrack, type TrackMetadata, } from '@/features/tracks/api/trackApi'; import { useQueryClient } from '@tanstack/react-query'; import { LIBRARY_KEYS } from '@/features/library/hooks/useMyTracks'; import { logger } from '@/utils/logger'; import { useIsRateLimited } from '@/hooks/useIsRateLimited'; export interface UploadModalProps { open: boolean; onClose: () => void; } const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB const ACCEPTED_AUDIO_TYPES = { 'audio/mpeg': ['.mp3'], 'audio/wav': ['.wav'], 'audio/ogg': ['.ogg'], 'audio/flac': ['.flac'], 'audio/mp4': ['.m4a'], 'audio/aac': ['.aac'], }; type UploadFormData = { file: File | null; title: string; artist: string; album: string; genre: string; }; const MAX_RETRY_ATTEMPTS = 3; export function UploadModal({ open, onClose }: UploadModalProps) { const [file, setFile] = useState(null); const [uploadProgress, setUploadProgress] = useState(0); const [isUploading, setIsUploading] = useState(false); const [error, setError] = useState(null); const [errorCode, setErrorCode] = useState(null); const [isRetryable, setIsRetryable] = useState(false); const [retryCount, setRetryCount] = useState(0); const [success, setSuccess] = useState(false); const isRateLimited = useIsRateLimited(); const queryClient = useQueryClient(); // TEMPORARY FIX: Replace useForm with useState to isolate react-hook-form initialization error const [formData, setFormData] = useState({ file: null, title: '', artist: '', album: '', genre: '', }); const [formErrors, setFormErrors] = useState>>({}); // Mock functions to replace useForm methods const register = (name: keyof UploadFormData) => ({ value: formData[name], onChange: (e: React.ChangeEvent) => { setFormData(prev => ({ ...prev, [name]: e.target.value })); }, }); const setValue = (name: keyof UploadFormData, value: any) => { setFormData(prev => ({ ...prev, [name]: value })); }; const getValues = () => formData; const reset = () => { setFormData({ file: null, title: '', artist: '', album: '', genre: '' }); setFormErrors({}); }; const handleSubmit = (onValid: (data: UploadFormData) => void, onInvalid?: (errors: any) => void) => { return (e: React.FormEvent) => { e.preventDefault(); // Simple validation if (!formData.file) { setFormErrors({ file: 'Veuillez sélectionner un fichier' }); onInvalid?.({ file: { message: 'Veuillez sélectionner un fichier' } }); return; } setFormErrors({}); onValid(formData); }; }; const errors = formErrors; // ORIGINAL CODE (commented for debugging): // const { // register, // handleSubmit, // setValue, // getValues, // formState: { errors }, // reset, // } = useForm({ // defaultValues: { // file: null, // title: '', // artist: '', // album: '', // genre: '', // }, // }); const onDrop = useCallback( (acceptedFiles: File[]) => { const selectedFile = acceptedFiles[0]; if (selectedFile) { setFile(selectedFile); setError(null); setSuccess(false); // Mettre à jour le formulaire avec setValue pour que react-hook-form connaisse le fichier setValue('file', selectedFile, { shouldValidate: true }); // Pré-remplir le titre avec le nom du fichier (sans extension) const fileNameWithoutExt = selectedFile.name.replace(/\.[^/.]+$/, ''); // Lecture à la demande avec getValues, pas de re-render const currentTitle = getValues('title'); if (!currentTitle) { setValue('title', fileNameWithoutExt, { shouldValidate: true }); } } }, [setValue, getValues], ); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: ACCEPTED_AUDIO_TYPES, maxSize: MAX_FILE_SIZE, multiple: false, onError: (err) => { setError(`Erreur lors de la sélection du fichier: ${err.message}`); }, onDropRejected: (fileRejections) => { const rejection = fileRejections[0]; if (rejection.errors[0]?.code === 'file-too-large') { setError('Le fichier est trop volumineux (max 100 MB)'); } else if (rejection.errors[0]?.code === 'file-invalid-type') { setError( 'Format de fichier non supporté. Formats acceptés: MP3, WAV, OGG, FLAC, M4A, AAC', ); } else { setError( rejection.errors[0]?.message || 'Erreur lors de la sélection du fichier', ); } }, }); const performUpload = useCallback( async (data: UploadFormData, attemptNumber: number = 1) => { if (!data.file) { setError('Veuillez sélectionner un fichier'); setErrorCode(null); setIsRetryable(false); return; } setIsUploading(true); setError(null); setErrorCode(null); setIsRetryable(false); setSuccess(false); setUploadProgress(0); try { const trackMetadata: TrackMetadata = { title: data.title || data.file.name.replace(/\.[^/.]+$/, ''), artist: data.artist, album: data.album, genre: data.genre, is_public: false, }; await uploadTrack(data.file, trackMetadata, (progress) => { setUploadProgress(progress); }); setSuccess(true); setUploadProgress(100); setRetryCount(0); // Invalider les queries pour rafraîchir la liste queryClient.invalidateQueries({ queryKey: LIBRARY_KEYS.all }); queryClient.invalidateQueries({ queryKey: ['tracks'] }); // Fermer après 1.5 secondes setTimeout(() => { handleClose(); }, 1500); } catch (err) { let errorMessage = "Erreur lors de l'upload"; let errorCodeValue: string | null = null; let retryable = false; if (err instanceof Error) { errorMessage = err.message; // Détecter les erreurs réseau qui sont généralement retryables const isNetworkError = errorMessage.toLowerCase().includes('network') || errorMessage.toLowerCase().includes('réseau') || errorMessage.toLowerCase().includes('timeout') || errorMessage.toLowerCase().includes('econnaborted') || errorMessage.toLowerCase().includes('etimedout') || errorMessage.toLowerCase().includes('se connecter'); // Détecter les erreurs serveur qui peuvent être retryables const isServerError = errorMessage.toLowerCase().includes('serveur') || errorMessage.toLowerCase().includes('server') || errorMessage.toLowerCase().includes('500') || errorMessage.toLowerCase().includes('503') || errorMessage.toLowerCase().includes('502'); // Les erreurs de validation ne sont pas retryables const isValidationError = errorMessage.toLowerCase().includes('format') || errorMessage.toLowerCase().includes('taille') || errorMessage.toLowerCase().includes('invalide') || errorMessage.toLowerCase().includes('trop volumineux') || errorMessage.toLowerCase().includes('non supporté') || errorMessage.toLowerCase().includes('400') || errorMessage.toLowerCase().includes('413') || errorMessage.toLowerCase().includes('415'); if (isNetworkError) { errorCodeValue = 'NETWORK'; retryable = attemptNumber < MAX_RETRY_ATTEMPTS; } else if (isServerError) { errorCodeValue = 'SERVER'; retryable = attemptNumber < MAX_RETRY_ATTEMPTS; } else if (isValidationError) { errorCodeValue = 'VALIDATION'; retryable = false; } } setError(errorMessage); setErrorCode(errorCodeValue); setIsRetryable(retryable); setUploadProgress(0); setRetryCount(attemptNumber); } finally { setIsUploading(false); } }, [queryClient], ); const onSubmit = useCallback( async (data: UploadFormData) => { await performUpload(data, 1); }, [performUpload], ); const handleRetry = useCallback(() => { const formData = getValues(); const nextAttempt = retryCount + 1; performUpload(formData, nextAttempt); }, [retryCount, getValues, performUpload]); const handleClose = () => { if (!isUploading) { setFile(null); setUploadProgress(0); setError(null); setErrorCode(null); setIsRetryable(false); setRetryCount(0); setSuccess(false); reset(); onClose(); } }; const handleRemoveFile = () => { setFile(null); setError(null); setSuccess(false); setUploadProgress(0); setValue('file', null, { shouldValidate: true }); }; return (
{ logger.warn('Form validation errors:', { errors }); })} >
{/* Zone de Drag & Drop */} {!file ? (

{isDragActive ? 'Déposez le fichier ici' : 'Glissez-déposez un fichier audio'}

ou cliquez pour sélectionner

Formats acceptés: MP3, WAV, OGG, FLAC, M4A, AAC (max 100 MB)

) : (

{file.name}

{(file.size / 1024 / 1024).toFixed(2)} MB

{!isUploading && ( )}
)} {/* Progress Bar */} {isUploading && (
Upload en cours... {uploadProgress}%
)} {/* Messages d'erreur */} {error && (

{error}

{errorCode && (

Code d'erreur: {errorCode}

)} {retryCount > 0 && (

Tentative {retryCount}/{MAX_RETRY_ATTEMPTS}

)}
{isRetryable && ( )}
)} {/* Message de succès */} {success && ( Fichier uploadé avec succès ! )} {/* Formulaire de métadonnées */} {file && !isUploading && !success && (

Métadonnées (optionnel)

{errors.title && (

{errors.title.message}

)}
)}
{!success && ( )}
); }