Skip to content

Commit a0294ff

Browse files
committed
now image is loading and the ui looks good
1 parent 648c805 commit a0294ff

File tree

4 files changed

+208
-89
lines changed

4 files changed

+208
-89
lines changed

src/app/api/events/[id]/route.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,27 @@ import { processFileForStorage } from "@/lib/storage";
55
import { eq } from "drizzle-orm";
66

77

8-
// Helper function to convert binary image to base64
8+
// Helper function to convert binary image to base64 data URL
99
function processEventImage(event: Event) {
1010
if (!event.imageData) return { ...event, imageData: null };
1111

12-
const buffer = Buffer.isBuffer(event.imageData)
13-
? event.imageData
14-
: Buffer.from(event.imageData as unknown as ArrayBuffer);
15-
16-
const base64Image = buffer.toString('base64');
17-
return {
18-
...event,
19-
imageData: `data:${event.imageMimeType};base64,${base64Image}`
20-
};
12+
try {
13+
const buffer = Buffer.isBuffer(event.imageData)
14+
? event.imageData
15+
: Buffer.from(event.imageData as unknown as ArrayBuffer);
16+
17+
const base64Image = buffer.toString('base64');
18+
const mimeType = event.imageMimeType || 'image/jpeg';
19+
20+
return {
21+
...event,
22+
imageData: `data:${mimeType};base64,${base64Image}`,
23+
imageMimeType: mimeType // Ensure mimeType is included in the response
24+
};
25+
} catch (error) {
26+
console.error('Error processing image:', error);
27+
return { ...event, imageData: null, imageMimeType: null };
28+
}
2129
}
2230

2331
// GET one event

src/app/api/events/route.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,27 @@ import { events, type Event } from "@/lib/db/schema";
33
import { NextResponse } from "next/server";
44
import { processFileForStorage } from "@/lib/storage";
55

6-
// Helper function to convert binary image to base64
6+
// Helper function to convert binary image to base64 data URL
77
function processEventImage(event: Event) {
88
if (!event.imageData) return { ...event, imageData: null };
99

10-
const buffer = Buffer.isBuffer(event.imageData)
11-
? event.imageData
12-
: Buffer.from(event.imageData as unknown as ArrayBuffer);
13-
14-
const base64Image = buffer.toString('base64');
15-
return {
16-
...event,
17-
imageData: `data:${event.imageMimeType};base64,${base64Image}`
18-
};
10+
try {
11+
const buffer = Buffer.isBuffer(event.imageData)
12+
? event.imageData
13+
: Buffer.from(event.imageData as unknown as ArrayBuffer);
14+
15+
const base64Image = buffer.toString('base64');
16+
const mimeType = event.imageMimeType || 'image/jpeg';
17+
18+
return {
19+
...event,
20+
imageData: `data:${mimeType};base64,${base64Image}`,
21+
imageMimeType: mimeType // Ensure mimeType is included in the response
22+
};
23+
} catch (error) {
24+
console.error('Error processing image:', error);
25+
return { ...event, imageData: null, imageMimeType: null };
26+
}
1927
}
2028

2129
export async function GET() {

src/app/events/[id]/page.tsx

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { notFound } from "next/navigation";
2+
import Image from "next/image";
23

34
export default async function EventDetailPage({
45
params,
@@ -7,8 +8,7 @@ export default async function EventDetailPage({
78
}) {
89
const { id } = params;
910

10-
const baseUrl =
11-
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
11+
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
1212

1313
const res = await fetch(`${baseUrl}/api/events/${id}`, {
1414
cache: "no-store",
@@ -19,20 +19,41 @@ export default async function EventDetailPage({
1919
const event = await res.json();
2020

2121
return (
22-
<div className="max-w-2xl mx-auto p-6 space-y-4">
23-
<h1 className="text-3xl font-bold">{event.title}</h1>
24-
<p className="text-gray-600">{event.venue}</p>
25-
<p className="text-gray-500">
26-
{new Date(event.date).toLocaleDateString()} at {event.time}
27-
</p>
28-
{event.image && (
29-
<img
30-
src={event.image}
31-
alt={event.title}
32-
className="w-full rounded-lg shadow"
33-
/>
22+
<div className="max-w-4xl mx-auto p-6 space-y-6">
23+
<div className="space-y-2">
24+
<h1 className="text-4xl font-bold text-gray-900">{event.title}</h1>
25+
<div className="flex items-center text-gray-600 space-x-4">
26+
<span>{event.venue}</span>
27+
<span></span>
28+
<span>
29+
{new Date(event.date).toLocaleDateString('en-US', {
30+
year: 'numeric',
31+
month: 'long',
32+
day: 'numeric'
33+
})}
34+
</span>
35+
<span></span>
36+
<span>{event.time}</span>
37+
</div>
38+
</div>
39+
40+
{event.imageData && (
41+
<div className="relative w-full h-96 rounded-lg overflow-hidden shadow-lg">
42+
<Image
43+
src={event.imageData}
44+
alt={event.title}
45+
fill
46+
className="object-cover"
47+
priority
48+
/>
49+
</div>
50+
)}
51+
52+
{event.description && (
53+
<div className="prose max-w-none mt-8">
54+
<p className="whitespace-pre-line text-gray-700">{event.description}</p>
55+
</div>
3456
)}
35-
<p className="mt-4 whitespace-pre-line">{event.description}</p>
3657
</div>
3758
);
3859
}

src/app/events/page.tsx

Lines changed: 136 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,161 @@
22
"use client";
33

44
import { useState, useEffect } from "react";
5-
import { Plus, Pencil, Trash } from "lucide-react";
5+
import { Plus, Pencil, Trash, Calendar, MapPin, Clock } from "lucide-react";
66
import { useRouter } from "next/navigation";
7+
import Image from "next/image";
78

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+
}
1519

20+
export default function AdminEventsPage() {
1621
const [events, setEvents] = useState<Event[]>([]);
22+
const [isLoading, setIsLoading] = useState(true);
1723
const router = useRouter();
1824

1925
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();
2139
}, []);
2240

2341
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+
);
2659
}
2760

2861
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>
3268
<button
3369
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"
3571
>
36-
<Plus className="w-4 h-4" /> Add Event
72+
<Plus className="w-5 h-5" />
73+
<span>Add New Event</span>
3774
</button>
3875
</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>
7676

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+
)}
78160
</div>
79161
);
80162
}

0 commit comments

Comments
 (0)