venues and ui changes

This commit is contained in:
2025-07-24 09:42:57 -04:00
parent 049def6886
commit 27590f9509
24 changed files with 757 additions and 164 deletions

View File

@@ -71,6 +71,23 @@ My goal for this project is to be an all-in-one self hosted event planner for ma
- Inline editing with live preview - Inline editing with live preview
- Task list per event with due dates & completion toggle - Task list per event with due dates & completion toggle
#### 7.24.25 Notes and Tasks
**Major Update**
- Added Venues
- Venues are significant locations which can be added to an event.
| Column | Default | Required | Type |
| --- | --- | --- | --- |
| Name | null | Yes | String |
| Address | null | Yes | String |
| City/Town | null | Yes | String |
| State | null | Yes | String |
| Postal/Area Code | null | Yes | String |
| Country | 'United States' | Yes | String |
| Phone | null | No | String |
| Email | null | No | String |
- UI changes to Dashboard to make use of Shadcn components
## Getting Started ## Getting Started
This app is fully deployable with Docker or runnable in development with Node. This app is fully deployable with Docker or runnable in development with Node.

View File

@@ -1,20 +1,21 @@
import AddFirstGuestBookEntryClient from '@/components/AddFirstGuestBookEntryClient' import DashboardEvents from '@/components/dashboard/DashboardEvents'
import CreateEventClient from '@/components/CreateEventClient' import DashboardGuestBook from '@/components/dashboard/DashboardGuestBook'
import EventInfoQuickView from '@/components/EventInfoQuickView'
import GuestBookQuickView from '@/components/GuestBookQuickView'
import { queries } from '@/lib/queries' import { queries } from '@/lib/queries'
import Link from 'next/link'
import React from 'react' import React from 'react'
export default async function DashboardPage() { export default async function DashboardPage() {
const events = await queries.fetchEvents(); const events = await queries.fetchQuickViewEvents();
const guestBookData = await queries.fetchGuestBookEntries({ takeOnlyRecent: 5 }); const guestBookData = await queries.fetchGuestBookEntries({ takeOnlyRecent: 5 });
const guestBookEntries = Array.isArray(guestBookData) ? guestBookData : guestBookData.entries; const guestBookEntries = Array.isArray(guestBookData) ? guestBookData : guestBookData.entries;
return ( return (
<>
<div className='grid grid-cols-1 md:grid-cols-7 gap-4'> <div className='grid grid-cols-1 md:grid-cols-7 gap-4'>
<div className='md:col-span-5 md:row-span-3 bg-[#00000008] rounded-xl p-4 md:p-6 relative'> <DashboardEvents events={events} />
<DashboardGuestBook guestBookEntries={guestBookEntries} />
{/* <div className='md:col-span-5 md:row-span-3 bg-[#00000008] rounded-xl p-4 md:p-6 relative'>
<div> <div>
<div className='w-full flex items-center justify-between'> <div className='w-full flex items-center justify-between'>
<h2 className='text-lg font-semibold py-4'>Your Events</h2> <h2 className='text-lg font-semibold py-4'>Your Events</h2>
@@ -49,7 +50,8 @@ export default async function DashboardPage() {
<GuestBookQuickView key={entry.id} {...entry} /> <GuestBookQuickView key={entry.id} {...entry} />
))} ))}
</div> </div>
</div> */}
</div> </div>
</div> </>
) )
} }

View File

@@ -1,13 +0,0 @@
import LocationsTable from '@/components/tables/LocationsTable'
import { queries } from '@/lib/queries'
import React from 'react'
export default async function LocationsPage() {
const eventLocations = await queries.fetchAllLocations()
return (
<div>
<LocationsTable eventLocations={eventLocations} />
</div>
)
}

View File

