Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions app/api/events/[slug]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { NextRequest, NextResponse } from "next/server";

import connectDB from "@/lib/mongodb";
import Event, { IEvent } from "@/database/event.model";

// Define route params type for type safety
type RouteParams = {
params: Promise<{
slug: string;
}>;
};

/**
* GET /api/events/[slug]
* Fetches a single events by its slug
*/
export async function GET(
req: NextRequest,
{ params }: RouteParams
): Promise<NextResponse> {
try {
// Connect to database
await connectDB();

// Await and extract slug from params
const { slug } = await params;

// Validate slug parameter
if (!slug || typeof slug !== "string" || slug.trim() === "") {
return NextResponse.json(
{ message: "Invalid or missing slug parameter" },
{ status: 400 }
);
}

// Sanitize slug (remove any potential malicious input)
const sanitizedSlug = slug.trim().toLowerCase();

// Query events by slug
const event: IEvent | null = await Event.findOne({
slug: sanitizedSlug,
}).lean();

// Handle events not found
if (!event) {
return NextResponse.json(
{ message: `Event with slug '${sanitizedSlug}' not found` },
{ status: 404 }
);
}

// Return successful response with events data
return NextResponse.json(
{ message: "Event fetched successfully", event },
{ status: 200 }
);
} catch (error) {
// Log error for debugging (only in development)
if (process.env.NODE_ENV === "development") {
console.error("Error fetching events by slug:", error);
}

// Handle specific error types
if (error instanceof Error) {
// Handle database connection errors
if (error.message.includes("MONGODB_URI")) {
return NextResponse.json(
{ message: "Database configuration error" },
{ status: 500 }
);
}

// Return generic error with error message
return NextResponse.json(
{ message: "Failed to fetch events", error: error.message },
{ status: 500 }
);
}

// Handle unknown errors
return NextResponse.json(
{ message: "An unexpected error occurred" },
{ status: 500 }
);
}
}
94 changes: 94 additions & 0 deletions app/api/events/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Event } from "@/database";
import { v2 as cloudinary } from "cloudinary";
import connectDB from "@/lib/mongodb";
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
try {
await connectDB();

const formData = await req.formData();

let event;

try {
event = Object.fromEntries(formData.entries());
} catch (error) {
return NextResponse.json(
{ message: "Invalid JSON data format" },
{ status: 400 }
);
}

const file = formData.get("image") as File;
if (!file) {
return NextResponse.json(
{ message: "Image file is required" },
{ status: 400 }
);
}

let tags = JSON.parse(formData.get("tags") as string);
let agenda = JSON.parse(formData.get("agenda") as string);
Comment on lines +31 to +32
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Unhandled JSON.parse errors will crash the request.

If tags or agenda fields contain invalid JSON or are missing, JSON.parse will throw an uncaught exception, resulting in a 500 error instead of a proper 400 validation error.

🔎 Proposed fix
-    let tags = JSON.parse(formData.get("tags") as string);
-    let agenda = JSON.parse(formData.get("agenda") as string);
+    let tags: string[];
+    let agenda: string[];
+    
+    try {
+      const tagsRaw = formData.get("tags");
+      const agendaRaw = formData.get("agenda");
+      
+      if (!tagsRaw || !agendaRaw) {
+        return NextResponse.json(
+          { message: "Tags and agenda are required" },
+          { status: 400 }
+        );
+      }
+      
+      tags = JSON.parse(tagsRaw as string);
+      agenda = JSON.parse(agendaRaw as string);
+    } catch {
+      return NextResponse.json(
+        { message: "Invalid JSON format for tags or agenda" },
+        { status: 400 }
+      );
+    }
🤖 Prompt for AI Agents
In app/api/events/route.ts around lines 31-32, the direct calls to JSON.parse on
formData.get("tags") and formData.get("agenda") can throw if the fields are
missing or contain invalid JSON; wrap each parse in validation and error
handling: first check formData.get(...) is not null/undefined and is a string,
then parse inside a try/catch and on parse errors return a 400 Bad Request with
a clear validation message; alternatively, use a helper that attempts JSON.parse
and returns a tuple or throws a controlled ValidationError so callers can
produce a 400 response rather than letting the exception bubble as a 500.


const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);

const uploadResult = await new Promise((resolve, reject) => {
cloudinary.uploader
.upload_stream(
{ resource_type: "image", folder: "dev-events" },
(error, result) => {
if (error) return reject(error);
resolve(result);
}
)
.end(buffer);
});

event.image = (uploadResult as { secure_url: string }).secure_url;

const createdEvent = await Event.create({ ...event, tags, agenda });

