Skip to content
Open
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
167 changes: 167 additions & 0 deletions app/admin/events/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
"use client";

import { useEffect, useState } from "react";
import AdminLayout from "@/components/admin/AdminLayout";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Need to incorporate RBAC (Role-based access control). So incorporate useSession() and hasRole()

here add this:

import { useSession } from "next-auth/react";
import { hasRole } from "@/lib/rbac";

type Event = {
id: string;
title: string;
description?: string | null;
startAt: string;
endAt?: string | null;
location?: string | null;
link?: string | null;
};

export default function AdminEventsPage() {
const [events, setEvents] = useState<Event[]>([]);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Continuing with the RBAC comments, add this right under AdminEventsPage() and keep the other stuff:

  const { data: session } = useSession();
  const canCreate = hasRole(session?.user?.role ?? "USER", "AMBASSADOR");

const [loading, setLoading] = useState(true);

const [title, setTitle] = useState("");
const [startAt, setStartAt] = useState("");
const [location, setLocation] = useState("");

const [message, setMessage] = useState("");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Track success vs error...

const [message, setMessage] = useState<{ text: string; isError: boolean } | null>(null);

Now have to update every setMessage(...) call (I will create a comment on each one to help)


async function loadEvents() {
setLoading(true);
setMessage("");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

setMessage(null);


try {
const res = await fetch("/api/admin/events");
const data = await res.json();

if (!res.ok) {
setEvents([]);
setMessage(data?.error || "Failed to load events");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

setMessage({ text: data?.error || "Failed to load events", isError: true });

return;
}

// Ensure events is always an array
setEvents(Array.isArray(data) ? data : []);
} catch {
setEvents([]);
setMessage("Failed to load events");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

setMessage({ text: "Failed to load events", isError: true });

} finally {
setLoading(false);
}
}

useEffect(() => {
loadEvents();
}, []);

async function createEvent(e: React.FormEvent) {
e.preventDefault();
setMessage("");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

setMessage(null);


try {
const res = await fetch("/api/admin/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title, startAt, location }),
});

const data = await res.json();

if (!res.ok) {
setMessage(data?.error || "Failed to create event");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

setMessage({ text: data?.error || "Failed to create event", isError: true });

return;
}

setMessage("Event created successfully");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

setMessage({ text: "Event created successfully", isError: false });


setTitle("");
setStartAt("");
setLocation("");

loadEvents();
} catch {
setMessage("Failed to create event");
}
}

return (
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Something like this for the role checking, adding to what i added above:

{canCreate && (
  <form onSubmit={createEvent} className="mt-6 space-y-4 max-w-md">
    ...
  </form>
)}
{!canCreate && (
  <p className="mt-6 text-sm text-gray-500">You do not have permission to create events.</p>
)}

<AdminLayout>
<h1 className="text-2xl font-semibold">Admin Events</h1>

{/* Create Event Form */}
<form onSubmit={createEvent} className="mt-6 space-y-4 max-w-md">
<div>
<label className="block text-sm font-medium">Title</label>
<input
className="w-full border rounded px-3 py-2"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>

<div>
<label className="block text-sm font-medium">Start Time</label>
<input
type="datetime-local"
className="w-full border rounded px-3 py-2"
value={startAt}
onChange={(e) => setStartAt(e.target.value)}
required
/>
</div>

<div>
<label className="block text-sm font-medium">Location</label>
<input
className="w-full border rounded px-3 py-2"
value={location}
onChange={(e) => setLocation(e.target.value)}
/>
</div>

<button className="border px-4 py-2 rounded hover:bg-gray-50">
Create Event
</button>

{message && (
<p className="text-sm mt-2 text-red-600">
{message}
</p>
)}
Comment on lines +126 to +130
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

{message && (
  <p className={`text-sm mt-2 ${message.isError ? "text-red-600" : "text-green-600"}`}>
    {message.text}
  </p>
)}

"Event created successfully" renders in red because message is a plain string with no distinction between success and error. Change message state to { text: string; isError: boolean } | null, and set isError: false on success and isError: true on errors, then conditionally apply text-green-600 vs text-red-600.

</form>

{/* Events Table */}
<div className="mt-10">
<h2 className="text-lg font-semibold mb-4">Events</h2>

{loading ? (
<p>Loading...</p>
) : events.length === 0 ? (
<p>No events yet.</p>
) : (
<table className="w-full border">
<thead className="bg-gray-50">
<tr>
<th className="text-left p-2 border">Title</th>
<th className="text-left p-2 border">Start</th>
<th className="text-left p-2 border">Location</th>
</tr>
</thead>

<tbody>
{events.map((event) => (
<tr key={event.id}>
<td className="p-2 border">{event.title}</td>
<td className="p-2 border">
{new Date(event.startAt).toLocaleString()}
</td>
<td className="p-2 border">{event.location || "-"}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</AdminLayout>
);
}