@@ -0,0 +1,13 @@
import VenuesTable from '@/components/tables/VenuesTable'
import { queries } from '@/lib/queries'
import React from 'react'
export default async function LocationsPage() {
const venues = await queries.fetchAllLocations()
return (
<div>
<VenuesTable eventLocations={venues} />
</div>
)
}

View File

@@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/app/api/auth/[...nextauth]/route'
export async function GET(
req: NextRequest,
{ params }: { params: { eventId: string } }
) {
const session = await getServerSession(authOptions)
if (!session?.user) {
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 })
}
try {
const event = await prisma.event.findUnique({
where: { id: params.eventId },
include: {
creator: {
select: { id: true, name: true, email: true, role: true }
},
venue: true,
todos: {
orderBy: [
{ complete: 'asc' },
{ dueDate: 'asc' }
]
},
eventGuests: {
include: {
guestBookEntry: true
}
}
}
})
if (!event) {
return NextResponse.json({ message: 'Event not found' }, { status: 404 })
}
return NextResponse.json(event)
} catch (err) {
console.error(err)
return NextResponse.json({ message: 'Error fetching event' }, { status: 500 })
}
}

View File

@@ -1,27 +1,29 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server'
import { mutations } from '@/lib/mutations'; import { prisma } from '@/lib/prisma'
import { getServerSession } from 'next-auth'; import { getServerSession } from 'next-auth'
import { authOptions } from '../../auth/[...nextauth]/route'; import { authOptions } from '@/app/api/auth/[...nextauth]/route'
export async function PATCH(req: NextRequest, { params }: { params: { eventId: string } }) { export async function PATCH(req: NextRequest, { params }: { params: { eventId: string } }) {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions)
if (!session?.user) { if (!session?.user) {
return new NextResponse('Unauthorized', { status: 401 }); return NextResponse.json({ message: 'Unauthorized' }, { status: 401 })
} }
const eventId = params.eventId; const body = await req.json()
const body = await req.json();
try { try {
const updated = await mutations.updateEvent(eventId, { const updated = await prisma.event.update({
where: { id: params.eventId },
data: {
name: body.name, name: body.name,
date: body.date, date: body.date ? new Date(body.date) : undefined,
location: body.location, venueId: body.venueId || null,
notes: body.notes, },
}); })
return NextResponse.json(updated);
} catch (error) { return NextResponse.json(updated)
console.error('[PATCH EVENT]', error); } catch (err) {
return new NextResponse('Failed to update event', { status: 500 }); console.error(err)
return NextResponse.json({ message: 'Error updating event' }, { status: 500 })
} }
} }

View File

@@ -0,0 +1,33 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/app/api/auth/[...nextauth]/route'
export async function POST(req: NextRequest) {
const session = await getServerSession(authOptions)
if (!session?.user) {
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 })
}
const body = await req.json()
try {
const venue = await prisma.venue.create({
data: {
name: body.name,
address: body.address,
city: body.city,
state: body.state,
postalCode: body.postalCode,
country: body.country,
phone: body.phone || undefined,
email: body.email || undefined
}
})
return NextResponse.json(venue)
} catch (err) {
console.error(err)
return NextResponse.json({ message: 'Error creating venue' }, { status: 500 })
}
}

View File

@@ -0,0 +1,12 @@
import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET() {
try {
const venues = await prisma.venue.findMany()
return NextResponse.json(venues)
} catch (error) {
console.error('Failed to fetch venues:', error)
return new NextResponse('Failed to fetch venues', { status: 500 })
}
}

View File

