diff --git a/app/(auth)/events/page.tsx b/app/(auth)/events/page.tsx
index c257ba4..7de53d5 100644
--- a/app/(auth)/events/page.tsx
+++ b/app/(auth)/events/page.tsx
@@ -7,37 +7,40 @@ export default async function EventsPage() {
console.log(allEvents)
return (
-
- Events
-
- {allEvents.length == 0 ? (
- <>
- You don't have any events yet.
Create One!
- >
- ) : (
-
-
-
- | Event Name |
- Event Date |
- Created by |
-
-
-
- {allEvents.map((item) => (
-
- | {item.name} |
- {item.date?.toDateString()} |
- {item.creatorId} |
-
- ))}
-
-
- )}
+
+
+
Your Events
+
+ Create Event
+
+
+ {allEvents.length === 0 ? (
+
+ You don't have any events yet.{' '}
+
+ Create one!
+
+
+ ) : (
+
+ {allEvents.map(event => (
+
+
{event.name}
+
+ {event.date ? new Date(event.date).toLocaleDateString() : 'No date set'}
+
+
+ Created by: {event.creator?.username || 'Unknown'}
+
+
+ ))}
+
+ )}
)
}
diff --git a/app/api/events/[eventId]/todo/[todoId]/route.ts b/app/api/events/[eventId]/todo/[todoId]/route.ts
new file mode 100644
index 0000000..2c5c440
--- /dev/null
+++ b/app/api/events/[eventId]/todo/[todoId]/route.ts
@@ -0,0 +1,49 @@
+// app/api/events/[eventId]/todos/[todoId]/route.ts
+import { NextRequest, NextResponse } from 'next/server';
+import { mutations } from '@/lib/mutations';
+import { getServerSession } from 'next-auth';
+import { authOptions } from '@/app/api/auth/[...nextauth]/route';
+
+export async function PATCH(
+ req: NextRequest,
+ { params }: { params: { todoId: string; eventId: string } }
+) {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return new NextResponse('Unauthorized', { status: 401 });
+ }
+
+ const { name, dueDate, complete } = await req.json();
+
+ try {
+ const updated = await mutations.updateEventTodo(params.todoId, {
+ name,
+ dueDate,
+ complete,
+ });
+
+ return NextResponse.json(updated);
+ } catch (error) {
+ console.error('[UPDATE_TODO]', error);
+ return new NextResponse('Failed to update todo', { status: 500 });
+ }
+}
+
+export async function DELETE(
+ _req: NextRequest,
+ { params }: { params: { todoId: string; eventId: string } }
+) {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return new NextResponse('Unauthorized', { status: 401 });
+ }
+
+ try {
+ await mutations.deleteEventTodo(params.todoId);
+ return new NextResponse(null, { status: 204 });
+ } catch (error) {
+ console.error('[DELETE_TODO]', error);
+ return new NextResponse('Failed to delete todo', { status: 500 });
+ }
+}
+
diff --git a/app/api/events/[eventId]/todo/route.ts b/app/api/events/[eventId]/todo/route.ts
new file mode 100644
index 0000000..81f951a
--- /dev/null
+++ b/app/api/events/[eventId]/todo/route.ts
@@ -0,0 +1,33 @@
+// app/api/events/[eventId]/todos/route.ts
+import { NextRequest, NextResponse } from 'next/server';
+import { mutations } from '@/lib/mutations';
+import { getServerSession } from 'next-auth';
+import { authOptions } from '@/app/api/auth/[...nextauth]/route';
+
+export async function POST(
+ req: NextRequest,
+ { params }: { params: { eventId: string } }
+) {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ return new NextResponse('Unauthorized', { status: 401 });
+ }
+
+ const { name, dueDate } = await req.json();
+
+ if (!name) {
+ return NextResponse.json({ message: 'Name is required' }, { status: 400 });
+ }
+
+ try {
+ const todo = await mutations.addTodoToEvent({
+ eventId: params.eventId,
+ name,
+ dueDate,
+ });
+ return NextResponse.json(todo);
+ } catch (error) {
+ console.error('[CREATE_TODO]', error);
+ return new NextResponse('Failed to create todo', { status: 500 });
+ }
+}
diff --git a/components/EditGuestBookEntryModal.tsx b/components/EditGuestBookEntryModal.tsx
index 8b8ad94..ac55203 100644
--- a/components/EditGuestBookEntryModal.tsx
+++ b/components/EditGuestBookEntryModal.tsx
@@ -97,6 +97,7 @@ export default function EditGuestBookEntryModal({ isOpen, onClose, initialData,
className="input input-bordered w-full"
type="tel"
name="phone"
+ placeholder="phone"
value={formData.phone || ''}
onChange={handleChange}
/>
@@ -104,6 +105,7 @@ export default function EditGuestBookEntryModal({ isOpen, onClose, initialData,
className="input input-bordered w-full"
type="text"
name="address"
+ placeholder="address"
value={formData.address || ''}
onChange={handleChange}
/>
@@ -111,12 +113,14 @@ export default function EditGuestBookEntryModal({ isOpen, onClose, initialData,
className="input input-bordered w-full"
type="text"
name="side"
+ placeholder="Bride/Groom"
value={formData.side || ''}
onChange={handleChange}
/>
diff --git a/components/EventInfoDisplay.tsx b/components/EventInfoDisplay.tsx
index 9adb34f..517f4f4 100644
--- a/components/EventInfoDisplay.tsx
+++ b/components/EventInfoDisplay.tsx
@@ -5,6 +5,8 @@
import React, { useState } from 'react'
import AddGuestFromGuestBook from './AddGuestFromGuestBook'
import EventNotesEditor from './EventNotesEditor'
+import ToDoList from './ToDoList'
+import { fetchEventTodos } from '@/lib/helper/fetchTodos'
interface Creator {
id: string
@@ -13,6 +15,15 @@ interface Creator {
role: 'COUPLE' | 'PLANNER' | 'GUEST'
}
+interface Todo {
+ id: string
+ name: string
+ complete: boolean
+ dueDate?: string | null
+ createdAt: string
+ updatedAt?: string
+}
+
interface EventData {
id: string
name: string
@@ -24,6 +35,7 @@ interface EventData {
guests: any[]
notes?: string
eventGuests: any[]
+ todos: Todo[]
}
interface Props {
@@ -33,6 +45,7 @@ interface Props {
export default function EventInfoDisplay({ event }: Props) {
const [isEditing, setIsEditing] = useState(false);
const [showSearch, setShowSearch] = useState(false);
+ const [todos, setTodos] = useState(event.todos)
const eventGuests = event.eventGuests
console.log(eventGuests)
@@ -83,29 +96,14 @@ export default function EventInfoDisplay({ event }: Props) {
}
}
- // async function handleChangeRsvp(e: any) {
- // const newRsvp = e.target.value as 'YES' | 'NO' | 'PENDING';
-
- // try {
- // const res = await fetch(
- // `/api/events/${guest.eventId}/guests/${guest.guestBookEntryId}/rsvp`,
- // {
- // method: 'PATCH',
- // headers: { 'Content-Type': 'application/json' },
- // body: JSON.stringify({ rsvp: newRsvp }),
- // }
- // );
-
- // if (!res.ok) {
- // throw new Error('Failed to update RSVP');
- // }
-
- // // Optionally trigger re-fetch or state update here
- // } catch (err) {
- // console.error('RSVP update error:', err);
- // // Optionally show error message in UI
- // }
- // }
+ async function refreshTodos() {
+ try {
+ const data = await fetchEventTodos(event.id)
+ setTodos(data)
+ } catch (err) {
+ console.error('Failed to refresh todos:', err)
+ }
+ }
function formatDate(date: string) {
const d = new Date(date)
@@ -241,8 +239,12 @@ export default function EventInfoDisplay({ event }: Props) {
-
)
diff --git a/components/ToDoList.tsx b/components/ToDoList.tsx
new file mode 100644
index 0000000..0c3b7a2
--- /dev/null
+++ b/components/ToDoList.tsx
@@ -0,0 +1,117 @@
+'use client'
+import React, { useState } from 'react'
+
+interface Todo {
+ id: string
+ name: string
+ complete: boolean
+ dueDate?: string | null
+}
+
+interface Props {
+ eventId: string
+ initialTodos: Todo[]
+ onUpdate: () => void
+}
+
+export default function ToDoList({ eventId, initialTodos, onUpdate }: Props) {
+ const [todos, setTodos] = useState(initialTodos)
+ const [newName, setNewName] = useState('')
+ const [newDueDate, setNewDueDate] = useState('')
+
+ async function handleAdd() {
+ if (!newName.trim()) return
+
+ const res = await fetch(`/api/events/${eventId}/todo`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ name: newName, dueDate: newDueDate || null }),
+ })
+
+ const data = await res.json()
+ if (res.ok) {
+ setTodos(prev => [...prev, data])
+ setNewName('')
+ setNewDueDate('')
+ }
+ if (onUpdate) await onUpdate()
+ }
+
+ async function toggleComplete(id: string, complete: boolean) {
+ const res = await fetch(`/api/events/${eventId}/todo/${id}`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ complete }),
+ })
+
+ if (res.ok) {
+ setTodos(prev =>
+ prev.map(todo =>
+ todo.id === id ? { ...todo, complete } : todo
+ )
+ )
+ }
+ if (onUpdate) await onUpdate()
+ }
+
+ async function handleDelete(id: string) {
+ const res = await fetch(`/api/events/${eventId}/todo/${id}`, {
+ method: 'DELETE',
+ })
+
+ if (res.ok) {
+ setTodos(prev => prev.filter(todo => todo.id !== id))
+ }
+ if (onUpdate) await onUpdate()
+ }
+
+ return (
+
+ )
+}
diff --git a/lib/helper/fetchTodos.ts b/lib/helper/fetchTodos.ts
new file mode 100644
index 0000000..b6a0213
--- /dev/null
+++ b/lib/helper/fetchTodos.ts
@@ -0,0 +1,5 @@
+export async function fetchEventTodos(eventId: string) {
+ const res = await fetch(`/api/events/${eventId}/todo`)
+ if (!res.ok) throw new Error('Failed to fetch todos')
+ return await res.json()
+}
\ No newline at end of file
diff --git a/lib/mutations.ts b/lib/mutations.ts
index f4f41da..a246b84 100644
--- a/lib/mutations.ts
+++ b/lib/mutations.ts
@@ -160,4 +160,37 @@ export const mutations = {
});
},
+ async addTodoToEvent(data: {
+ eventId: string;
+ name: string;
+ dueDate?: string;
+ }) {
+ return await prisma.eventTodo.create({
+ data: {
+ name: data.name,
+ eventId: data.eventId,
+ dueDate: data.dueDate ? new Date(data.dueDate) : undefined,
+ },
+ });
+ },
+
+ async updateEventTodo(id: string, data: Partial<{ name: string; dueDate?: string; complete: boolean }>) {
+ const { dueDate, ...rest } = data;
+
+ return await prisma.eventTodo.update({
+ where: { id },
+ data: {
+ ...rest,
+ ...(dueDate !== undefined ? { dueDate: new Date(dueDate) } : {}),
+ },
+ });
+ },
+
+ async deleteEventTodo(id: string) {
+ return await prisma.eventTodo.delete({
+ where: { id },
+ });
+ },
+
+
};
\ No newline at end of file
diff --git a/lib/queries.ts b/lib/queries.ts
index 3736585..e0ee4e2 100644
--- a/lib/queries.ts
+++ b/lib/queries.ts
@@ -71,6 +71,9 @@ export const queries = {
guestBookEntry: true,
},
},
+ todos: {
+ orderBy: { dueDate: 'asc' },
+ },
}
})
return event
diff --git a/prisma/migrations/20250629141739_add_event_todo/migration.sql b/prisma/migrations/20250629141739_add_event_todo/migration.sql
new file mode 100644
index 0000000..972b2ec
--- /dev/null
+++ b/prisma/migrations/20250629141739_add_event_todo/migration.sql
@@ -0,0 +1,15 @@
+-- CreateTable
+CREATE TABLE "EventTodo" (
+ "id" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "complete" BOOLEAN NOT NULL DEFAULT false,
+ "dueDate" TIMESTAMP(3),
+ "eventId" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "EventTodo_pkey" PRIMARY KEY ("id")
+);
+
+-- AddForeignKey
+ALTER TABLE "EventTodo" ADD CONSTRAINT "EventTodo_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index c88a15b..b7101ce 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -34,6 +34,7 @@ model Event {
guests Guest[]
eventGuests EventGuest[]
notes String?
+ todos EventTodo[]
createdAt DateTime @default(now())
}
@@ -77,14 +78,28 @@ model GuestBookEntry {
}
model EventGuest {
- id String @id @default(cuid())
- event Event @relation(fields: [eventId], references: [id])
- eventId String
- guestBookEntry GuestBookEntry @relation(fields: [guestBookEntryId], references: [id])
- guestBookEntryId String
- rsvp RsvpStatus @default(PENDING)
- createdAt DateTime @default(now())
+ id String @id @default(cuid())
+ event Event @relation(fields: [eventId], references: [id])
+ eventId String
+ guestBookEntry GuestBookEntry @relation(fields: [guestBookEntryId], references: [id])
+ guestBookEntryId String
+ rsvp RsvpStatus @default(PENDING)
+ createdAt DateTime @default(now())
- @@unique([eventId, guestBookEntryId])
+ @@unique([eventId, guestBookEntryId])
}
+model EventTodo {
+ id String @id @default(cuid())
+ name String
+ complete Boolean @default(false)
+ dueDate DateTime?
+ event Event @relation(fields: [eventId], references: [id])
+ eventId String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ // Optional for future extensibility
+ // category String?
+ // assignedTo String? // could link to User in future
+}