215 lines
7.6 KiB
TypeScript
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>
|
|
)
|
|
}
|