173 lines
6.4 KiB
TypeScript
173 lines
6.4 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
|
|
export interface CreatePlaylistData {
|
|
title: string
|
|
description?: string
|
|
isPublic: boolean
|
|
}
|
|
|
|
export interface CreatePlaylistModalProps {
|
|
isOpen: boolean
|
|
onClose: () => void
|
|
onSubmit: (data: CreatePlaylistData) => void | Promise<void>
|
|
isLoading?: boolean
|
|
}
|
|
|
|
export function CreatePlaylistModal({
|
|
isOpen,
|
|
onClose,
|
|
onSubmit,
|
|
isLoading = false
|
|
}: CreatePlaylistModalProps) {
|
|
const [formData, setFormData] = useState<CreatePlaylistData>({
|
|
title: '',
|
|
description: '',
|
|
isPublic: true
|
|
})
|
|
|
|
if (!isOpen) return null
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
if (formData.title.trim()) {
|
|
onSubmit(formData)
|
|
}
|
|
}
|
|
|
|
const handleClose = () => {
|
|
if (!isLoading) {
|
|
setFormData({ title: '', description: '', isPublic: true })
|
|
onClose()
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
|
{/* Backdrop */}
|
|
<div
|
|
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
|
onClick={handleClose}
|
|
/>
|
|
|
|
{/* Modal */}
|
|
<div className="relative bg-zinc-900 rounded-2xl shadow-2xl border border-zinc-800 w-full max-w-md overflow-hidden">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between p-6 border-b border-zinc-800">
|
|
<h2 className="text-2xl font-bold text-white">Create Playlist</h2>
|
|
<button
|
|
onClick={handleClose}
|
|
disabled={isLoading}
|
|
className="p-2 text-zinc-400 hover:text-white transition disabled:opacity-50"
|
|
>
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Form */}
|
|
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
|
{/* Title */}
|
|
<div>
|
|
<label htmlFor="title" className="block text-sm font-medium text-zinc-300 mb-2">
|
|
Playlist Name *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="title"
|
|
value={formData.title}
|
|
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
|
placeholder="My awesome playlist"
|
|
className="w-full px-4 py-3 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-purple-500"
|
|
required
|
|
autoFocus
|
|
/>
|
|
</div>
|
|
|
|
{/* Description */}
|
|
<div>
|
|
<label htmlFor="description" className="block text-sm font-medium text-zinc-300 mb-2">
|
|
Description (Optional)
|
|
</label>
|
|
<textarea
|
|
id="description"
|
|
value={formData.description}
|
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
|
placeholder="Add a description for your playlist"
|
|
rows={3}
|
|
className="w-full px-4 py-3 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-purple-500 resize-none"
|
|
/>
|
|
</div>
|
|
|
|
{/* Privacy */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-zinc-300 mb-3">
|
|
Privacy
|
|
</label>
|
|
<div className="space-y-3">
|
|
<label className="flex items-center gap-3 p-4 bg-zinc-800 rounded-lg cursor-pointer hover:bg-zinc-750 transition">
|
|
<input
|
|
type="radio"
|
|
name="privacy"
|
|
checked={formData.isPublic}
|
|
onChange={() => setFormData({ ...formData, isPublic: true })}
|
|
className="w-5 h-5 text-purple-500 focus:ring-purple-500"
|
|
/>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<svg className="w-5 h-5 text-zinc-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M10 2a8 8 0 100 16 8 8 0 000-16zM8 9a1 1 0 000 2h4a1 1 0 100-2H8z" />
|
|
</svg>
|
|
<span className="font-medium text-white">Public</span>
|
|
</div>
|
|
<p className="text-sm text-zinc-400">Anyone can see and listen to this playlist</p>
|
|
</div>
|
|
</label>
|
|
|
|
<label className="flex items-center gap-3 p-4 bg-zinc-800 rounded-lg cursor-pointer hover:bg-zinc-750 transition">
|
|
<input
|
|
type="radio"
|
|
name="privacy"
|
|
checked={!formData.isPublic}
|
|
onChange={() => setFormData({ ...formData, isPublic: false })}
|
|
className="w-5 h-5 text-purple-500 focus:ring-purple-500"
|
|
/>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<svg className="w-5 h-5 text-zinc-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" />
|
|
</svg>
|
|
<span className="font-medium text-white">Private</span>
|
|
</div>
|
|
<p className="text-sm text-zinc-400">Only you can see and listen to this playlist</p>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Buttons */}
|
|
<div className="flex gap-3 pt-2">
|
|
<button
|
|
type="button"
|
|
onClick={handleClose}
|
|
disabled={isLoading}
|
|
className="flex-1 px-6 py-3 bg-zinc-800 hover:bg-zinc-700 disabled:bg-zinc-800 disabled:text-zinc-500 text-white font-medium rounded-lg transition"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={isLoading || !formData.title.trim()}
|
|
className="flex-1 px-6 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 ? 'Creating...' : 'Create'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|