@@ -1,15 +1,22 @@
import Link from 'next/link' import Link from 'next/link'
import React from 'react' import React from 'react'
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from './ui/card'
export default function EventInfoQuickView(props: EventProps) { export default function EventInfoQuickView(props: QucikEventProps) {
return ( return (
<Link href={`/events/${props.id}`} > <Link href={`/events/${props.id}`}>
<div className='hover:cursor-pointer rounded-lg p-2 bg-brand-primary-900 hover:bg-brand-primary-800 transition-colors duration-200'> <Card className='bg-brand-primary-900 hover:bg-brand-primary-800 transition-colors duration-200'>
<h3 className='text-md font-semibold'>{props.name}</h3> <CardHeader>
<CardTitle>{props.name}</CardTitle>
</CardHeader>
<CardContent>
<p>Date: {props.date ? props.date.toDateString() : 'null'}</p> <p>Date: {props.date ? props.date.toDateString() : 'null'}</p>
<p>Location: {props.location ? props.location.name : 'null'}</p> <p>Location: {props.venue ? props.venue.name : 'null'}</p>
</CardContent>
<CardFooter>
<p className='text-xs mt-2'>Created By: {props.creator.username}</p> <p className='text-xs mt-2'>Created By: {props.creator.username}</p>
</div> </CardFooter>
</Card>
</Link> </Link>
) )
} }

View File

@@ -48,7 +48,7 @@ const data = {
}, },
{ {
title: "Locations", title: "Locations",
url: "/locations", url: "/venues",
icon: IconBuildingArch, icon: IconBuildingArch,
}, },
], ],

View File

@@ -0,0 +1,51 @@
import React from 'react'
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '../ui/card'
import { Button } from '../ui/button'
import Link from 'next/link'
import EventInfoQuickView from '../EventInfoQuickView'
interface EventsProps {
events: {
id: string
name: string
date?: Date | null
creator: {
id: string,
username: string
},
venue?: {
name: string
} | null
}[]
}
export default function DashboardEvents({events}: EventsProps) {
return (
<Card className='md:col-span-5 pb-3'>
<CardHeader>
<div className='flex justify-between items-center'>
<CardTitle>
Your Events
</CardTitle>
<Button
className='bg-brand-primary-600 hover:bg-brand-primary-400'
>
Create Event
</Button>
</div>
</CardHeader>
<CardContent>
<div className='grid md:grid-cols-3'>
{events.map((item) => (
<EventInfoQuickView key={item.id} {...item} />
))}
</div>
</CardContent>
<CardFooter>
<div className='text-right w-full text-sm'>
<Link href={'/events'}>View all</Link>
</div>
</CardFooter>
</Card>
)
}

View File

@@ -0,0 +1,41 @@
import React from 'react'
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'
import Link from 'next/link'
import AddFirstGuestBookEntryClient from '../AddFirstGuestBookEntryClient'
import GuestBookQuickView from '../GuestBookQuickView'
interface GuestBookEntryProps {
guestBookEntries: {
id: string
fName: string
lName: string
email?: string | null
phone?: string | null
address?: string | null
notes?: string | null
side: string
congratulated?: boolean | null
createdAt: Date
}[]
}
export default function DashboardGuestBook(guestBookEntries: GuestBookEntryProps) {
return (
<Card className='md:col-start-6 col-span-2 row-span-2'>
<CardHeader>
<div className='flex justify-between items-center'>
<CardTitle>
Guest Book
</CardTitle>
<Link href={'/guest-book'}>View All</Link>
</div>
</CardHeader>
<CardContent className='space-y-2'>
{!guestBookEntries.guestBookEntries.length && <AddFirstGuestBookEntryClient />}
{guestBookEntries.guestBookEntries.map(entry => (
<GuestBookQuickView key={entry.id} {...entry} />
))}
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,30 @@
'use client'
import React from 'react'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui/dialog'
export default function DialogWrapper({
title,
description,
form,
open,
onOpenChange,
}: {
title: string,
description?: string,
form: React.ReactNode,
open: boolean,
onOpenChange: (open: boolean) => void
}) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent onOpenAutoFocus={(e) => e.preventDefault()}>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<div className="space-y-4">{form}</div>
</DialogContent>
</Dialog>
)
}

View File

