Enhance guest management features in GuestListTable
Some checks failed
Build and Push Docker Images / build-frontend (push) Failing after 52s
Build and Push Docker Images / changes (push) Successful in 5s
Build and Push Docker Images / build-backend (push) Has been skipped

Added functionality for adding, editing, deleting, and copying guests' details, along with filtering, exporting, and sending invitations. Improved user interactions with dialog handling and introduced error handling for better usability. Integrated GuestsList into the event detail page.
This commit is contained in:
2025-03-16 10:13:41 +01:00
parent 4e66b22bae
commit c231f41e9c
3 changed files with 663 additions and 154 deletions

View File

@@ -26,6 +26,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useEventThemes } from "@/context/event-theme-context"; import { useEventThemes } from "@/context/event-theme-context";
import { getServerFileUrl } from "@/lib/utils"; import { getServerFileUrl } from "@/lib/utils";
import GuestsList from "@/components/guests/guests-list";
export default function EventDetailPage() { export default function EventDetailPage() {
const { slug } = useParams<{ slug: string }>(); const { slug } = useParams<{ slug: string }>();
@@ -87,7 +88,7 @@ export default function EventDetailPage() {
} }
return ( return (
<div> <div className="space-y-8">
<header className="mb-8 flex justify-between items-start"> <header className="mb-8 flex justify-between items-start">
<div> <div>
<h1 className="text-4xl font-bold tracking-tight">{event.title}</h1> <h1 className="text-4xl font-bold tracking-tight">{event.title}</h1>
@@ -347,6 +348,7 @@ export default function EventDetailPage() {
)} )}
</div> </div>
</div> </div>
<GuestsList event={event} />
</div> </div>
); );
} }

View File