return NextResponse.json(
{
message: "Event Created Successfully",
event: createdEvent,
},
{ status: 201 }
);
} catch (e) {
console.error("Error handling POST request:", e);
return NextResponse.json(
{
message: "Event Creation Failed",
error: e instanceof Error ? e.message : "Unknown error",
},
{ status: 500 }
);
}
}

export async function GET() {
try {
await connectDB();
const events = await Event.find().sort({ createdAt: -1 });

if (!events)
return NextResponse.json({ message: "No event found" }, { status: 404 });
Comment on lines +77 to +78
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Incorrect empty check—Event.find() returns an array, never null.

Event.find() returns an empty array [] when no documents match, not null or undefined. The condition !events will never be true.

🔎 Proposed fix
     const events = await Event.find().sort({ createdAt: -1 });
 
-    if (!events)
+    if (events.length === 0)
       return NextResponse.json({ message: "No event found" }, { status: 404 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!events)
return NextResponse.json({ message: "No event found" }, { status: 404 });
const events = await Event.find().sort({ createdAt: -1 });
if (events.length === 0)
return NextResponse.json({ message: "No event found" }, { status: 404 });
🤖 Prompt for AI Agents
In app/api/events/route.ts around lines 77 to 78, the code checks `if (!events)`
after `Event.find()` but `find()` always returns an array, so that condition
never triggers; replace the check with an explicit empty-array check such as `if
(events.length === 0)` (or `if (!events?.length)` if you prefer defensive
coding) and return the 404 response when the array is empty.


return NextResponse.json(
{ message: "Events fetched successfully", events },
{ status: 200 }
);
} catch (e) {
console.error("Error handling GET request:", e);
return NextResponse.json(
{
message: "Failed to fetch events",
error: e instanceof Error ? e.message : "Unknown error",
},
{ status: 500 }
);
}
}
19 changes: 19 additions & 0 deletions app/event/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Suspense } from "react";
import EventDetails from "@/components/EventDetails";

const EventDetailsPage = async ({
params,
}: {
params: Promise<{ slug: string }>;
}) => {
const slug = params.then((p) => p.slug);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CRITICAL: Promise assigned instead of awaited value.

Line 9 assigns a Promise to the slug variable instead of extracting the actual string value. This will cause runtime errors when EventDetails tries to use the slug.

🔎 Required fix
  const EventDetailsPage = async ({
    params,
  }: {
    params: Promise<{ slug: string }>;
  }) => {
-   const slug = params.then((p) => p.slug);
+   const { slug } = await params;

    return (
      <main>
        <Suspense fallback={<div>Loading...</div>}>
-         <EventDetails params={slug} />
+         <EventDetails slug={slug} />
        </Suspense>
      </main>
    );
  };
🤖 Prompt for AI Agents
In app/event/[slug]/page.tsx around line 9, the code assigns a Promise to `slug`
(const slug = params.then((p) => p.slug)); instead of the resolved string;
change this to await the params and extract the slug (e.g., ensure the
component/function is async, do `const { slug } = await params;` or `const
paramsResolved = await params; const slug = paramsResolved.slug;`) and ensure
`slug` is typed as a string before passing to EventDetails.


return (
<main>
<Suspense fallback={<div>Loading...</div>}>
<EventDetails params={slug} />
</Suspense>
</main>
);
};
export default EventDetailsPage;
24 changes: 17 additions & 7 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import EventCard from "@/components/EventCard";
import ExploreBtn from "@/components/ExploreBtn";
import { events } from "@/lib/constants";
import { IEvent } from "@/database";
import { cacheLife } from "next/cache";

const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;