@@ -22,8 +22,6 @@ export default function EventDashboard({ event }: Props) {
} }
} }
console.log(todos)
return ( return (
<div className='grid grid-cols-1 lg:grid-cols-3 gap-6'> <div className='grid grid-cols-1 lg:grid-cols-3 gap-6'>
<EventInfo event={event} /> <EventInfo event={event} />

View File

@@ -4,6 +4,8 @@ import { Card, CardContent } from '../ui/card'
import { getDaysUntilEvent } from '@/lib/helper/getDaysUntilEvent' import { getDaysUntilEvent } from '@/lib/helper/getDaysUntilEvent'
import { Button } from '../ui/button' import { Button } from '../ui/button'
import EventNotesEditor from '../EventNotesEditor' import EventNotesEditor from '../EventNotesEditor'
import DialogWrapper from '../dialogs/DialogWrapper'
import EditEventForm from '../forms/EditEventForm'
interface EventProps { interface EventProps {
event: EventData event: EventData
@@ -11,6 +13,7 @@ interface EventProps {
export default function EventInfo({ event }: EventProps) { export default function EventInfo({ event }: EventProps) {
const [daysLeft, setDaysLeft] = useState<number | null>(null) const [daysLeft, setDaysLeft] = useState<number | null>(null)
const [isDialogOpen, setIsDialogOpen] = useState(false)
useEffect(() => { useEffect(() => {
if (event.date) { if (event.date) {
@@ -19,6 +22,19 @@ export default function EventInfo({ event }: EventProps) {
} }
}, [event.date]) }, [event.date])
async function refreshEventData(eventId: string) {
try {
const res = await fetch(`/api/events/${eventId}/fetch`)
if (!res.ok) throw new Error('Failed to fetch event data')
const data = await res.json()
return data
} catch (err) {
console.error('Failed to refresh event data:', err)
return null
}
}
return ( return (
<div className='lg:col-span-1 space-y-4'> <div className='lg:col-span-1 space-y-4'>
<Card className='py-0'> <Card className='py-0'>
@@ -26,13 +42,18 @@ export default function EventInfo({ event }: EventProps) {
<h2 className='text-xl font-semibold'>Event Info</h2> <h2 className='text-xl font-semibold'>Event Info</h2>
<p className='text-sm mt-2'>Name: {event.name}</p> <p className='text-sm mt-2'>Name: {event.name}</p>
<p className='text-sm'>Date: {event.date ? event.date.toDateString() : 'Upcoming'}</p> <p className='text-sm'>Date: {event.date ? event.date.toDateString() : 'Upcoming'}</p>
<p className='text-sm'>Location: {event.location ? event.location.name : 'No location yet'}</p> <p className='text-sm'>Venue: {event.venue ? event.venue.name : 'No location yet'}</p>
{daysLeft !== null && ( {daysLeft !== null && (
<p className='text-sm mt-2 font-medium text-brand-primary-400'> <p className='text-sm mt-2 font-medium text-brand-primary-400'>
{daysLeft} days until this event! {daysLeft} days until this event!
</p> </p>
)} )}
<Button className="mt-4 w-full bg-brand-primary-600 hover:bg-brand-primary-400">Edit Event</Button> <Button
className="mt-4 w-full bg-brand-primary-600 hover:bg-brand-primary-400"
onClick={() => setIsDialogOpen(true)}
>
Edit Event
</Button>
</CardContent> </CardContent>
</Card> </Card>
<Card className='py-0'> <Card className='py-0'>
@@ -45,6 +66,20 @@ export default function EventInfo({ event }: EventProps) {
/> />
</CardContent> </CardContent>
</Card> </Card>
<DialogWrapper
open={isDialogOpen}
onOpenChange={setIsDialogOpen}
title="Edit Event"
description="Update the event details"
form={
<EditEventForm event={event} onSuccess={async () => {
await refreshEventData(event.id)
setIsDialogOpen(false)
}}
/>
}
/>
</div> </div>
) )
} }

View File

@@ -0,0 +1,113 @@
'use client'
import React, { useState } from 'react'
import { Input } from '../ui/input'
import { Button } from '../ui/button'
import { Label } from '../ui/label'
import { toast } from 'sonner'
interface CreateVenueFormProps {
onSuccess?: () => void
}
export default function CreateVenueForm({ onSuccess }: CreateVenueFormProps) {
const [formData, setFormData] = useState({
name: '',
address: '',
city: '',
state: '',
postalCode: '',
country: 'United States',
phone: '',
email: ''
})
const [loading, setLoading] = useState(false)
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
if (!formData.name || !formData.address || !formData.city || !formData.state || !formData.postalCode) {
toast.error('Please fill in all required fields')
return
}
setLoading(true)
try {
const res = await fetch('/api/venues/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
})
if (!res.ok) throw new Error('Failed to create venue')
toast.success('Venue created!')
setFormData({
name: '',
address: '',
city: '',
state: '',
postalCode: '',
country: 'United States',
phone: '',
email: ''
})
if (onSuccess) onSuccess()
} catch (err) {
console.error(err)
toast.error('Something went wrong')
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
{[
{ label: 'Name', name: 'name', required: true },
{ label: 'Address', name: 'address', required: true },
{ label: 'City', name: 'city', required: true },
{ label: 'State', name: 'state', required: true },
{ label: 'Postal Code', name: 'postalCode', required: true },
{ label: 'Country', name: 'country', required: false },
{ label: 'Phone', name: 'phone', required: false },
{ label: 'Email', name: 'email', required: false }
].map(field => (
<div key={field.name} className="space-y-1">
<Label htmlFor={field.name}>
{field.label}{field.required && ' *'}
</Label>
<Input
id={field.name}
name={field.name}
value={formData[field.name as keyof typeof formData]}
onChange={handleChange}
required={field.required}
/>
</div>
))}
{/* <div className="space-y-1">
<Label htmlFor='name'>
Name *
</Label>
<Input
id='name'
name='name'
value={formData[field.name as keyof typeof formData]}
onChange={handleChange}
required={field.required}
/>
</div> */}
<Button type="submit" disabled={loading}>
{loading ? 'Creating...' : 'Create Venue'}
</Button>
</form>
)
}

View File

@@ -0,0 +1,93 @@
'use client'
import React, { useState, useEffect } from 'react'
import { Input } from '../ui/input'
import { Label } from '../ui/label'
import { Button } from '../ui/button'
import { toast } from 'sonner'
interface EditEventFormProps {
event: EventData
onSuccess?: () => void
}
export default function EditEventForm({ event, onSuccess }: EditEventFormProps) {
const [formData, setFormData] = useState({
name: event.name,
date: event.date?.toISOString().substring(0, 10) || '',
venueId: event.venue?.id || ''
})
const [venues, setVenues] = useState<{ id: string; name: string }[]>([])
const [loading, setLoading] = useState(false)
useEffect(() => {
async function fetchVenues() {
const res = await fetch('/api/venues/fetch')
const data = await res.json()
setVenues(data)
}
fetchVenues()
}, [])
function handleChange(e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setLoading(true)
try {
const res = await fetch(`/api/events/${event.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
})
if (!res.ok) throw new Error('Failed to update event')
toast.success('Event updated!')
if (onSuccess) onSuccess()
} catch (err) {
console.error(err)
toast.error('Something went wrong')
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<Label htmlFor="name">Event Name</Label>
<Input id="name" name="name" value={formData.name} onChange={handleChange} required />
</div>
<div>
<Label htmlFor="date">Event Date</Label>
<Input id="date" name="date" type="date" value={formData.date} onChange={handleChange} />
</div>
<div>
<Label htmlFor="venueId">Venue</Label>
<select
id="venueId"
name="venueId"
className="input input-bordered w-full"
value={formData.venueId}
onChange={handleChange}
>
<option value="">No venue</option>
{venues.map(v => (
<option key={v.id} value={v.id}>
{v.name}
</option>
))}
</select>
</div>
<Button type="submit" disabled={loading}>
{loading ? 'Saving...' : 'Save Changes'}
</Button>
</form>
)
}

View File

@@ -1,65 +0,0 @@
'use client'
import { ColumnDef } from '@tanstack/react-table'
import { DataTable } from './DataTable'
interface LocationRow {
id: string
name: string
address: string
city: string
state: string
postalCode: string
country: string
phone?: string | null
email?: string | null
}
interface Props {
eventLocations: LocationRow[]
}
const columns: ColumnDef<LocationRow>[] = [
{
accessorKey: 'name',
header: 'Name'
},
{
accessorKey: 'address',
header: 'Address'
},
{
accessorKey: 'city',
header: 'City'
},
{
accessorKey: 'state',
header: 'State'
},
{
accessorKey: 'postalCode',
header: 'Postal Code'
},
{
accessorKey: 'country',
header: 'Country'
},
{
accessorKey: 'phone',
header: 'Phone',
cell: ({ row }) => row.original.phone || '—'
},
{
accessorKey: 'email',
header: 'Email',
cell: ({ row }) => row.original.email || '—'
}
]
export default function LocationsTable({ eventLocations }: Props) {
return (
<div className="mt-4">
<DataTable columns={columns} data={eventLocations} />
</div>
)
}

View File

@@ -0,0 +1,99 @@
'use client'
import { ColumnDef } from '@tanstack/react-table'
import { DataTable } from './DataTable'
import { Button } from '../ui/button'
import DialogWrapper from '../dialogs/DialogWrapper'
import CreateVenueForm from '../forms/CreateVenueForm'
import { useState } from 'react'
import { fetchVenuesClient } from '@/lib/helper/fetchVenues'
interface LocationRow {
id: string
name: string
address: string
city: string
state: string
postalCode: string
country: string
phone?: string | null
email?: string | null
}
interface Props {
eventLocations: LocationRow[]
}
const columns: ColumnDef<LocationRow>[] = [
{
accessorKey: 'name',
header: 'Name'
},
{
accessorKey: 'address',
header: 'Address'
},
{
accessorKey: 'city',
header: 'City'
},
{
accessorKey: 'state',
header: 'State'
},
{
accessorKey: 'postalCode',
header: 'Postal Code'
},
{
accessorKey: 'country',
header: 'Country'
},
{
accessorKey: 'phone',
header: 'Phone',
cell: ({ row }) => row.original.phone || '—'
},
{
accessorKey: 'email',
header: 'Email',
cell: ({ row }) => row.original.email || '—'
}
]
export default function VenuesTable({ eventLocations }: Props) {
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [venues, setVenues] = useState<LocationRow[]>(eventLocations)
async function refreshVenues() {
try {
const updated = await fetchVenuesClient()
setVenues(updated)
} catch (err) {
console.error('Failed to refresh venues:', err)
}
}
return (
<div className="space-y-4">
<Button
variant={'outline'}
onClick={() => setIsDialogOpen(true)}
>
Create Venue
</Button>
<DataTable columns={columns} data={venues} />
<DialogWrapper
title="Create a New Venue"
description="Enter the Venue information below"
open={isDialogOpen}
onOpenChange={setIsDialogOpen}
form={<CreateVenueForm onSuccess={async () => {
await refreshVenues()
setIsDialogOpen(false)
}}
/>}
/>
</div>
)
}

View File

@@ -0,0 +1,5 @@
export async function fetchVenuesClient() {
const res = await fetch('/api/venues/fetch', { cache: 'no-store' }) // ensure no stale cache
if (!res.ok) throw new Error('Failed to fetch venues')
return res.json()
}

View File

@@ -10,13 +10,37 @@ export const queries = {
username: true username: true
} }
}, },
location: true venue: true
}, },
}) })
return allEvents; return allEvents;
}, },
async fetchQuickViewEvents() {
const events = await prisma.event.findMany({
take: 3,
select: {
id: true,
name: true,
date: true,
creator: {
select: {
id: true,
username: true,
}
},
venue: {
select: {
name: true,
},
},
}
})
return events
},
async fetchEventGuests(eventId: string) { async fetchEventGuests(eventId: string) {
return await prisma.eventGuest.findMany({ return await prisma.eventGuest.findMany({
where: { eventId }, where: { eventId },
@@ -78,7 +102,7 @@ export const queries = {
{ dueDate: 'asc' }, { dueDate: 'asc' },
], ],
}, },
location: true venue: true
} }
}) })
return event return event
@@ -136,7 +160,7 @@ export const queries = {
}, },
async fetchAllLocations() { async fetchAllLocations() {
return await prisma.location.findMany() return await prisma.venue.findMany()
}, },
} }

View File

@@ -0,0 +1,34 @@
/*
Warnings:
- You are about to drop the column `locationid` on the `Event` table. All the data in the column will be lost.
- You are about to drop the `Location` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "Event" DROP CONSTRAINT "Event_locationid_fkey";
-- AlterTable
ALTER TABLE "Event" DROP COLUMN "locationid",
ADD COLUMN "venueId" TEXT;
-- DropTable
DROP TABLE "Location";
-- CreateTable
CREATE TABLE "Venue" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"address" TEXT NOT NULL,
"city" TEXT NOT NULL,
"state" TEXT NOT NULL,
"postalCode" TEXT NOT NULL,
"country" TEXT NOT NULL DEFAULT 'United States',
"phone" TEXT,
"email" TEXT,
CONSTRAINT "Venue_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "Event" ADD CONSTRAINT "Event_venueId_fkey" FOREIGN KEY ("venueId") REFERENCES "Venue"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -30,8 +30,8 @@ model Event {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
date DateTime? date DateTime?
location Location? @relation(fields: [locationid], references: [id]) venue Venue? @relation(fields: [venueId], references: [id])
locationid String? venueId String?
creator User @relation("EventCreator", fields: [creatorId], references: [id]) creator User @relation("EventCreator", fields: [creatorId], references: [id])
creatorId String creatorId String
guests Guest[] guests Guest[]
@@ -43,7 +43,7 @@ model Event {
FileUpload FileUpload[] FileUpload FileUpload[]
} }
model Location { model Venue {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
address String address String
@@ -53,6 +53,7 @@ model Location {
country String @default("United States") country String @default("United States")
phone String? phone String?
email String? email String?
createdAt DateTime @default(now())
Event Event[] Event Event[]
} }
@@ -64,6 +65,7 @@ model Guest {
name String name String
email String? email String?
rsvp RsvpStatus @default(PENDING) rsvp RsvpStatus @default(PENDING)
// attended RsvpStatus @default(PENDING)
} }
enum RsvpStatus { enum RsvpStatus {

17
types.d.ts vendored
View File

@@ -22,6 +22,19 @@ interface EventProps {
key: string; key: string;
} }
interface QucikEventProps {
id: string
name: string
date?: Date | null
creator: {
id: string
username: string
},
venue?: {
name: string
} | null
}
interface Creator { interface Creator {
id: string id: string
email: string email: string
@@ -44,7 +57,7 @@ interface EventData {
id: string id: string
name: string name: string
date: Date | null date: Date | null
location: EventLocation | null venue: Venue | null
creatorId: string creatorId: string
createdAt: string createdAt: string
creator: Creator creator: Creator
@@ -73,7 +86,7 @@ type User = {
role: 'COUPLE' | 'PLANNER' | 'GUEST' role: 'COUPLE' | 'PLANNER' | 'GUEST'
} }
interface EventLocation { interface Venue {
id: string id: string
name: string name: string
address: string address: string