project-standalo-sonic-cloud/components/UploadForm.tsx

215 lines
7.6 KiB
TypeScript

'use client'
import { useState } from 'react'
export interface UploadFormData {
title: string
audioFile: File | null
coverFile: File | null
albumId?: string
genreIds: string[]
releaseDate: string
}
export interface UploadFormProps {
onSubmit: (data: UploadFormData) => void | Promise<void>
albums?: Array<{ id: string; title: string }>
genres?: Array<{ id: string; name: string }>
isLoading?: boolean
}
export function UploadForm({ onSubmit, albums = [], genres = [], isLoading = false }: UploadFormProps) {
const [formData, setFormData] = useState<UploadFormData>({
title: '',
audioFile: null,
coverFile: null,
albumId: '',
genreIds: [],
releaseDate: new Date().toISOString().split('T')[0]
})
const [audioPreview, setAudioPreview] = useState<string | null>(null)
const [coverPreview, setCoverPreview] = useState<string | null>(null)
const handleAudioChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file) {
setFormData({ ...formData, audioFile: file })
setAudioPreview(URL.createObjectURL(file))
}
}
const handleCoverChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file) {
setFormData({ ...formData, coverFile: file })
setCoverPreview(URL.createObjectURL(file))
}
}
const handleGenreToggle = (genreId: string) => {
setFormData(prev => ({
...prev,
genreIds: prev.genreIds.includes(genreId)
? prev.genreIds.filter(id => id !== genreId)
: [...prev.genreIds, genreId]
}))
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!formData.audioFile || !formData.title) return
onSubmit(formData)
}
return (
<form onSubmit={handleSubmit} className="space-y-6">
{/* Audio File Upload */}
<div>
<label className="block text-sm font-medium text-zinc-300 mb-2">
Audio File *
</label>
<div className="border-2 border-dashed border-zinc-700 rounded-lg p-8 hover:border-purple-500 transition">
<input
type="file"
accept="audio/*"
onChange={handleAudioChange}
className="hidden"
id="audio-upload"
required
/>
<label
htmlFor="audio-upload"
className="flex flex-col items-center cursor-pointer"
>
<svg className="w-12 h-12 text-zinc-500 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3" />
</svg>
<span className="text-zinc-400">
{formData.audioFile ? formData.audioFile.name : 'Click to upload audio file'}
</span>
<span className="text-xs text-zinc-500 mt-1">MP3, WAV, FLAC up to 100MB</span>
</label>
</div>
{audioPreview && (
<audio src={audioPreview} controls className="w-full mt-3" />
)}
</div>
{/* Cover Image Upload */}
<div>
<label className="block text-sm font-medium text-zinc-300 mb-2">
Cover Image
</label>
<div className="flex gap-4">
{coverPreview && (
<div className="w-32 h-32 rounded-lg overflow-hidden bg-zinc-800">
<img src={coverPreview} alt="Cover preview" className="w-full h-full object-cover" />
</div>
)}
<div className="flex-1 border-2 border-dashed border-zinc-700 rounded-lg p-6 hover:border-purple-500 transition">
<input
type="file"
accept="image/*"
onChange={handleCoverChange}
className="hidden"
id="cover-upload"
/>
<label htmlFor="cover-upload" className="flex flex-col items-center cursor-pointer">
<svg className="w-10 h-10 text-zinc-500 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span className="text-sm text-zinc-400">
{formData.coverFile ? formData.coverFile.name : 'Upload cover image'}
</span>
</label>
</div>
</div>
</div>
{/* Title */}
<div>
<label htmlFor="title" className="block text-sm font-medium text-zinc-300 mb-2">
Song Title *
</label>
<input
type="text"
id="title"
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-purple-500"
placeholder="Enter song title"
required
/>
</div>
{/* Album Selection */}
{albums.length > 0 && (
<div>
<label htmlFor="album" className="block text-sm font-medium text-zinc-300 mb-2">
Album (Optional)
</label>
<select
id="album"
value={formData.albumId}
onChange={(e) => setFormData({ ...formData, albumId: e.target.value })}
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">No album</option>
{albums.map(album => (
<option key={album.id} value={album.id}>{album.title}</option>
))}
</select>
</div>
)}
{/* Genre Selection */}
{genres.length > 0 && (
<div>
<label className="block text-sm font-medium text-zinc-300 mb-2">
Genres
</label>
<div className="flex flex-wrap gap-2">
{genres.map(genre => (
<button
key={genre.id}
type="button"
onClick={() => handleGenreToggle(genre.id)}
className={`px-4 py-2 rounded-full text-sm transition ${
formData.genreIds.includes(genre.id)
? 'bg-purple-500 text-white'
: 'bg-zinc-800 text-zinc-300 hover:bg-zinc-700'
}`}
>
{genre.name}
</button>
))}
</div>
</div>
)}
{/* Release Date */}
<div>
<label htmlFor="releaseDate" className="block text-sm font-medium text-zinc-300 mb-2">
Release Date
</label>
<input
type="date"
id="releaseDate"
value={formData.releaseDate}
onChange={(e) => setFormData({ ...formData, releaseDate: e.target.value })}
className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
</div>
{/* Submit Button */}
<button
type="submit"
disabled={isLoading || !formData.audioFile || !formData.title}
className="w-full py-3 bg-purple-500 hover:bg-purple-600 disabled:bg-zinc-700 disabled:text-zinc-500 text-white font-medium rounded-lg transition"
>
{isLoading ? 'Uploading...' : 'Upload Song'}
</button>
</form>
)
}