|
2 | 2 | "use client"; |
3 | 3 |
|
4 | 4 | import { useState, useEffect } from "react"; |
5 | | -import { Plus, Pencil, Trash } from "lucide-react"; |
| 5 | +import { Plus, Pencil, Trash, Calendar, MapPin, Clock } from "lucide-react"; |
6 | 6 | import { useRouter } from "next/navigation"; |
| 7 | +import Image from "next/image"; |
7 | 8 |
|
8 | | -export default function AdminEventsPage() { |
9 | | - interface Event { |
10 | | - id: string; |
11 | | - title: string; |
12 | | - venue: string; |
13 | | - date: string; |
14 | | - } |
| 9 | +interface Event { |
| 10 | + id: string; |
| 11 | + title: string; |
| 12 | + description: string; |
| 13 | + venue: string; |
| 14 | + date: string; |
| 15 | + time: string; |
| 16 | + imageData?: string | null; |
| 17 | + imageMimeType?: string | null; |
| 18 | +} |
15 | 19 |
|
| 20 | +export default function AdminEventsPage() { |
16 | 21 | const [events, setEvents] = useState<Event[]>([]); |
| 22 | + const [isLoading, setIsLoading] = useState(true); |
17 | 23 | const router = useRouter(); |
18 | 24 |
|
19 | 25 | useEffect(() => { |
20 | | - fetch("/api/events").then(res => res.json()).then(setEvents); |
| 26 | + const fetchEvents = async () => { |
| 27 | + try { |
| 28 | + const res = await fetch("/api/events"); |
| 29 | + const data = await res.json(); |
| 30 | + setEvents(data); |
| 31 | + } catch (error) { |
| 32 | + console.error("Error fetching events:", error); |
| 33 | + } finally { |
| 34 | + setIsLoading(false); |
| 35 | + } |
| 36 | + }; |
| 37 | + |
| 38 | + fetchEvents(); |
21 | 39 | }, []); |
22 | 40 |
|
23 | 41 | async function deleteEvent(id: string) { |
24 | | - await fetch(`/api/events/${id}`, { method: "DELETE" }); |
25 | | - setEvents(events.filter(e => e.id !== id)); |
| 42 | + if (!confirm("Are you sure you want to delete this event?")) return; |
| 43 | + |
| 44 | + try { |
| 45 | + await fetch(`/api/events/${id}`, { method: "DELETE" }); |
| 46 | + setEvents(events.filter(e => e.id !== id)); |
| 47 | + } catch (error) { |
| 48 | + console.error("Error deleting event:", error); |
| 49 | + alert("Failed to delete event. Please try again."); |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + if (isLoading) { |
| 54 | + return ( |
| 55 | + <div className="flex justify-center items-center min-h-screen"> |
| 56 | + <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div> |
| 57 | + </div> |
| 58 | + ); |
26 | 59 | } |
27 | 60 |
|
28 | 61 | return ( |
29 | | - <div className="p-6"> |
30 | | - <div className="flex justify-between items-center mb-6"> |
31 | | - <h1 className="text-3xl font-bold">Manage Events</h1> |
| 62 | + <div className="container mx-auto px-4 py-8"> |
| 63 | + <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4"> |
| 64 | + <div> |
| 65 | + <h1 className="text-3xl font-bold text-gray-900">Events</h1> |
| 66 | + <p className="text-gray-600 mt-1">Manage your upcoming events</p> |
| 67 | + </div> |
32 | 68 | <button |
33 | 69 | onClick={() => router.push("/events/new")} |
34 | | - className="flex items-center gap-2 bg-black text-white px-4 py-2 rounded-lg" |
| 70 | + className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors" |
35 | 71 | > |
36 | | - <Plus className="w-4 h-4" /> Add Event |
| 72 | + <Plus className="w-5 h-5" /> |
| 73 | + <span>Add New Event</span> |
37 | 74 | </button> |
38 | 75 | </div> |
39 | | - |
40 | | - <table className="w-full border text-left"> |
41 | | - <thead> |
42 | | - <tr className="bg-gray-100"> |
43 | | - <th className="p-2">Title</th> |
44 | | - <th className="p-2">Venue</th> |
45 | | - <th className="p-2">Date</th> |
46 | | - <th className="p-2 w-32">Actions</th> |
47 | | - </tr> |
48 | | - </thead> |
49 | | - <tbody> |
50 | | - {events.map((e: Event) => ( |
51 | | - <tr |
52 | | - key={e.id} |
53 | | - className="border-t cursor-pointer hover:bg-gray-50" |
54 | | - onClick={() => router.push(`/events/${e.id}`)} |
55 | | - > |
56 | | - <td className="p-2">{e.title}</td> |
57 | | - <td className="p-2">{e.venue}</td> |
58 | | - <td className="p-2">{new Date(e.date).toLocaleDateString()}</td> |
59 | | - <td className="p-2 flex gap-2" onClick={(ev) => ev.stopPropagation()}> |
60 | | - <button |
61 | | - onClick={() => router.push(`/events/${e.id}/edit`)} |
62 | | - className="p-1 bg-yellow-300 rounded" |
63 | | - > |
64 | | - <Pencil className="w-4 h-4" /> |
65 | | - </button> |
66 | | - <button |
67 | | - onClick={() => deleteEvent(e.id)} |
68 | | - className="p-1 bg-red-500 text-white rounded" |
69 | | - > |
70 | | - <Trash className="w-4 h-4" /> |
71 | | - </button> |
72 | | - </td> |
73 | | - </tr> |
74 | | - ))} |
75 | | -</tbody> |
76 | 76 |
|
77 | | - </table> |
| 77 | + {events.length === 0 ? ( |
| 78 | + <div className="text-center py-12"> |
| 79 | + <div className="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4"> |
| 80 | + <Calendar className="w-8 h-8 text-gray-400" /> |
| 81 | + </div> |
| 82 | + <h3 className="text-lg font-medium text-gray-900">No events yet</h3> |
| 83 | + <p className="mt-1 text-gray-500">Get started by creating a new event.</p> |
| 84 | + </div> |
| 85 | + ) : ( |
| 86 | + <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> |
| 87 | + {events.map((event) => ( |
| 88 | + <div key={event.id} className="bg-white rounded-xl shadow-md overflow-hidden border border-gray-100 hover:shadow-lg transition-shadow"> |
| 89 | + {event.imageData && ( |
| 90 | + <div className="relative h-48 w-full"> |
| 91 | + <Image |
| 92 | + src={event.imageData} |
| 93 | + alt={event.title} |
| 94 | + fill |
| 95 | + className="object-cover" |
| 96 | + /> |
| 97 | + </div> |
| 98 | + )} |
| 99 | + <div className="p-5"> |
| 100 | + <h3 className="text-xl font-bold text-gray-900 mb-2 line-clamp-1">{event.title}</h3> |
| 101 | + |
| 102 | + <div className="flex items-center text-gray-600 text-sm mb-3"> |
| 103 | + <MapPin className="w-4 h-4 mr-1.5 flex-shrink-0" /> |
| 104 | + <span className="truncate">{event.venue}</span> |
| 105 | + </div> |
| 106 | + |
| 107 | + <div className="flex items-center text-gray-600 text-sm mb-4"> |
| 108 | + <Calendar className="w-4 h-4 mr-1.5 flex-shrink-0" /> |
| 109 | + <span> |
| 110 | + {new Date(event.date).toLocaleDateString('en-US', { |
| 111 | + year: 'numeric', |
| 112 | + month: 'short', |
| 113 | + day: 'numeric' |
| 114 | + })} |
| 115 | + {event.time && ( |
| 116 | + <> |
| 117 | + <span className="mx-1">•</span> |
| 118 | + <Clock className="w-4 h-4 inline-block mr-1" /> |
| 119 | + {event.time} |
| 120 | + </> |
| 121 | + )} |
| 122 | + </span> |
| 123 | + </div> |
| 124 | + |
| 125 | + {event.description && ( |
| 126 | + <p className="text-gray-600 text-sm mb-4 line-clamp-2"> |
| 127 | + {event.description} |
| 128 | + </p> |
| 129 | + )} |
| 130 | + |
| 131 | + <div className="flex justify-between items-center pt-3 border-t border-gray-100"> |
| 132 | + <button |
| 133 | + onClick={() => router.push(`/events/${event.id}`)} |
| 134 | + className="text-sm text-blue-600 hover:text-blue-800 font-medium" |
| 135 | + > |
| 136 | + View Details |
| 137 | + </button> |
| 138 | + <div className="flex space-x-2"> |
| 139 | + <button |
| 140 | + onClick={() => router.push(`/events/${event.id}/edit`)} |
| 141 | + className="p-1.5 text-gray-500 hover:text-blue-600 rounded-full hover:bg-blue-50" |
| 142 | + title="Edit" |
| 143 | + > |
| 144 | + <Pencil className="w-4 h-4" /> |
| 145 | + </button> |
| 146 | + <button |
| 147 | + onClick={() => deleteEvent(event.id)} |
| 148 | + className="p-1.5 text-gray-500 hover:text-red-600 rounded-full hover:bg-red-50" |
| 149 | + title="Delete" |
| 150 | + > |
| 151 | + <Trash className="w-4 h-4" /> |
| 152 | + </button> |
| 153 | + </div> |
| 154 | + </div> |
| 155 | + </div> |
| 156 | + </div> |
| 157 | + ))} |
| 158 | + </div> |
| 159 | + )} |
78 | 160 | </div> |
79 | 161 | ); |
80 | 162 | } |
0 commit comments