feat: enhance list management UI with create, edit, and delete functionality

- Add ListForm component for creating and editing lists with color picker
- Add Modal component for reusable dialog functionality
- Add DeleteListButton with confirmation dialog
- Add NewListButton for creating new lists
- Update ListCard to show edit/delete buttons on hover
- Update home page to use new list creation flow
- All components include proper error handling and loading states

Requirements: 2.1, 2.2, 2.4, 2.5
This commit is contained in:
2025-12-01 19:18:54 +00:00
parent 282a0295ec
commit c1afb2cb6e
7 changed files with 402 additions and 31 deletions

134
components/ListForm.tsx Normal file
View File

@@ -0,0 +1,134 @@
'use client'
import { useState } from 'react'
import { createList, updateList } from '@/lib/actions'
import { useRouter } from 'next/navigation'
interface ListFormProps {
list?: {
id: string
title: string
color: string
}
onCancel?: () => void
onSuccess?: () => void
}
const PRESET_COLORS = [
'#3B82F6', // blue
'#EF4444', // red
'#10B981', // green
'#F59E0B', // amber
'#8B5CF6', // purple
'#EC4899', // pink
'#06B6D4', // cyan
'#F97316', // orange
]
export default function ListForm({ list, onCancel, onSuccess }: ListFormProps) {
const router = useRouter()
const [title, setTitle] = useState(list?.title || '')
const [color, setColor] = useState(list?.color || PRESET_COLORS[0])
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!title.trim()) {
setError('List title is required')
return
}
setIsSubmitting(true)
setError('')
try {
if (list) {
// Update existing list
await updateList(list.id, { title: title.trim(), color })
} else {
// Create new list
await createList(title.trim(), color)
}
if (onSuccess) {
onSuccess()
} else {
router.refresh()
}
} catch (err) {
setError('Failed to save list. Please try again.')
console.error(err)
} finally {
setIsSubmitting(false)
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="title" className="block text-sm font-medium text-gray-700 mb-1">
List Title
</label>
<input
type="text"
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter list title"
disabled={isSubmitting}
autoFocus
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Color
</label>
<div className="flex gap-2 flex-wrap">
{PRESET_COLORS.map((presetColor) => (
<button
key={presetColor}
type="button"
onClick={() => setColor(presetColor)}
className={`w-10 h-10 rounded-lg transition-all ${
color === presetColor
? 'ring-2 ring-offset-2 ring-gray-400 scale-110'
: 'hover:scale-105'
}`}
style={{ backgroundColor: presetColor }}
disabled={isSubmitting}
aria-label={`Select color ${presetColor}`}
/>
))}
</div>
</div>
{error && (
<div className="text-red-600 text-sm">{error}</div>
)}
<div className="flex gap-2 justify-end pt-2">
{onCancel && (
<button
type="button"
onClick={onCancel}
disabled={isSubmitting}
className="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50"
>
Cancel
</button>
)}
<button
type="submit"
disabled={isSubmitting}
className="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition-colors disabled:opacity-50"
>
{isSubmitting ? 'Saving...' : list ? 'Update List' : 'Create List'}
</button>
</div>
</form>
)
}