guest book added
This commit is contained in:
127
components/AddGuestBookEntryModal.tsx
Normal file
127
components/AddGuestBookEntryModal.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
'use client'
|
||||
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react'
|
||||
import { Fragment, useState } from 'react'
|
||||
|
||||
export default function AddGuestBookEntryModal({ isOpen, onClose, onSubmit }: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onSubmit: (data: { fName: string, lName: string, email: string, phone?: string, address?: string, side: string, notes?: string }) => void
|
||||
}) {
|
||||
const [fName, setFName] = useState('');
|
||||
const [lName, setLName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [address, setAddress] = useState('');
|
||||
const [side, setSide] = useState('');
|
||||
const [notes, setNotes] = useState('');
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
onSubmit({ fName, lName, email, phone, address, side, notes })
|
||||
setFName('')
|
||||
setLName('')
|
||||
setEmail('')
|
||||
setPhone('')
|
||||
setAddress('')
|
||||
setSide('')
|
||||
setNotes('')
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-50" onClose={onClose}>
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="ease-out duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-150"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-30" />
|
||||
</TransitionChild>
|
||||
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="ease-out duration-200"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-150"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<DialogPanel className="w-full max-w-lg transform overflow-hidden rounded bg-white p-6 text-left shadow-xl transition-all">
|
||||
<DialogTitle className="text-lg font-bold mb-4">
|
||||
Add Guest Entry
|
||||
</DialogTitle>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="text"
|
||||
placeholder="First name"
|
||||
value={fName}
|
||||
onChange={e => setFName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="text"
|
||||
placeholder="Last name"
|
||||
value={lName}
|
||||
onChange={e => setLName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="tel"
|
||||
placeholder="Phone"
|
||||
value={phone}
|
||||
onChange={e => setPhone(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="text"
|
||||
placeholder="Address"
|
||||
value={address}
|
||||
onChange={e => setAddress(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="text"
|
||||
placeholder="Side (e.g., Bride/Groom)"
|
||||
value={side}
|
||||
onChange={e => setSide(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<textarea
|
||||
className="input input-bordered w-full"
|
||||
placeholder='Notes (e.g. Family/Friend of ...)'
|
||||
value={notes}
|
||||
onChange={e => setNotes(e.target.value)}
|
||||
/>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<button type="button" onClick={onClose} className="btn btn-outline">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
138
components/EditGuestBookEntryModal.tsx
Normal file
138
components/EditGuestBookEntryModal.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
'use client'
|
||||
|
||||
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react'
|
||||
import { Fragment, useState, useEffect } from 'react'
|
||||
|
||||
export default function EditGuestBookEntryModal({ isOpen, onClose, initialData, onSubmit }: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
initialData: {
|
||||
id: string
|
||||
fName: string
|
||||
lName: string
|
||||
email?: string
|
||||
phone?: string
|
||||
address?: string
|
||||
side?: string
|
||||
notes?: string
|
||||
}
|
||||
onSubmit: (updated: typeof initialData) => void
|
||||
}) {
|
||||
const [formData, setFormData] = useState(initialData)
|
||||
|
||||
useEffect(() => {
|
||||
setFormData(initialData)
|
||||
}, [initialData])
|
||||
|
||||
function handleChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[e.target.name]: e.target.value,
|
||||
}))
|
||||
}
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
onSubmit(formData)
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-50" onClose={onClose}>
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="ease-out duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-150"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-30" />
|
||||
</TransitionChild>
|
||||
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="ease-out duration-200"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-150"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<DialogPanel className="w-full max-w-lg transform overflow-hidden rounded bg-white p-6 text-left shadow-xl transition-all">
|
||||
<DialogTitle className="text-lg font-bold mb-4">
|
||||
Edit Guest Entry
|
||||
</DialogTitle>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder='First Name'
|
||||
value={formData.fName}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder='Last Name'
|
||||
value={formData.lName}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="email"
|
||||
placeholder='email'
|
||||
name="email"
|
||||
value={formData.email || ''}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="tel"
|
||||
name="phone"
|
||||
value={formData.phone || ''}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="text"
|
||||
name="address"
|
||||
value={formData.address || ''}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<input
|
||||
className="input input-bordered w-full"
|
||||
type="text"
|
||||
name="side"
|
||||
value={formData.side || ''}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<textarea
|
||||
className='input input-bordered w-full'
|
||||
name='notes'
|
||||
value={formData.notes || ''}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<button type="button" onClick={onClose} className="btn btn-outline">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
)
|
||||
}
|
||||
127
components/GuestBookList.tsx
Normal file
127
components/GuestBookList.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import EditGuestBookEntryModal from './EditGuestBookEntryModal'
|
||||
|
||||
interface GuestBookEntry {
|
||||
id: string
|
||||
fName: string
|
||||
lName: string
|
||||
side: string
|
||||
email?: string | null
|
||||
phone?: string | null
|
||||
address?: string | null
|
||||
notes?: string | null
|
||||
}
|
||||
|
||||
export default function GuestBookList({ entries }: { entries: GuestBookEntry[] }) {
|
||||
const [editingEntry, setEditingEntry] = useState<GuestBookEntry | null>(null)
|
||||
|
||||
function handleModalClose() {
|
||||
setEditingEntry(null)
|
||||
}
|
||||
|
||||
async function handleUpdate(updated: {
|
||||
id: string
|
||||
fName: string
|
||||
lName: string
|
||||
email?: string
|
||||
phone?: string
|
||||
address?: string
|
||||
side?: string
|
||||
notes?: string
|
||||
}) {
|
||||
try {
|
||||
const res = await fetch(`/api/guestbook/${updated.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
fName: updated.fName,
|
||||
lName: updated.lName,
|
||||
email: updated.email,
|
||||
phone: updated.phone,
|
||||
address: updated.address,
|
||||
side: updated.side,
|
||||
notes: updated.notes,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const { message } = await res.json()
|
||||
throw new Error(message || 'Update failed')
|
||||
}
|
||||
|
||||
// Optional: trigger a state update/refetch if needed
|
||||
setEditingEntry(null)
|
||||
} catch (err) {
|
||||
console.error('Update failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
<div className='overflow-hidden rounded-xl'>
|
||||
<table className='table-auto w-full mb-16 p-4'>
|
||||
<thead className='bg-text text-background'>
|
||||
<tr className='text-left'>
|
||||
<th className='px-4 py-2'>Name</th>
|
||||
<th className='px-4 py-2'>Email</th>
|
||||
<th className='px-4 py-2'>Phone</th>
|
||||
<th className='px-4 py-2'>Address</th>
|
||||
<th className='px-4 py-2'>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className=''>
|
||||
{entries.map(entry => (
|
||||
<tr
|
||||
key={entry.id}
|
||||
className=''
|
||||
>
|
||||
<td className='border border-gray-300 px-4 py-2'>{entry.fName + ' ' + entry.lName} <span className='text-sm text-gray-600'>(Side: {entry.side})</span></td>
|
||||
<td className='border border-gray-300 px-4 py-2'>{entry.email || 'N/A'}</td>
|
||||
<td className='border border-gray-300 px-4 py-2'>{entry.phone || 'N/A'}</td>
|
||||
<td className='border border-gray-300 px-4 py-2'>{entry.address || 'N/A'}</td>
|
||||
<td className='border border-gray-300 px-4 py-2'>{entry.notes || 'N/A'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='max-w-3xl space-y-4 mx-auto'>
|
||||
{entries.map(entry => (
|
||||
<div key={entry.id} className='p-4 border rounded shadow-sm'>
|
||||
<h2 className='font-semibold text-lg'>{entry.fName} {entry.lName}</h2>
|
||||
<p className='text-sm text-gray-600'>Side: {entry.side}</p>
|
||||
<p>Email: {entry.email || 'N/A'}</p>
|
||||
<p>Phone: {entry.phone || 'N/A'}</p>
|
||||
<p>Address: {entry.address || 'N/A'}</p>
|
||||
<p>Notes: {entry.notes || 'N/A'}</p>
|
||||
<button
|
||||
className='text-blue-600 underline text-sm mt-2'
|
||||
onClick={() => setEditingEntry(entry)}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{editingEntry && (
|
||||
<EditGuestBookEntryModal
|
||||
isOpen={!!editingEntry}
|
||||
onClose={handleModalClose}
|
||||
initialData={{
|
||||
id: editingEntry.id,
|
||||
fName: `${editingEntry.fName}`,
|
||||
lName: `${editingEntry.lName}`,
|
||||
email: editingEntry.email || '',
|
||||
phone: editingEntry.phone || '',
|
||||
address: editingEntry.address || '',
|
||||
side: editingEntry.side,
|
||||
notes: editingEntry.notes || '',
|
||||
}}
|
||||
onSubmit={handleUpdate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
64
components/GuestBookPageClient.tsx
Normal file
64
components/GuestBookPageClient.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import AddGuestBookEntryModal from '@/components/AddGuestBookEntryModal'
|
||||
import GuestBookList from '@/components/GuestBookList'
|
||||
|
||||
interface GuestBookEntry {
|
||||
id: string
|
||||
fName: string
|
||||
lName: string
|
||||
side: string
|
||||
email?: string | null
|
||||
phone?: string | null
|
||||
address?: string | null
|
||||
notes?: string | null
|
||||
}
|
||||
|
||||
export default function GuestBookPageClient({ entries }: { entries: GuestBookEntry[] }) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='max-w-6xl mx-auto mt-8 space-y-4'>
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className='text-2xl font-bold'>Guest Book</h1>
|
||||
<button onClick={() => setIsOpen(true)} className="btn btn-primary">Add Guest</button>
|
||||
</div>
|
||||
|
||||
<GuestBookList entries={entries} />
|
||||
|
||||
<AddGuestBookEntryModal
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
onSubmit={handleAddGuest}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user