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

View File

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import {
Table,
TableBody,
@@ -11,6 +11,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
@@ -18,142 +19,461 @@ import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
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 = () => {
const [open, setOpen] = useState(false);
// Helper to generate a random invitation code
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 [currentGuest, setCurrentGuest] = useState<GuestRead | null>(null);
// Mock data
const guests = [
{
id: "1",
fullName: "John Smith",
email: "john.smith@example.com",
phone: "+1 555-123-4567",
invitationCode: "JSMITH21",
status: "CONFIRMED",
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,
},
];
// Form state
const initialState = {
full_name: "",
email: "",
phone: "",
max_additional_guests: 0,
dietary_restrictions: "",
notes: "",
can_bring_guests: true,
};
const [formData, setFormData] =
useState<Partial<GuestCreate & GuestUpdate>>(initialState);
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",
};
// Access guest context
const {
guests,
isLoadingGuests,
error,
createGuest,
updateGuest,
deleteGuest,
refetchGuests,
currentGuestId,
setCurrentGuestId,
} = useGuests();
const { user } = useAuth();
return <Badge className={statusStyles[status]}>{status}</Badge>;
// Filter guests by search query
const filteredGuests =
guests?.filter(
(guest) =>
guest.full_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
(guest.email?.toLowerCase() || "").includes(
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 }));
};
const filteredGuests = guests.filter(
(guest) =>
guest.fullName.toLowerCase().includes(searchQuery.toLowerCase()) ||
guest.email.toLowerCase().includes(searchQuery.toLowerCase()) ||
guest.invitationCode.toLowerCase().includes(searchQuery.toLowerCase()),
);
// 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 (
<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">
<h2 className="text-2xl font-bold">Guest List</h2>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className="bg-blue-600 hover:bg-blue-700">
<Plus className="mr-2 h-4 w-4" /> Add Guest
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add New Guest</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Full Name
</Label>
<Input id="name" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="email" className="text-right">
Email
</Label>
<Input id="email" type="email" className="col-span-3" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="phone" className="text-right">
Phone
</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
id="additionalGuests"
type="number"
min="0"
className="col-span-3"
/>
</div>
</div>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => setOpen(false)}>
Cancel
</Button>
<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>
<Button className="bg-blue-600 hover:bg-blue-700">
Add Guest
<Plus className="mr-2 h-4 w-4" /> Add Guest
</Button>
</div>
</DialogContent>
</Dialog>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Add New Guest</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="full_name" className="text-right">
Full Name <span className="text-red-500">*</span>
</Label>
<Input
id="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="email" className="text-right">
Email
</Label>
<Input
id="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="phone" className="text-right">
Phone
</Label>
<Input
id="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="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 className="grid grid-cols-4 items-start gap-4">
<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
</Button>
<Button
className="bg-blue-600 hover:bg-blue-700"
onClick={handleAddGuest}
>
Add Guest
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
<div className="flex justify-between items-center">
@@ -170,7 +490,7 @@ const GuestListTable = () => {
<Button variant="outline" size="sm">
<Filter className="mr-2 h-4 w-4" /> Filter
</Button>
<Button variant="outline" size="sm">
<Button variant="outline" size="sm" onClick={sendInvitations}>
<Send className="mr-2 h-4 w-4" /> Send Invites
</Button>
</div>
@@ -190,54 +510,239 @@ const GuestListTable = () => {
</TableRow>
</TableHeader>
<TableBody>
{filteredGuests.map((guest) => (
<TableRow key={guest.id}>
<TableCell className="font-medium">{guest.fullName}</TableCell>
<TableCell>{guest.email}</TableCell>
<TableCell>{guest.phone}</TableCell>
<TableCell>
<div className="flex items-center gap-1">
{guest.invitationCode}
<Button variant="ghost" size="icon" className="h-6 w-6">
<Copy className="h-3 w-3" />
</Button>
</div>
</TableCell>
<TableCell>{getStatusBadge(guest.status)}</TableCell>
<TableCell>{guest.additionalGuests}</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Resend Invitation</DropdownMenuItem>
<DropdownMenuItem className="text-red-600">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{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}>
<TableCell className="font-medium">
{guest.full_name}
</TableCell>
<TableCell>{guest.email || "-"}</TableCell>
<TableCell>{guest.phone || "-"}</TableCell>
<TableCell>
<div className="flex items-center gap-1">
{guest.invitation_code}
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={() => copyToClipboard(guest.invitation_code)}
>
<Copy className="h-3 w-3" />
</Button>
</div>
</TableCell>
<TableCell>{getStatusBadge(guest.status)}</TableCell>
<TableCell>{guest.max_additional_guests || 0}</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => prepareEditGuest(guest)}
>
Edit
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => copyToClipboard(guest.invitation_code)}
>
Copy Invitation Code
</DropdownMenuItem>
<DropdownMenuItem>Resend Invitation</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-red-600"
onClick={() => prepareDeleteGuest(guest)}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
<div className="flex justify-between text-sm text-gray-500">
<div>
Showing {filteredGuests.length} of {guests.length} guests
Showing {filteredGuests.length} of {guests?.length || 0} guests
</div>
<div>
Total Confirmed:{" "}
{guests.filter((g) => g.status === "CONFIRMED").length} | Total
Additional Guests:{" "}
{guests.reduce((acc, g) => acc + g.additionalGuests, 0)}
{guests?.filter((g) => g.status === GuestStatus.CONFIRMED).length ||
0}{" "}
| Total Additional Guests:{" "}
{guests?.reduce(
(acc, g) => acc + (g.max_additional_guests || 0),
0,
) || 0}
</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>
);
};

View File

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