const Page = async () => {
"use cache";
cacheLife("hours");
const response = await fetch(`${BASE_URL}/api/events`);
const { events } = await response.json();
Comment on lines +6 to +12
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling and validation for data fetching.

The fetch call lacks critical error handling:

  1. BASE_URL might be undefined if the environment variable is not set
  2. No check for response.ok before parsing JSON
  3. No try-catch for network or parsing errors

This will cause unhandled exceptions if the API is unavailable or returns an error.

🔎 Proposed fix
  const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;

  const Page = async () => {
    "use cache";
    cacheLife("hours");
+   
+   if (!BASE_URL) {
+     console.error('NEXT_PUBLIC_BASE_URL is not configured');
+     return <div>Configuration error</div>;
+   }
+   
+   try {
      const response = await fetch(`${BASE_URL}/api/events`);
+     
+     if (!response.ok) {
+       throw new Error(`Failed to fetch events: ${response.statusText}`);
+     }
+     
      const { events } = await response.json();
+     
+     if (!events || !Array.isArray(events)) {
+       throw new Error('Invalid events data structure');
+     }
+   } catch (error) {
+     console.error('Error fetching events:', error);
+     return <div>Failed to load events</div>;
+   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
const Page = async () => {
"use cache";
cacheLife("hours");
const response = await fetch(`${BASE_URL}/api/events`);
const { events } = await response.json();
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
const Page = async () => {
"use cache";
cacheLife("hours");
if (!BASE_URL) {
console.error('NEXT_PUBLIC_BASE_URL is not configured');
return <div>Configuration error</div>;
}
try {
const response = await fetch(`${BASE_URL}/api/events`);
if (!response.ok) {
throw new Error(`Failed to fetch events: ${response.statusText}`);
}
const { events } = await response.json();
if (!events || !Array.isArray(events)) {
throw new Error('Invalid events data structure');
}
} catch (error) {
console.error('Error fetching events:', error);
return <div>Failed to load events</div>;
}
🤖 Prompt for AI Agents
In app/page.tsx around lines 6 to 12, the fetch logic doesn't validate BASE_URL,
doesn't check response.ok, and lacks try/catch for network or parsing errors;
update the code to (1) validate that process.env.NEXT_PUBLIC_BASE_URL is set and
throw or return a clear error if missing, (2) wrap the fetch and response.json()
in a try/catch to handle network and parsing failures, (3) after fetch check
response.ok and handle non-2xx responses by logging/throwing a descriptive error
(including status and statusText) instead of blindly calling response.json(),
and (4) return a safe default (e.g., empty events array) or propagate a
controlled error to the caller.


const Page = () => {
return (
<section>
<h1 className="text-center">
Expand All @@ -17,11 +25,13 @@ const Page = () => {
<div className="mt-20 space-y-7">
<h3>Featured Events</h3>
<ul className="events">
{events.map((event) => (
<li key={event.title}>
<EventCard {...event} />
</li>
))}
{events &&
events.length > 0 &&
events.map((event: IEvent) => (
<li className="list-none" key={event.title}>
<EventCard {...event} />
</li>
))}
</ul>
</div>
</section>
Expand Down
41 changes: 41 additions & 0 deletions components/BookEvent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"use client";

import React, { useState } from "react";

const BookEvent = () => {
const [email, setEmail] = useState("");
const [submitted, setSubmitted] = useState(false);

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();

setTimeout(() => {
setSubmitted(true);
}, 1000);
};
Comment on lines +9 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Form submission is only simulated, not implemented.

The handleSubmit function uses a timeout to simulate submission but never actually sends the email data to any API endpoint. This creates a misleading user experience where users think they've registered but no data is persisted.

Do you want me to generate an implementation that calls an API endpoint to persist the booking data?

🔎 Proposed fix to add actual API call
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
-
-    setTimeout(() => {
-      setSubmitted(true);
-    }, 1000);
+    
+    // Call API to submit booking
+    fetch('/api/bookings', {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ email }),
+    })
+      .then(response => {
+        if (response.ok) {
+          setSubmitted(true);
+        }
+      })
+      .catch(error => {
+        console.error('Booking failed:', error);
+        // Handle error state
+      });
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setTimeout(() => {
setSubmitted(true);
}, 1000);
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Call API to submit booking
fetch('/api/bookings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
})
.then(response => {
if (response.ok) {
setSubmitted(true);
}
})
.catch(error => {
console.error('Booking failed:', error);
// Handle error state
});
};
🤖 Prompt for AI Agents
In components/BookEvent.tsx around lines 9 to 15, the handleSubmit currently
only uses setTimeout to simulate submission and does not persist any booking
data; replace the simulated timeout with an actual async submission: collect the
form state (name, email, event details) and send it to your backend booking API
(via fetch or axios) inside an async function, await the response, and on
success setSubmitted(true) and clear loading state; wrap the call in try/catch
to set an error state on failure and avoid marking submitted when the request
fails; also remove the artificial setTimeout and add a loading indicator state
so the UI reflects real network activity.


return (
<div id="book-event">
{submitted ? (
<p className="text-sm">Thank you for signing up!</p>
) : (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">Email Address</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
id="email"
placeholder="Enter your email address"
/>
</div>
<button type="submit" className="button-submit">
Submit
</button>
</form>
)}
</div>
);
};
export default BookEvent;
2 changes: 1 addition & 1 deletion components/EventCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const EventCard = ({ title, image, slug, location, date, time }: Props) => {
};

return (
<Link href="/events" id="event-card" onClick={handleClick}>
<Link href={`/event/${slug}`} id="event-card" onClick={handleClick}>
<Image
src={image}
alt="title"
Expand Down
Loading