)
}
diff --git a/app/(auth)/events/[eventId]/page.tsx b/app/(auth)/events/[eventId]/page.tsx
index 9adb68e..afa29f5 100644
--- a/app/(auth)/events/[eventId]/page.tsx
+++ b/app/(auth)/events/[eventId]/page.tsx
@@ -9,16 +9,13 @@ export default async function SingleEventPage({ params }: { params: { eventId: s
console.log(data)
return (
-
+
{data ? (
// @ts-ignore
) : (
Event not found.
)}
- {data?.name && (
-
- )}
)
}
diff --git a/app/(auth)/events/create/page.tsx b/app/(auth)/events/create/page.tsx
deleted file mode 100644
index 70be375..0000000
--- a/app/(auth)/events/create/page.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-'use client'
-import { useState } from 'react';
-import { useRouter } from 'next/navigation';
-
-export default function CreateEventPage() {
- const [name, setName] = useState('');
- const router = useRouter();
-
- async function handleSubmit(e: React.FormEvent) {
- e.preventDefault();
- const res = await fetch('/api/events', {
- method: 'POST',
- body: JSON.stringify({ name }),
- });
- const data = await res.json();
- router.push(`/events/${data.id}`);
- }
-
- return (
-
- );
-}
diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx
index 60035d8..ae9a5c4 100644
--- a/app/(auth)/layout.tsx
+++ b/app/(auth)/layout.tsx
@@ -1,19 +1,22 @@
'use client'
-import { SessionProvider, useSession } from 'next-auth/react'
+import { SessionProvider } from 'next-auth/react'
import { redirect } from 'next/navigation'
import { ReactNode } from 'react'
-import Navbar from '@/components/Navbar'
+import DashboardNavbar from '@/components/DashboardNavbar'
export default function AuthLayout({ children }: { children: ReactNode }) {
return (
<>
-
-
- {/* Could also add a private header here */}
- {children}
+
+
+
+
+
>
diff --git a/app/(public)/setup/page.tsx b/app/(public)/setup/page.tsx
index 637c66b..82014e5 100644
--- a/app/(public)/setup/page.tsx
+++ b/app/(public)/setup/page.tsx
@@ -14,11 +14,11 @@ export default function SetupPage() {
const res = await fetch('/api/setup', {
method: 'POST',
- body: JSON.stringify({ email, password, role }),
+ body: JSON.stringify({ username, email, password, role }),
});
if (res.ok) {
- await signIn('credentials', { email, password, callbackUrl: '/' });
+ await signIn('credentials', { username, email, password, callbackUrl: '/' });
} else {
alert('Error setting up user');
}
diff --git a/app/api/events/[eventId]/guests/add/route.ts b/app/api/events/[eventId]/guests/add/route.ts
index 2b5d8f5..8a1910b 100644
--- a/app/api/events/[eventId]/guests/add/route.ts
+++ b/app/api/events/[eventId]/guests/add/route.ts
@@ -3,14 +3,14 @@ import { mutations } from '@/lib/mutations'
export async function POST(req: NextRequest, { params }: { params: { eventId: string } }) {
const eventId = params.eventId
- const { guestBookEntryId } = await req.json()
+ const { guestId } = await req.json() // ← match client
- if (!eventId || !guestBookEntryId) {
- return NextResponse.json({ message: 'Missing eventId or guestBookEntryId' }, { status: 400 })
+ if (!eventId || !guestId) {
+ return NextResponse.json({ message: 'Missing eventId or guestId' }, { status: 400 })
}
try {
- const added = await mutations.addEventGuest({ eventId, guestBookEntryId })
+ const added = await mutations.addEventGuest({ eventId, guestBookEntryId: guestId }) // ← match expected arg
return NextResponse.json(added)
} catch (error) {
console.error('Add Event Guest Error:', error)
diff --git a/app/api/events/create/route.ts b/app/api/events/create/route.ts
new file mode 100644
index 0000000..d62a77d
--- /dev/null
+++ b/app/api/events/create/route.ts
@@ -0,0 +1,24 @@
+import { getServerSession } from 'next-auth'
+import { NextRequest, NextResponse } from 'next/server'
+import { authOptions } from '@/app/api/auth/[...nextauth]/route'
+import { prisma } from '@/lib/prisma'
+
+export async function POST(req: NextRequest) {
+ const session = await getServerSession(authOptions)
+ if (!session?.user?.id) {
+ return new NextResponse('Unauthorized', { status: 403 })
+ }
+
+ const { name, date, location } = await req.json()
+
+ const event = await prisma.event.create({
+ data: {
+ name,
+ date: date ? new Date(date) : undefined,
+ location,
+ creatorId: session.user.id,
+ },
+ })
+
+ return NextResponse.json(event)
+}
diff --git a/app/api/events/route.ts b/app/api/events/route.ts
deleted file mode 100644
index c25ca9d..0000000
--- a/app/api/events/route.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { getServerSession } from 'next-auth'
-import { authOptions } from '@/app/api/auth/[...nextauth]/route'
-import { mutations } from '@/lib/mutations';
-import { NextRequest, NextResponse } from 'next/server';
-
-export async function POST(req: NextRequest) {
- const session = await getServerSession(authOptions)
- if (!session?.user) return new NextResponse('Unauthorized', { status: 401 });
-
- const body = await req.json();
- const { name, date, location } = body;
-
- const event = await mutations.createEvent({
- name,
- date,
- location,
- creatorId: session.user.id,
- });
-
- return NextResponse.json(event)
-}
\ No newline at end of file
diff --git a/app/api/guestbook/search/route.ts b/app/api/guestbook/search/route.ts
new file mode 100644
index 0000000..4e1b265
--- /dev/null
+++ b/app/api/guestbook/search/route.ts
@@ -0,0 +1,29 @@
+// api/guestbook/search/route.ts
+import { prisma } from '@/lib/prisma'
+import { NextRequest, NextResponse } from 'next/server'
+
+export async function GET(req: NextRequest) {
+ const { searchParams } = new URL(req.url)
+ const query = searchParams.get('search') // ✅ Match the client-side key
+
+ if (!query || query.length < 2) {
+ return NextResponse.json([], { status: 200 })
+ }
+
+ const results = await prisma.guestBookEntry.findMany({
+ where: {
+ OR: [
+ { fName: { contains: query, mode: 'insensitive' } },
+ { lName: { contains: query, mode: 'insensitive' } },
+ { email: { contains: query, mode: 'insensitive' } },
+ ]
+ },
+ orderBy: [
+ { lName: 'asc' },
+ { fName: 'asc' },
+ ],
+ take: 10
+ })
+
+ return NextResponse.json(results)
+}
diff --git a/components/AddFirstGuestBookEntryClient.tsx b/components/AddFirstGuestBookEntryClient.tsx
new file mode 100644
index 0000000..89ec18c
--- /dev/null
+++ b/components/AddFirstGuestBookEntryClient.tsx
@@ -0,0 +1,24 @@
+'use client'
+import React, { useState } from 'react'
+import AddGuestBookEntryModal from './AddGuestBookEntryModal';
+import { handleAddGuest } from '@/lib/helper/addGuest';
+
+export default function AddFirstGuestBookEntryClient() {
+ const [isOpen, setIsOpen] = useState(false);
+ return (
+
+
You haven't added anyone to your guest book yet.
+
setIsOpen(true)}
+ >
+ Add your first guest
+
+
setIsOpen(false)}
+ onSubmit={handleAddGuest}
+ />
+
+ )
+}
diff --git a/components/AddGuestFromGuestBook.tsx b/components/AddGuestFromGuestBook.tsx
new file mode 100644
index 0000000..b9621ce
--- /dev/null
+++ b/components/AddGuestFromGuestBook.tsx
@@ -0,0 +1,113 @@
+'use client'
+
+import React, { useEffect, useState } from 'react'
+
+interface GuestBookEntry {
+ id: string
+ fName: string
+ lName: string
+ email?: string | null
+ side?: string
+}
+
+interface Props {
+ eventId: string
+ onGuestAdded?: (guestId: string) => void
+}
+
+export default function AddGuestFromGuestBook({ eventId, onGuestAdded }: Props) {
+ const [searchTerm, setSearchTerm] = useState('')
+ const [filteredGuests, setFilteredGuests] = useState
([])
+ const [allGuests, setAllGuests] = useState([])
+ const [isLoading, setIsLoading] = useState(false)
+ const [error, setError] = useState('')
+
+ useEffect(() => {
+ if (searchTerm.length < 2) {
+ setFilteredGuests([])
+ return
+ }
+
+ async function fetchGuests() {
+ setIsLoading(true)
+ try {
+ const res = await fetch(`/api/guestbook/search?search=${encodeURIComponent(searchTerm)}`)
+ const data = await res.json()
+ setAllGuests(data)
+ } catch (err) {
+ console.error(err)
+ setError('Failed to fetch guest book entries.')
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ fetchGuests()
+ }, [searchTerm])
+
+
+ useEffect(() => {
+ const filtered = allGuests.filter((guest) => {
+ const fullName = `${guest.fName} ${guest.lName}`.toLowerCase()
+ return fullName.includes(searchTerm.toLowerCase())
+ })
+ setFilteredGuests(filtered)
+ }, [searchTerm, allGuests])
+
+ async function handleAdd(guestId: string) {
+ try {
+ const res = await fetch(`/api/events/${eventId}/guests/add`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ guestId }),
+ })
+
+ if (!res.ok) {
+ const data = await res.json()
+ throw new Error(data.message || 'Failed to add guest to event')
+ }
+
+ if (onGuestAdded) onGuestAdded(guestId)
+
+ setSearchTerm('')
+ } catch (err) {
+ console.error(err)
+ setError('Could not add guest to event.')
+ }
+ }
+
+ return (
+
+
setSearchTerm(e.target.value)}
+ />
+
+ {searchTerm && (
+
+ {isLoading ? (
+ Loading...
+ ) : filteredGuests.length === 0 ? (
+ No matches found
+ ) : (
+ filteredGuests.map((guest) => (
+ handleAdd(guest.id)}
+ >
+ {guest.fName} {guest.lName} {guest.side ? `(${guest.side})` : ''}
+ {guest.email ? ` – ${guest.email}` : ''}
+
+ ))
+ )}
+
+ )}
+
+ {error &&
{error}
}
+
+ )
+}
diff --git a/components/CreateEventClient.tsx b/components/CreateEventClient.tsx
new file mode 100644
index 0000000..fc2e1cd
--- /dev/null
+++ b/components/CreateEventClient.tsx
@@ -0,0 +1,23 @@
+'use client'
+import React, { useState } from 'react'
+import CreateEventModal from './CreateEventModal'
+import { handleCreateEvent } from '@/lib/helper/createEvent';
+
+export default function CreateEventClient() {
+ const [isOpen, setIsOpen] = useState(false);
+ return (
+ <>
+ setIsOpen(true)}
+ >
+ Create Event
+
+ setIsOpen(false)}
+ onSubmit={handleCreateEvent}
+ />
+ >
+ )
+}
diff --git a/components/CreateEventModal.tsx b/components/CreateEventModal.tsx
new file mode 100644
index 0000000..b758936
--- /dev/null
+++ b/components/CreateEventModal.tsx
@@ -0,0 +1,93 @@
+'use client'
+
+import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react'
+import { Fragment, useState } from 'react'
+
+export default function CreateEventModal({
+ isOpen,
+ onClose,
+ onSubmit
+}: {
+ isOpen: boolean
+ onClose: () => void
+ onSubmit: (data: { name: string; date?: string; location?: string }) => void
+}) {
+ const [name, setName] = useState('')
+ const [date, setDate] = useState('')
+ const [location, setLocation] = useState('')
+
+ function handleSubmit(e: React.FormEvent) {
+ e.preventDefault()
+ onSubmit({ name, date, location })
+ setName('')
+ setDate('')
+ setLocation('')
+ onClose()
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ Create New Event
+
+
+
+
+
+
+ )
+}
diff --git a/components/DashboardNavbar.tsx b/components/DashboardNavbar.tsx
new file mode 100644
index 0000000..7878ea3
--- /dev/null
+++ b/components/DashboardNavbar.tsx
@@ -0,0 +1,19 @@
+'use client'
+import { useSession } from 'next-auth/react'
+import Link from 'next/link'
+import React from 'react'
+
+export default function DashboardNavbar() {
+ const session = useSession()
+ console.log(session)
+ return (
+
+
Hello, {session.data?.user.username}
+
+ Overview
+ Events
+ Guest Book
+
+
+ )
+}
diff --git a/components/EventInfoDisplay.tsx b/components/EventInfoDisplay.tsx
index 68cbce6..07afae5 100644
--- a/components/EventInfoDisplay.tsx
+++ b/components/EventInfoDisplay.tsx
@@ -3,6 +3,7 @@
'use client'
import React, { useState } from 'react'
+import AddGuestFromGuestBook from './AddGuestFromGuestBook'
interface Creator {
id: string
@@ -20,6 +21,7 @@ interface EventData {
createdAt: string
creator: Creator
guests: any[]
+ eventGuests: any[]
}
interface Props {
@@ -27,7 +29,11 @@ interface Props {
}
export default function EventInfoDisplay({ event }: Props) {
- const [isEditing, setIsEditing] = useState(false)
+ const [isEditing, setIsEditing] = useState(false);
+ const [showSearch, setShowSearch] = useState(false);
+
+ const eventGuests = event.eventGuests
+ console.log(eventGuests)
const [saving, setSaving] = useState(false)
const [error, setError] = useState('')
@@ -75,98 +81,160 @@ 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
+ // }
+ // }
+
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 && (
-
+
+
+
+
{event.name}
setIsEditing(prev => !prev)}
+ className="text-sm text-brand-primary-600 underline"
>
- {saving ? 'Saving...' : 'Save Changes'}
+ {isEditing ? 'Cancel' : 'Edit'}
- )}
+
+
+ {/* 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'}
+
+
+ )}
+
+
+
+
Guest List
+ setShowSearch(true)}
+ >
+ Add Guests
+
+
+ {showSearch &&
}
+
+
+
+
+ Vendors
+
)
}
diff --git a/lib/helper/addGuest.ts b/lib/helper/addGuest.ts
new file mode 100644
index 0000000..c916f95
--- /dev/null
+++ b/lib/helper/addGuest.ts
@@ -0,0 +1,26 @@
+export async function handleAddGuest(data: {
+ fName: string
+ lName: string
+ email?: string
+ phone?: string
+ address?: string
+ side: string
+ notes?: string
+}) {
+ try {
+ const res = await fetch('/api/guestbook/add', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data),
+ })
+
+ if (!res.ok) {
+ const { message } = await res.json()
+ throw new Error(message || 'Failed to add guest')
+ }
+
+ // Optionally: re-fetch entries or mutate state here
+ } catch (err) {
+ console.error('Error adding guest:', err)
+ }
+}
\ No newline at end of file
diff --git a/lib/helper/createEvent.ts b/lib/helper/createEvent.ts
new file mode 100644
index 0000000..cfcc675
--- /dev/null
+++ b/lib/helper/createEvent.ts
@@ -0,0 +1,23 @@
+export async function handleCreateEvent(data: {
+ name: string
+ date?: string
+ location?: string
+}) {
+ try {
+ const res = await fetch('/api/events/create', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data),
+ })
+
+ if (!res.ok) {
+ const { message } = await res.json()
+ throw new Error(message || 'Failed to create event')
+ }
+
+ // Optionally return or mutate data
+ return await res.json()
+ } catch (err) {
+ console.error('Error creating event:', err)
+ }
+}
diff --git a/lib/queries.ts b/lib/queries.ts
index e162dd1..98a5d3d 100644
--- a/lib/queries.ts
+++ b/lib/queries.ts
@@ -12,7 +12,7 @@ export const queries = {
}
}
})
- console.log(allEvents)
+
return allEvents;
},
@@ -65,7 +65,12 @@ export const queries = {
creator: {
select: { id: true, email: true, name: true, role: true },
},
- guests: true
+ guests: true,
+ eventGuests: {
+ include: {
+ guestBookEntry: true,
+ }
+ },
}
})
return event