Adding new version of the ButtonWithConfirm component.

This commit is contained in:
charlesgauthereau
2025-08-27 18:01:28 +02:00
parent 60728885b6
commit 4c0d2b1f7f
6 changed files with 276 additions and 79 deletions
+2
View File
@@ -35,6 +35,8 @@ const buttonVariants = cva(
} }
) )
export type ButtonVariantsProps = VariantProps<typeof buttonVariants>;
function Button({ function Button({
className, className,
variant, variant,
@@ -1,45 +1,161 @@
"use client"; // "use client";
//
import { Button } from "@/components/ui/button"; // import { Button } from "@/components/ui/button";
// import { useState } from "react";
// import { Loader2 } from "lucide-react";
//
// export type VariantButton = {
// secondary: string;
// default: string;
// outline: string;
// ghost: string;
// link: string;
// destructive: string;
// };
//
// export type ButtonWithConfirmProps = {
// icon?: any;
// text: string;
// variant?: keyof VariantButton;
// className?: string;
// onClick?: () => void;
// isPending?: boolean;
// };
//
// export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => {
// const [isConfirming, setIsConfirming] = useState(false);
//
// return (
// <Button
// onClick={() => {
// if (isConfirming && props.onClick) {
// props.onClick();
// } else {
// setIsConfirming(true);
// }
// }}
// variant={props.variant ? props.variant : "default"}
// className={props.className}
// >
// {props.isPending && <Loader2 className="animate-spin mr-4" size={16} />}
// {isConfirming ? "Are you sure ?" : `${props.text}`}
// <>{props.icon ? props.icon : null}</>
// </Button>
// );
// };
"use client"
import {Button, ButtonVariantsProps} from "@/components/ui/button";
import {useState} from "react"; import {useState} from "react";
import {Loader2} from "lucide-react"; import {Loader2} from "lucide-react";
import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover";
export type VariantButton = { import {cn} from "@/lib/utils";
secondary: string; import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip";
default: string;
outline: string;
ghost: string;
link: string;
destructive: string;
};
export type ButtonWithConfirmProps = { export type ButtonWithConfirmProps = {
icon?: any; title: string;
text: string; description: string;
variant?: keyof VariantButton; button: {
main: {
className?: string; className?: string;
text?: string;
icon?: any;
variant?: ButtonVariantsProps["variant"];
size?: ButtonVariantsProps["size"];
disabled?: boolean;
tooltipText?: string;
};
confirm: {
className?: string;
text: string;
icon?: any;
variant?: ButtonVariantsProps["variant"];
size?: ButtonVariantsProps["size"];
onClick?: () => void; onClick?: () => void;
};
cancel: {
className?: string;
text: string;
icon?: any;
variant?: ButtonVariantsProps["variant"];
size?: ButtonVariantsProps["size"];
onClick?: () => void;
};
};
isPending?: boolean; isPending?: boolean;
}; };
export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => { export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => {
const [isConfirming, setIsConfirming] = useState(false); const [isConfirming, setIsConfirming] = useState(false);
return ( return (
<Popover open={isConfirming} onOpenChange={!props.button.main.disabled ? setIsConfirming : undefined}>
<Tooltip>
<TooltipTrigger asChild>
<PopoverTrigger asChild>
<span
className={cn(
"inline-flex",
props.button.main.disabled ? "cursor-not-allowed" : ""
)}
role="button">
<Button
disabled={!!props.button.main.disabled}
variant={props.button.main.variant ?? "default"}
size={props.button.main.size ?? "default"}
className={props.button.main.className}
onClick={() => {
if (!props.button.main.disabled) setIsConfirming(true);
}}
>
{props.button.main.icon}
{props.button.main.text && <span>{props.button.main.text}</span>}
</Button>
</span>
</PopoverTrigger>
</TooltipTrigger>
{props.button.main.tooltipText && props.button.main.disabled && (
<TooltipContent>
<p>{props.button.main.tooltipText}</p>
</TooltipContent>
)}
</Tooltip>
<PopoverContent className="w-80">
<div className="grid gap-4">
<div className="space-y-2">
<h4 className="font-medium leading-none">{props.title}</h4>
<p className="text-sm text-muted-foreground">{props.description}</p>
</div>
<div className="grid gap-2">
<Button <Button
onClick={() => { onClick={() => {
if (isConfirming && props.onClick) { if (isConfirming && props.button.confirm.onClick) {
props.onClick(); props.button.confirm.onClick();
setIsConfirming(false);
} else { } else {
setIsConfirming(true); setIsConfirming(true);
} }
}} }}
variant={props.variant ? props.variant : "default"} variant={props.button.confirm.variant ? props.button.confirm.variant : "default"}
className={props.className} size={props.button.main.size ?? "default"}
className={cn(props.button.main.className, "w-full")}
> >
{props.isPending && <Loader2 className="animate-spin mr-4" size={16}/>} {props.isPending && <Loader2 className="animate-spin mr-4" size={16}/>}
{isConfirming ? "Are you sure ?" : `${props.text}`} {props.button.confirm.icon ? props.button.confirm.icon : null}
<>{props.icon ? props.icon : null}</> <span>{props.button.confirm.text}</span>
</Button> </Button>
<Button
variant={props.button.cancel.variant ? props.button.cancel.variant : "default"}
onClick={props.button.cancel.onClick ? () => props.button.cancel.onClick!() : () => setIsConfirming(false)}
className={cn(props.button.main.className, "w-full")}
size={props.button.main.size ?? "default"}
>
Cancel
</Button>
</div>
</div>
</PopoverContent>
</Popover>
); );
}; };
@@ -29,14 +29,31 @@ export const ButtonDeleteAgent = (props: ButtonDeleteAgentProps) => {
return ( return (
<ButtonWithConfirm <ButtonWithConfirm
text={props.text ? props.text : ""} title={props.text ? props.text : ""}
onClick={() => { description="Are you sure you want to remove this agent? This action cannot be undone."
button={{
main: {
text: props.text ? props.text : "",
variant: "outline",
icon: <Trash2 color="red"/>,
},
confirm: {
className: "w-full",
text: "Delete",
icon: <Trash2/>,
variant: "destructive",
onClick: () => {
mutation.mutate(); mutation.mutate();
},
},
cancel: {
className: "w-full",
text: "Cancel",
icon: <Trash2/>,
variant: "outline",
},
}} }}
variant={"destructive"}
isPending={mutation.isPending} isPending={mutation.isPending}
className="gap-2"
icon={<Trash2 />}
/> />
); );
}; };
@@ -6,6 +6,7 @@ import { setCurrentOrganizationSlug } from "@/features/dashboard/organization-co
import {useRouter} from "next/navigation"; import {useRouter} from "next/navigation";
import {toast} from "sonner"; import {toast} from "sonner";
import {authClient} from "@/lib/auth/auth-client"; import {authClient} from "@/lib/auth/auth-client";
import {Trash2} from "lucide-react";
export type DeleteOrganizationButtonProps = { export type DeleteOrganizationButtonProps = {
organizationSlug: string; organizationSlug: string;
@@ -38,13 +39,35 @@ export const DeleteOrganizationButton = (props: DeleteOrganizationButtonProps) =
}); });
return ( return (
<ButtonWithConfirm <ButtonWithConfirm
onClick={() => { title="Delete Organization"
description="Are you sure you want to remove this organization? This action cannot be undone."
button={{
main: {
text: "Delete Organization",
variant: "outline",
icon: <Trash2 color="red"/>,
},
confirm: {
className: "w-full",
text: "Delete",
icon: <Trash2/>,
variant: "destructive",
onClick: () => {
mutation.mutate(); mutation.mutate();
},
},
cancel: {
className: "w-full",
text: "Cancel",
icon: <Trash2/>,
variant: "outline",
},
}} }}
isPending={mutation.isPending} isPending={mutation.isPending}
text="Delete Organization"
variant="destructive"
/> />
); );
}; };
@@ -29,14 +29,33 @@ export const ButtonDeleteAccount = (props: ButtonDeleteAccountProps) => {
return ( return (
<ButtonWithConfirm <ButtonWithConfirm
text={props.text ? props.text : ""} title={props.text ? props.text : ""}
onClick={() => { description="Are you sure you want to delete your account ? This action cannot be undone."
button={{
main: {
text: props.text ? props.text : "",
variant: "outline",
icon: <Trash2 color="red"/>,
},
confirm: {
className: "w-full",
text: "Delete",
icon: <Trash2/>,
variant: "destructive",
onClick: () => {
mutation.mutate(); mutation.mutate();
},
},
cancel: {
className: "w-full",
text: "Cancel",
icon: <Trash2/>,
variant: "outline",
},
}} }}
variant={"destructive"}
isPending={mutation.isPending} isPending={mutation.isPending}
className="gap-2"
icon={<Trash2 />}
/> />
); );
}; };
@@ -3,7 +3,9 @@
import {Trash2} from "lucide-react"; import {Trash2} from "lucide-react";
import {ButtonWithConfirm} from "@/components/wrappers/common/button/button-with-confirm"; import {ButtonWithConfirm} from "@/components/wrappers/common/button/button-with-confirm";
import {useMutation} from "@tanstack/react-query"; import {useMutation} from "@tanstack/react-query";
import { deleteProjectAction } from "@/components/wrappers/dashboard/projects/button-delete-project/delete-project.action"; import {
deleteProjectAction
} from "@/components/wrappers/dashboard/projects/button-delete-project/delete-project.action";
import {useRouter} from "next/navigation"; import {useRouter} from "next/navigation";
import {toast} from "sonner"; import {toast} from "sonner";
@@ -27,15 +29,33 @@ export const ButtonDeleteProject = (props: ButtonDeleteProjectProps) => {
}); });
return ( return (
<ButtonWithConfirm <ButtonWithConfirm
text={props.text ? props.text : ""} title={props.text ? props.text : ""}
onClick={() => { description="Are you sure you want to delete this project ? This action cannot be undone."
button={{
main: {
text: props.text ? props.text : "",
variant: "outline",
icon: <Trash2 color="red"/>,
},
confirm: {
className: "w-full",
text: "Delete",
icon: <Trash2/>,
variant: "destructive",
onClick: () => {
mutation.mutate(); mutation.mutate();
},
},
cancel: {
className: "w-full",
text: "Cancel",
icon: <Trash2/>,
variant: "outline",
},
}} }}
variant={"destructive"}
isPending={mutation.isPending} isPending={mutation.isPending}
className="gap-2"
icon={<Trash2 />}
/> />
); );
}; };