diff --git a/README.md b/README.md
index 92464fa..e985cd2 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ My goal for this project is to be an all-in-one self hosted event planner for ma
- [x] First time setup to create the admin user
- [x] Invite users via email (smtp) users can be COUPLE, PLANNER, GUEST
- [x] Create local accounts (no use of SMTP)
-- [ ] Creating custom events
+- [x] Creating and Editing custom events
- [ ] Information about each event
- Date/Time
- Event type
@@ -40,6 +40,9 @@ My goal for this project is to be an all-in-one self hosted event planner for ma
- added usernames to `Users` table
- updated first time setup to include username creation
+#### 6.25.25
+- now able to see and edit event data from the individual event page
+
## Getting Started
This is very much a work in progress but this `README` will stay up to date on working features and steps to get it running **in its current state**. That being said if you're interested in starting it as is, you can follow these instructions.
diff --git a/app/(auth)/dashboard/page.tsx b/app/(auth)/dashboard/page.tsx
index 0a217c9..88728fb 100644
--- a/app/(auth)/dashboard/page.tsx
+++ b/app/(auth)/dashboard/page.tsx
@@ -18,8 +18,8 @@ export default async function DashboardPage() {
Name: {item.name}
Date: {item.date ? item.date.toISOString() : 'null'}
Location: {item.location ? item.location : 'null'}
- Creator ID:{item.creatorId}
- Created At:{item.createdAt.toISOString()}
+ Created By: {item.creator.username}
+ Created At: {item.createdAt.toISOString()}
diff --git a/app/(auth)/events/[eventId]/page.tsx b/app/(auth)/events/[eventId]/page.tsx
index 182b294..9adb68e 100644
--- a/app/(auth)/events/[eventId]/page.tsx
+++ b/app/(auth)/events/[eventId]/page.tsx
@@ -1,15 +1,24 @@
+/* eslint-disable @typescript-eslint/ban-ts-comment */
+import EventInfoDisplay from '@/components/EventInfoDisplay'
+import HeadingWithEdit from '@/components/HeadingWithEdit'
import { queries } from '@/lib/queries'
import React from 'react'
export default async function SingleEventPage({ params }: { params: { eventId: string }}) {
- console.log(params)
const data = await queries.singleEvent(params.eventId)
+ console.log(data)
return (
-
- {data?.name}
-
+ {data ? (
+ // @ts-ignore
+
+ ) : (
+
Event not found.
+ )}
+ {data?.name && (
+
+ )}
)
}
diff --git a/app/api/events/[eventId]/route.ts b/app/api/events/[eventId]/route.ts
new file mode 100644
index 0000000..647dcb0
--- /dev/null
+++ b/app/api/events/[eventId]/route.ts
@@ -0,0 +1,22 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { mutations } from '@/lib/mutations';
+import { getServerSession } from 'next-auth';
+import { authOptions } from '../../auth/[...nextauth]/route';
+
+export async function PATCH(req: NextRequest, { params }: { params: { eventId: string } }) {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return new NextResponse('Unauthorized', { status: 401 });
+ }
+
+ const eventId = params.eventId;
+ const body = await req.json();
+
+ try {
+ const updated = await mutations.updateEvent(eventId, body);
+ return NextResponse.json(updated);
+ } catch (error) {
+ console.error('[PATCH EVENT]', error);
+ return new NextResponse('Failed to update event', { status: 500 });
+ }
+}
diff --git a/components/EventInfoDisplay.tsx b/components/EventInfoDisplay.tsx
new file mode 100644
index 0000000..68cbce6
--- /dev/null
+++ b/components/EventInfoDisplay.tsx
@@ -0,0 +1,172 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable @typescript-eslint/no-unused-vars */
+'use client'
+
+import React, { useState } from 'react'
+
+interface Creator {
+ id: string
+ email: string
+ name: string | null
+ role: 'COUPLE' | 'PLANNER' | 'GUEST'
+}
+
+interface EventData {
+ id: string
+ name: string
+ date: Date | null
+ location: string | null
+ creatorId: string
+ createdAt: string
+ creator: Creator
+ guests: any[]
+}
+
+interface Props {
+ event: EventData
+}
+
+export default function EventInfoDisplay({ event }: Props) {
+ const [isEditing, setIsEditing] = useState(false)
+
+ const [saving, setSaving] = useState(false)
+ const [error, setError] = useState('')
+ const [dateTime, setDateTime] = useState(() => {
+ if (event.date) {
+ const date = new Date(event.date);
+ return new Date(date.getTime() - date.getTimezoneOffset() * 60000)
+ .toISOString()
+ .slice(0, 16); // format: "yyyy-MM-ddTHH:mm"
+ }
+ return '';
+ });
+ const [form, setForm] = useState({
+ name: event.name,
+ date: dateTime,
+ location: event.location || '',
+ })
+
+ function handleChange(e: React.ChangeEvent) {
+ const { name, value } = e.target
+ setForm(prev => ({ ...prev, [name]: value }))
+ }
+
+ async function handleSave() {
+ setSaving(true)
+ setError('')
+ try {
+ const res = await fetch(`/api/events/${event.id}`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(form),
+ })
+
+ if (!res.ok) {
+ const data = await res.json()
+ setError(data.message || 'Update failed')
+ return
+ }
+
+ setIsEditing(false)
+ } catch (err) {
+ setError('Something went wrong.')
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ function formatDate(date: string) {
+ const d = new Date(date)
+ return `${d.toLocaleDateString()} ${d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
+ }
+
+ return (
+
+
+
Event Info
+ setIsEditing(prev => !prev)}
+ className="text-sm text-blue-600 underline"
+ >
+ {isEditing ? 'Cancel' : 'Edit'}
+
+
+
+
+ {/* Event Name */}
+
+
Event Name
+ {isEditing ? (
+
+ ) : (
+
{event.name}
+ )}
+
+
+ {/* Event Date */}
+
+
Date
+ {isEditing ? (
+
setDateTime(e.target.value)}
+ />
+ ) : (
+
{event.date ? formatDate(event.date.toDateString()) : 'N/A'}
+ )}
+
+
+ {/* Location */}
+
+
Location
+ {isEditing ? (
+
+ ) : (
+
{event.location || 'N/A'}
+ )}
+
+
+ {/* Creator Email */}
+
+
Creator Email
+
{event.creator.email}
+
+
+ {/* Created At */}
+
+
Created At
+
{formatDate(event.createdAt)}
+
+
+
+ {error &&
{error}
}
+
+ {isEditing && (
+
+
+ {saving ? 'Saving...' : 'Save Changes'}
+
+
+ )}
+
+ )
+}
diff --git a/components/HeadingWithEdit.tsx b/components/HeadingWithEdit.tsx
new file mode 100644
index 0000000..7f00e8f
--- /dev/null
+++ b/components/HeadingWithEdit.tsx
@@ -0,0 +1,77 @@
+'use client'
+
+import React, { useState } from 'react'
+import PencilIcon from './icons/PencilIcon'
+
+export default function HeadingWithEdit({
+ title,
+ eventId,
+}: {
+ title: string
+ eventId: string
+}) {
+ const [edit, setEdit] = useState(false)
+ const [currentTitle, setCurrentTitle] = useState(title)
+ const [newTitle, setNewTitle] = useState(title)
+ const [saving, setSaving] = useState(false)
+ const [error, setError] = useState('')
+
+ async function handleSave() {
+ setSaving(true)
+ setError('')
+
+ try {
+ const res = await fetch(`/api/events/${eventId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ name: newTitle }),
+ })
+
+ if (!res.ok) {
+ const data = await res.json()
+ setError(data.message || 'Failed to update event')
+ return
+ }
+
+ setCurrentTitle(newTitle) // update display title
+ setEdit(false)
+ } catch (err) {
+ console.error(err)
+ setError('Something went wrong')
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ return (
+ <>
+
+ {edit ? (
+ setNewTitle(e.target.value)}
+ className="border rounded-lg px-2 py-1"
+ />
+ ) : (
+ currentTitle
+ )}
+ setEdit(!edit)} className="hover:cursor-pointer">
+
+
+ {edit && (
+
+ {saving ? 'Saving...' : 'Save'}
+
+ )}
+
+ {error && {error}
}
+ >
+ )
+}
diff --git a/components/icons/PencilIcon.tsx b/components/icons/PencilIcon.tsx
new file mode 100644
index 0000000..02e81ac
--- /dev/null
+++ b/components/icons/PencilIcon.tsx
@@ -0,0 +1,11 @@
+import React from 'react'
+
+export default function PencilIcon() {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/lib/mutations.ts b/lib/mutations.ts
index 3994602..197eb93 100644
--- a/lib/mutations.ts
+++ b/lib/mutations.ts
@@ -57,4 +57,31 @@ export const mutations = {
return user
},
+
+ async updateEvent(
+ eventId: string,
+ data: Partial<{ name: string; date: string; location: string }>
+ ) {
+ const { date, ...rest } = data;
+
+ let parsedDate: Date | undefined = undefined;
+
+ if (date) {
+ // Parse full datetime-local string into Date object
+ parsedDate = new Date(date); // Automatically handled as local time
+ }
+
+ const event = await prisma.event.update({
+ where: { id: eventId },
+ data: {
+ ...rest,
+ ...(parsedDate ? { date: parsedDate } : {}),
+ },
+ });
+
+ return event;
+ }
+
+
+
};
\ No newline at end of file
diff --git a/lib/queries.ts b/lib/queries.ts
index 270a517..5c22af9 100644
--- a/lib/queries.ts
+++ b/lib/queries.ts
@@ -2,7 +2,16 @@ import { prisma } from './prisma';
export const queries = {
async fetchEvents() {
- const allEvents = await prisma.event.findMany()
+ const allEvents = await prisma.event.findMany({
+ include: {
+ creator: {
+ select: {
+ id: true,
+ username: true
+ }
+ }
+ }
+ })
console.log(allEvents)
return allEvents;
},
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index e1829bf..6ba0c56 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -25,14 +25,14 @@ enum Role {
}
model Event {
- id String @id @default(cuid())
- name String
- date DateTime?
- location String?
- creator User @relation("EventCreator", fields: [creatorId], references: [id])
- creatorId String
- guests Guest[]
- createdAt DateTime @default(now())
+ id String @id @default(cuid())
+ name String
+ date DateTime?
+ location String?
+ creator User @relation("EventCreator", fields: [creatorId], references: [id])
+ creatorId String
+ guests Guest[]
+ createdAt DateTime @default(now())
}
model Guest {