@@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { import {
Table, Table,
TableBody, TableBody,
@@ -11,6 +11,7 @@ import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -18,143 +19,462 @@ import { Badge } from "@/components/ui/badge";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { MoreHorizontal, Plus, Search, Filter, Send, Copy } from "lucide-react"; import {
AlertTriangle,
Copy,
Download,
Filter,
MoreHorizontal,
Plus,
Search,
Send,
} from "lucide-react";
import { useGuests } from "@/context/guest-context";
import {
EventResponse,
EventThemeResponse,
GuestCreate,
GuestRead,
GuestStatus,
GuestUpdate,
} from "@/client/types.gen";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { toast } from "sonner";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { useAuth } from "@/context/auth-context";
const GuestListTable = () => { // Helper to generate a random invitation code
const [open, setOpen] = useState(false); const generateInvitationCode = (fullName: string): string => {
const namePart = fullName
.replace(/[^A-Za-z]/g, "")
.substring(0, 6)
.toUpperCase();
const randomPart = Math.floor(Math.random() * 1000)
.toString()
.padStart(5, "0");
return `${namePart}${randomPart}`;
};
type GuestListTableProps = {
event: EventResponse;
};
const GuestListTable = ({ event }: GuestListTableProps) => {
// State
const [addGuestOpen, setAddGuestOpen] = useState(false);
const [editGuestOpen, setEditGuestOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [currentGuest, setCurrentGuest] = useState<GuestRead | null>(null);
// Mock data // Form state
const guests = [ const initialState = {
{ full_name: "",
id: "1", email: "",
fullName: "John Smith", phone: "",
email: "john.smith@example.com", max_additional_guests: 0,
phone: "+1 555-123-4567", dietary_restrictions: "",
invitationCode: "JSMITH21", notes: "",
status: "CONFIRMED", can_bring_guests: true,
additionalGuests: 2,
},
{
id: "2",
fullName: "Emma Johnson",
email: "emma.j@example.com",
phone: "+1 555-987-6543",
invitationCode: "EJOHN45",
status: "INVITED",
additionalGuests: 0,
},
{
id: "3",
fullName: "Michael Brown",
email: "mbrown@example.com",
phone: "+1 555-555-5555",
invitationCode: "MBROWN3",
status: "DECLINED",
additionalGuests: 0,
},
{
id: "4",
fullName: "Olivia Davis",
email: "olivia.d@example.com",
phone: "+1 555-111-2222",
invitationCode: "ODAVIS7",
status: "PENDING",
additionalGuests: 1,
},
{
id: "5",
fullName: "William Wilson",
email: "will.w@example.com",
phone: "+1 555-333-4444",
invitationCode: "WWILSON",
status: "CONFIRMED",
additionalGuests: 3,
},
];
const getStatusBadge = (status) => {
const statusStyles = {
INVITED: "bg-blue-100 text-blue-800",
PENDING: "bg-yellow-100 text-yellow-800",
CONFIRMED: "bg-green-100 text-green-800",
DECLINED: "bg-red-100 text-red-800",
WAITLISTED: "bg-purple-100 text-purple-800",
CANCELLED: "bg-gray-100 text-gray-800",
}; };
const [formData, setFormData] =
useState<Partial<GuestCreate & GuestUpdate>>(initialState);
return <Badge className={statusStyles[status]}>{status}</Badge>; // Access guest context
}; const {
guests,
isLoadingGuests,
error,
createGuest,
updateGuest,
deleteGuest,
refetchGuests,
currentGuestId,
setCurrentGuestId,
} = useGuests();
const { user } = useAuth();
const filteredGuests = guests.filter( // Filter guests by search query
const filteredGuests =
guests?.filter(
(guest) => (guest) =>
guest.fullName.toLowerCase().includes(searchQuery.toLowerCase()) || guest.full_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
guest.email.toLowerCase().includes(searchQuery.toLowerCase()) || (guest.email?.toLowerCase() || "").includes(
guest.invitationCode.toLowerCase().includes(searchQuery.toLowerCase()), searchQuery.toLowerCase(),
) ||
guest.invitation_code.toLowerCase().includes(searchQuery.toLowerCase()),
) || [];
// Handle form input changes
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
// Handle select changes
const handleSelectChange = (
name: string,
value: string | number | boolean,
) => {
setFormData((prev) => ({ ...prev, [name]: value }));
};
// Handle add guest form submission
const handleAddGuest = async () => {
try {
if (!formData.full_name) {
toast.error("Full name is required");
return;
}
// Get the event ID from the first guest or use a default
// In a real implementation, you would probably pass this as a prop
const eventId = event.id; // This should be replaced with the actual event ID
// Get the current user ID
// In a real implementation, you would get this from an auth context
const invitedBy = user?.id || "admin"; // This should be replaced with the current user ID
const newGuest: GuestCreate = {
event_id: eventId,
invited_by: invitedBy,
full_name: formData.full_name || "",
email: formData.email || null,
phone: formData.phone || null,
max_additional_guests:
typeof formData.max_additional_guests === "number"
? formData.max_additional_guests
: 0,
dietary_restrictions: formData.dietary_restrictions || null,
notes: formData.notes || null,
can_bring_guests: formData.can_bring_guests || true,
invitation_code: generateInvitationCode(formData.full_name || ""),
};
await createGuest(newGuest);
toast.success("Guest added successfully");
setAddGuestOpen(false);
setFormData(initialState);
} catch (error) {
console.error("Error adding guest:", error);
toast.error("Failed to add guest");
}
};
// Handle edit guest
const handleEditGuest = async () => {
try {
if (!currentGuest) return;
const updatedGuest: GuestUpdate = {
full_name: formData.full_name || null,
email: formData.email || null,
phone: formData.phone || null,
max_additional_guests:
typeof formData.max_additional_guests === "number"
? formData.max_additional_guests
: null,
dietary_restrictions: formData.dietary_restrictions || null,
notes: formData.notes || null,
can_bring_guests: formData.can_bring_guests || null,
};
await updateGuest(currentGuest.id, updatedGuest);
toast.success("Guest updated successfully");
setEditGuestOpen(false);
} catch (error) {
console.error("Error updating guest:", error);
toast.error("Failed to update guest");
}
};
// Handle delete guest
const handleDeleteGuest = async () => {
try {
if (!currentGuest) return;
await deleteGuest(currentGuest.id);
setFormData(initialState);
setCurrentGuestId(null);
toast.success("Guest deleted successfully");
setDeleteDialogOpen(false);
} catch (error) {
console.error("Error deleting guest:", error);
toast.error("Failed to delete guest");
}
};
// Prepare to edit a guest
const prepareEditGuest = (guest: GuestRead) => {
setCurrentGuest(guest);
setFormData({
full_name: guest.full_name,
email: guest.email || "",
phone: guest.phone || "",
max_additional_guests: guest.max_additional_guests || 0,
dietary_restrictions: guest.dietary_restrictions || "",
notes: guest.notes || "",
can_bring_guests: guest.can_bring_guests || false,
});
setEditGuestOpen(true);
};
// Prepare to delete a guest
const prepareDeleteGuest = (guest: GuestRead) => {
setCurrentGuest(guest);
setDeleteDialogOpen(true);
};
// Helper to get status badge
const getStatusBadge = (status: GuestStatus) => {
const statusStyles: Record<GuestStatus, string> = {
[GuestStatus.INVITED]: "bg-blue-100 text-blue-800",
[GuestStatus.PENDING]: "bg-yellow-100 text-yellow-800",
[GuestStatus.CONFIRMED]: "bg-green-100 text-green-800",
[GuestStatus.DECLINED]: "bg-red-100 text-red-800",
[GuestStatus.WAITLISTED]: "bg-purple-100 text-purple-800",
[GuestStatus.CANCELLED]: "bg-gray-100 text-gray-800",
};
return (
<Badge className={statusStyles[status]}>{status.toUpperCase()}</Badge>
); );
};
// Copy invitation code to clipboard
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
toast("Invitation code copied to clipboard");
};
// Export guest list to CSV
const exportToCSV = () => {
if (!guests || guests.length === 0) return;
const headers = [
"Name",
"Email",
"Phone",
"Invitation Code",
"Status",
"Additional Guests",
];
const csvContent = [
headers.join(","),
...guests.map((guest) =>
[
`"${guest.full_name}"`,
`"${guest.email || ""}"`,
`"${guest.phone || ""}"`,
`"${guest.invitation_code}"`,
`"${guest.status}"`,
guest.max_additional_guests || 0,
].join(","),
),
].join("\n");
const blob = new Blob([csvContent], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.setAttribute("href", url);
a.setAttribute("download", "guest-list.csv");
a.click();
};
// Send invitations (stub - in a real app, this would integrate with your email service)
const sendInvitations = () => {
toast("Sending Invitations", {
description:
"This feature would send invitations to all uninvited guests",
action: {
label: "Cancel",
onClick: () => console.log("Cancelled sending invitations"),
},
});
};
useEffect(() => {
setFormData(currentGuest || initialState);
}, [currentGuest, addGuestOpen, editGuestOpen]);
return ( return (
<div className="space-y-4 w-full"> <div className="space-y-4 w-full">
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative flex items-center">
<AlertTriangle className="h-5 w-5 mr-2" />
<span>Error loading guests: {error.message}</span>
</div>
)}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2 className="text-2xl font-bold">Guest List</h2> <h2 className="text-2xl font-bold">Guest List</h2>
<Dialog open={open} onOpenChange={setOpen}> <div className="flex gap-2">
<Button variant="outline" size="sm" onClick={exportToCSV}>
<Download className="mr-2 h-4 w-4" /> Export
</Button>
<Dialog open={addGuestOpen} onOpenChange={setAddGuestOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="bg-blue-600 hover:bg-blue-700"> <Button className="bg-blue-600 hover:bg-blue-700">
<Plus className="mr-2 h-4 w-4" /> Add Guest <Plus className="mr-2 h-4 w-4" /> Add Guest
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent className="sm:max-w-md">
<DialogHeader> <DialogHeader>
<DialogTitle>Add New Guest</DialogTitle> <DialogTitle>Add New Guest</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="grid gap-4 py-4"> <div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right"> <Label htmlFor="full_name" className="text-right">
Full Name Full Name <span className="text-red-500">*</span>
</Label> </Label>
<Input id="name" className="col-span-3" /> <Input
id="full_name"
name="full_name"
value={formData.full_name || ""}
onChange={handleInputChange}
className="col-span-3"
required
/>
</div> </div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="email" className="text-right"> <Label htmlFor="email" className="text-right">
Email Email
</Label> </Label>
<Input id="email" type="email" className="col-span-3" /> <Input
id="email"
name="email"
type="email"
value={formData.email || ""}
onChange={handleInputChange}
className="col-span-3"
/>
</div> </div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="phone" className="text-right"> <Label htmlFor="phone" className="text-right">
Phone Phone
</Label> </Label>
<Input id="phone" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="additionalGuests" className="text-right">
Additional Guests
</Label>
<Input <Input
id="additionalGuests" id="phone"
type="number" name="phone"
min="0" value={formData.phone || ""}
onChange={handleInputChange}
className="col-span-3" className="col-span-3"
/> />
</div> </div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="max_additional_guests" className="text-right">
Additional Guests
</Label>
<Input
id="max_additional_guests"
name="max_additional_guests"
type="number"
min="0"
value={formData.max_additional_guests || 0}
onChange={handleInputChange}
className="col-span-3"
/>
</div> </div>
<div className="flex justify-end gap-2"> <div className="grid grid-cols-4 items-start gap-4">
<Button variant="outline" onClick={() => setOpen(false)}> <Label
htmlFor="dietary_restrictions"
className="text-right pt-2"
>
Dietary Restrictions
</Label>
<Textarea
id="dietary_restrictions"
name="dietary_restrictions"
value={formData.dietary_restrictions || ""}
onChange={handleInputChange}
className="col-span-3"
rows={2}
/>
</div>
<div className="grid grid-cols-4 items-start gap-4">
<Label htmlFor="notes" className="text-right pt-2">
Notes
</Label>
<Textarea
id="notes"
name="notes"
value={formData.notes || ""}
onChange={handleInputChange}
className="col-span-3"
rows={2}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="can_bring_guests" className="text-right">
Can Bring Guests
</Label>
<Select
value={
formData.can_bring_guests !== undefined
? String(formData.can_bring_guests)
: "true"
}
onValueChange={(value: string) =>
handleSelectChange("can_bring_guests", value === "true")
}
>
<SelectTrigger className="col-span-3">
<SelectValue placeholder="Select option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true">Yes</SelectItem>
<SelectItem value="false">No</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setAddGuestOpen(false)}
>
Cancel Cancel
</Button> </Button>
<Button className="bg-blue-600 hover:bg-blue-700"> <Button
className="bg-blue-600 hover:bg-blue-700"
onClick={handleAddGuest}
>
Add Guest Add Guest
</Button> </Button>
</div> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
</div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="relative w-64"> <div className="relative w-64">
@@ -170,7 +490,7 @@ const GuestListTable = () => {
<Button variant="outline" size="sm"> <Button variant="outline" size="sm">
<Filter className="mr-2 h-4 w-4" /> Filter <Filter className="mr-2 h-4 w-4" /> Filter
</Button> </Button>
<Button variant="outline" size="sm"> <Button variant="outline" size="sm" onClick={sendInvitations}>
<Send className="mr-2 h-4 w-4" /> Send Invites <Send className="mr-2 h-4 w-4" /> Send Invites
</Button> </Button>
</div> </div>
@@ -190,21 +510,41 @@ const GuestListTable = () => {
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{filteredGuests.map((guest) => ( {isLoadingGuests ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8">
Loading guests...
</TableCell>
</TableRow>
) : filteredGuests.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8">
No guests found. Add your first guest!
</TableCell>
</TableRow>
) : (
filteredGuests.map((guest) => (
<TableRow key={guest.id}> <TableRow key={guest.id}>
<TableCell className="font-medium">{guest.fullName}</TableCell> <TableCell className="font-medium">
<TableCell>{guest.email}</TableCell> {guest.full_name}
<TableCell>{guest.phone}</TableCell> </TableCell>
<TableCell>{guest.email || "-"}</TableCell>
<TableCell>{guest.phone || "-"}</TableCell>
<TableCell> <TableCell>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{guest.invitationCode} {guest.invitation_code}
<Button variant="ghost" size="icon" className="h-6 w-6"> <Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={() => copyToClipboard(guest.invitation_code)}
>
<Copy className="h-3 w-3" /> <Copy className="h-3 w-3" />
</Button> </Button>
</div> </div>
</TableCell> </TableCell>
<TableCell>{getStatusBadge(guest.status)}</TableCell> <TableCell>{getStatusBadge(guest.status)}</TableCell>
<TableCell>{guest.additionalGuests}</TableCell> <TableCell>{guest.max_additional_guests || 0}</TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@@ -213,31 +553,196 @@ const GuestListTable = () => {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem> <DropdownMenuItem
onClick={() => prepareEditGuest(guest)}
>
Edit
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => copyToClipboard(guest.invitation_code)}
>
Copy Invitation Code
</DropdownMenuItem>
<DropdownMenuItem>Resend Invitation</DropdownMenuItem> <DropdownMenuItem>Resend Invitation</DropdownMenuItem>
<DropdownMenuItem className="text-red-600"> <DropdownMenuSeparator />
<DropdownMenuItem
className="text-red-600"
onClick={() => prepareDeleteGuest(guest)}
>
Delete Delete
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))
)}
</TableBody> </TableBody>
</Table> </Table>
</div> </div>
<div className="flex justify-between text-sm text-gray-500"> <div className="flex justify-between text-sm text-gray-500">
<div> <div>
Showing {filteredGuests.length} of {guests.length} guests Showing {filteredGuests.length} of {guests?.length || 0} guests
</div> </div>
<div> <div>
Total Confirmed:{" "} Total Confirmed:{" "}
{guests.filter((g) => g.status === "CONFIRMED").length} | Total {guests?.filter((g) => g.status === GuestStatus.CONFIRMED).length ||
Additional Guests:{" "} 0}{" "}
{guests.reduce((acc, g) => acc + g.additionalGuests, 0)} | Total Additional Guests:{" "}
{guests?.reduce(
(acc, g) => acc + (g.max_additional_guests || 0),
0,
) || 0}
</div> </div>
</div> </div>
{/* Edit Guest Dialog */}
<Dialog open={editGuestOpen} onOpenChange={setEditGuestOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Edit Guest</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="edit_full_name" className="text-right">
Full Name <span className="text-red-500">*</span>
</Label>
<Input
id="edit_full_name"
name="full_name"
value={formData.full_name || ""}
onChange={handleInputChange}
className="col-span-3"
required
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="edit_email" className="text-right">
Email
</Label>
<Input
id="edit_email"
name="email"
type="email"
value={formData.email || ""}
onChange={handleInputChange}
className="col-span-3"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="edit_phone" className="text-right">
Phone
</Label>
<Input
id="edit_phone"
name="phone"
value={formData.phone || ""}
onChange={handleInputChange}
className="col-span-3"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label
htmlFor="edit_max_additional_guests"
className="text-right"
>
Additional Guests
</Label>
<Input
id="edit_max_additional_guests"
name="max_additional_guests"
type="number"
min="0"
value={formData.max_additional_guests || 0}
onChange={handleInputChange}
className="col-span-3"
/>
</div>
<div className="grid grid-cols-4 items-start gap-4">
<Label
htmlFor="edit_dietary_restrictions"
className="text-right pt-2"
>
Dietary Restrictions
</Label>
<Textarea
id="edit_dietary_restrictions"
name="dietary_restrictions"
value={formData.dietary_restrictions || ""}
onChange={handleInputChange}
className="col-span-3"
rows={2}
/>
</div>
<div className="grid grid-cols-4 items-start gap-4">
<Label htmlFor="edit_notes" className="text-right pt-2">
Notes
</Label>
<Textarea
id="edit_notes"
name="notes"
value={formData.notes || ""}
onChange={handleInputChange}
className="col-span-3"
rows={2}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="edit_can_bring_guests" className="text-right">
Can Bring Guests
</Label>
<Select
value={formData.can_bring_guests ? "true" : "false"}
onValueChange={(value) =>
handleSelectChange("can_bring_guests", value === "true")
}
>
<SelectTrigger className="col-span-3">
<SelectValue placeholder="Select option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true">Yes</SelectItem>
<SelectItem value="false">No</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setEditGuestOpen(false)}>
Cancel
</Button>
<Button
className="bg-blue-600 hover:bg-blue-700"
onClick={handleEditGuest}
>
Save Changes
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Delete Confirmation Dialog */}
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This will permanently delete the guest "{currentGuest?.full_name}
". This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
className="bg-red-600 hover:bg-red-700 text-white"
onClick={handleDeleteGuest}
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div> </div>
); );
}; };

View File

@@ -6,6 +6,7 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { AuthProvider } from "@/context/auth-context"; import { AuthProvider } from "@/context/auth-context";
import { ThemeProvider } from "next-themes"; import { ThemeProvider } from "next-themes";
import { DataProviders } from "@/providers/data"; import { DataProviders } from "@/providers/data";
import { Toaster } from "sonner";
// Create a client // Create a client
const queryClient = new QueryClient({ const queryClient = new QueryClient({
@@ -29,6 +30,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
> >
<AuthProvider> <AuthProvider>
<DataProviders>{children}</DataProviders> <DataProviders>{children}</DataProviders>
<Toaster />
</AuthProvider> </AuthProvider>
</ThemeProvider> </ThemeProvider>
<ReactQueryDevtools initialIsOpen={false} /> <ReactQueryDevtools initialIsOpen={false} />