add confirmation dialog

This commit is contained in:
2024-11-22 22:23:00 -06:00
parent 13648edf55
commit 55447c6de4
3 changed files with 135 additions and 13 deletions

View File

@@ -3,6 +3,8 @@
import {} from "@radix-ui/react-dropdown-menu";
import { Dot, MoreHorizontal } from "lucide-react";
import Link from "next/link";
import { toast } from "sonner";
import { useDialogConfirmation } from "~/app/_components/macro/confirmation-dialog";
import { Button } from "~/app/_components/ui/button";
import {
DropdownMenu,
@@ -12,29 +14,49 @@ import {
} from "~/app/_components/ui/dropdown-menu";
import { api } from "~/trpc/react";
export const ListButton = ({ id, name }: { id: number; name: string }) => {
const newName = "test new name";
const newName = "test new name";
const { mutate: updateMutation } = api.list.update.useMutation();
const { mutate: deleteMutation } = api.list.delete.useMutation();
export const ListButton = ({ id, name }: { id: number; name: string }) => {
const dialogConfirmation = useDialogConfirmation();
const { mutate: updateMutation } = api.list.update.useMutation({
onSuccess: () => {
toast.success("List successfully renamed.");
},
});
const { mutate: deleteMutation } = api.list.delete.useMutation({
onSuccess: () => {
toast.success("List successfully deleted.");
},
});
const handleRename = () => updateMutation({ listId: id, name: newName });
const handleDelete = () => deleteMutation({ listId: id });
const handleDelete = async () => {
const didConfirm = await dialogConfirmation({});
if (didConfirm) {
deleteMutation({ listId: id });
}
};
return (
<Button
asChild
variant="ghost"
className="w-full justify-start underline-offset-4 hover:underline"
className="group w-full cursor-default justify-start"
>
<Link href={`/list/${id}`}>
<>
<Dot className="mr-2 h-4 w-4" />
<Link
href={`/list/${id}`}
className="w-full text-left underline-offset-4 hover:underline"
>
{name}
</Link>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="data-[state=open]:bg-muted ml-auto flex h-8 w-8 p-0"
className="data-[state=open]:bg-muted flex h-8 w-8 p-0"
>
<MoreHorizontal />
<span className="sr-only">Open menu</span>
@@ -45,7 +67,7 @@ export const ListButton = ({ id, name }: { id: number; name: string }) => {
<DropdownMenuItem onClick={handleDelete}>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</Link>
</>
</Button>
);
};

View File

@@ -0,0 +1,93 @@
"use client";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "~/app/_components/ui/dialog";
import { Button } from "~/app/_components/ui/button";
import { createContext, useContext, useState } from "react";
type DialogContextType = (options: {
title?: string;
description?: string;
confirmText?: string;
cancelText?: string;
}) => Promise<boolean>;
const DialogContext = createContext<DialogContextType | null>(null);
export const useDialogConfirmation = () => {
const context = useContext(DialogContext);
if (!context) {
throw new Error("useDialogConfirmation must be used within DialogProvider");
}
return context;
};
export const DialogProvider = ({ children }: { children: React.ReactNode }) => {
const [dialogState, setDialogState] = useState<{
isOpen: boolean;
options: {
title?: string;
description?: string;
confirmText?: string;
cancelText?: string;
};
resolve: ((value: boolean) => void) | null;
}>({
isOpen: false,
options: {},
resolve: null,
});
const dialogConfirmation: DialogContextType = (options) =>
new Promise((resolve) => {
setDialogState({
isOpen: true,
options,
resolve,
});
});
const handleConfirm = () => {
if (dialogState.resolve) dialogState.resolve(true);
setDialogState({ ...dialogState, isOpen: false });
};
const handleCancel = () => {
if (dialogState.resolve) dialogState.resolve(false);
setDialogState({ ...dialogState, isOpen: false });
};
return (
<DialogContext.Provider value={dialogConfirmation}>
{children}
<Dialog
open={dialogState.isOpen}
onOpenChange={(isOpen) => setDialogState({ ...dialogState, isOpen })}
>
<DialogContent>
<DialogHeader>
<DialogTitle>
{dialogState.options.title ?? "Are you sure?"}
</DialogTitle>
</DialogHeader>
<p>
{dialogState.options.description ?? "This action cannot be undone."}
</p>
<DialogFooter>
<Button variant="secondary" onClick={handleCancel}>
{dialogState.options.cancelText ?? "Cancel"}
</Button>
<Button variant="destructive" onClick={handleConfirm}>
{dialogState.options.confirmText ?? "Confirm"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</DialogContext.Provider>
);
};

View File

@@ -5,7 +5,9 @@ import { type Metadata } from "next";
import { TRPCReactProvider } from "~/trpc/react";
import { HydrateClient } from "~/trpc/server";
import { AuthProvider } from "~/app/_components/auth-provider";
import { AuthProvider } from "~/app/_components/providers/auth-provider";
import { DialogProvider } from "~/app/_components/macro/confirmation-dialog";
import { Toaster } from "~/app/_components/ui/sonner";
export const metadata: Metadata = {
title: "ls",
@@ -26,7 +28,12 @@ export default function RootLayout({
<body>
<TRPCReactProvider>
<HydrateClient>
<AuthProvider>{children}</AuthProvider>
<AuthProvider>
<DialogProvider>
{children}
<Toaster />
</DialogProvider>
</AuthProvider>
</HydrateClient>
</TRPCReactProvider>
</body>