feat: validating app name before delete

This commit is contained in:
190km
2024-11-29 02:06:25 +01:00
parent 37fa139a65
commit d4fdf881cd
7 changed files with 825 additions and 281 deletions

View File

@@ -1,63 +1,143 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod";
const deleteApplicationSchema = z.object({
projectName: z.string().min(1, {
message: "Application name is required",
}),
});
type DeleteApplication = z.infer<typeof deleteApplicationSchema>;
interface Props { interface Props {
applicationId: string; applicationId: string;
} }
export const DeleteApplication = ({ applicationId }: Props) => { export const DeleteApplication = ({ applicationId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.application.delete.useMutation(); const { mutateAsync, isLoading } = api.application.delete.useMutation();
const { data } = api.application.one.useQuery(
{ applicationId },
{ enabled: !!applicationId },
);
const { push } = useRouter(); const { push } = useRouter();
const form = useForm<DeleteApplication>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteApplicationSchema),
});
const onSubmit = async (formData: DeleteApplication) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({
applicationId,
})
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Application deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the application");
});
} else {
form.setError("projectName", {
message: "Project name does not match",
});
}
};
return ( return (
<AlertDialog> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" /> <TrashIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</AlertDialogTrigger> </DialogTrigger>
<AlertDialogContent> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<AlertDialogHeader> <DialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <DialogTitle>Are you absolutely sure?</DialogTitle>
<AlertDialogDescription> <DialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
application application. If you are sure please enter the application name to
</AlertDialogDescription> delete this application.
</AlertDialogHeader> </DialogDescription>
<AlertDialogFooter> </DialogHeader>
<AlertDialogCancel>Cancel</AlertDialogCancel> <div className="grid gap-4">
<AlertDialogAction <Form {...form}>
onClick={async () => { <form
await mutateAsync({ onSubmit={form.handleSubmit(onSubmit)}
applicationId, id="hook-form-delete-application"
}) className="grid w-full gap-4"
.then((data) => { >
push(`/dashboard/project/${data?.projectId}`); <FormField
control={form.control}
toast.success("Application delete succesfully"); name="projectName"
}) render={({ field }) => (
.catch(() => { <FormItem>
toast.error("Error to delete Application"); <FormLabel>
}); To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter application name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}} }}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-application"
type="submit"
variant="destructive"
> >
Confirm Confirm
</AlertDialogAction> </Button>
</AlertDialogFooter> </DialogFooter>
</AlertDialogContent> </DialogContent>
</AlertDialog> </Dialog>
); );
}; };

View File

@@ -1,63 +1,141 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod";
const deleteComposeSchema = z.object({
projectName: z.string().min(1, {
message: "Compose name is required",
}),
});
type DeleteCompose = z.infer<typeof deleteComposeSchema>;
interface Props { interface Props {
composeId: string; composeId: string;
} }
export const DeleteCompose = ({ composeId }: Props) => { export const DeleteCompose = ({ composeId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.compose.delete.useMutation(); const { mutateAsync, isLoading } = api.compose.delete.useMutation();
const { data } = api.compose.one.useQuery(
{ composeId },
{ enabled: !!composeId },
);
const { push } = useRouter(); const { push } = useRouter();
const form = useForm<DeleteCompose>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteComposeSchema),
});
const onSubmit = async (formData: DeleteCompose) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ composeId })
.then((result) => {
push(`/dashboard/project/${result?.projectId}`);
toast.success("Compose deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the compose");
});
} else {
form.setError("projectName", {
message: `Project name must match "${expectedName}"`,
});
}
};
return ( return (
<AlertDialog> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" /> <TrashIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</AlertDialogTrigger> </DialogTrigger>
<AlertDialogContent> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<AlertDialogHeader> <DialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <DialogTitle>Are you absolutely sure?</DialogTitle>
<AlertDialogDescription> <DialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
compose and all its services. compose. If you are sure please enter the compose name to delete
</AlertDialogDescription> this compose.
</AlertDialogHeader> </DialogDescription>
<AlertDialogFooter> </DialogHeader>
<AlertDialogCancel>Cancel</AlertDialogCancel> <div className="grid gap-4">
<AlertDialogAction <Form {...form}>
onClick={async () => { <form
await mutateAsync({ onSubmit={form.handleSubmit(onSubmit)}
composeId, id="hook-form-delete-compose"
}) className="grid w-full gap-4"
.then((data) => { >
push(`/dashboard/project/${data?.projectId}`); <FormField
control={form.control}
toast.success("Compose delete succesfully"); name="projectName"
}) render={({ field }) => (
.catch(() => { <FormItem>
toast.error("Error to delete the compose"); <FormLabel>
}); To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>{" "}
<FormControl>
<Input
placeholder="Enter compose name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}} }}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-compose"
type="submit"
variant="destructive"
> >
Confirm Confirm
</AlertDialogAction> </Button>
</AlertDialogFooter> </DialogFooter>
</AlertDialogContent> </DialogContent>
</AlertDialog> </Dialog>
); );
}; };

View File

@@ -1,62 +1,140 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod";
const deleteMariadbSchema = z.object({
projectName: z.string().min(1, {
message: "Database name is required",
}),
});
type DeleteMariadb = z.infer<typeof deleteMariadbSchema>;
interface Props { interface Props {
mariadbId: string; mariadbId: string;
} }
export const DeleteMariadb = ({ mariadbId }: Props) => { export const DeleteMariadb = ({ mariadbId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.mariadb.remove.useMutation(); const { mutateAsync, isLoading } = api.mariadb.remove.useMutation();
const { data } = api.mariadb.one.useQuery(
{ mariadbId },
{ enabled: !!mariadbId },
);
const { push } = useRouter(); const { push } = useRouter();
const form = useForm<DeleteMariadb>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteMariadbSchema),
});
const onSubmit = async (formData: DeleteMariadb) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ mariadbId })
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the database");
});
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return ( return (
<AlertDialog> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " /> <TrashIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</AlertDialogTrigger> </DialogTrigger>
<AlertDialogContent> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<AlertDialogHeader> <DialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <DialogTitle>Are you absolutely sure?</DialogTitle>
<AlertDialogDescription> <DialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
database database. If you are sure please enter the database name to delete
</AlertDialogDescription> this database.
</AlertDialogHeader> </DialogDescription>
<AlertDialogFooter> </DialogHeader>
<AlertDialogCancel>Cancel</AlertDialogCancel> <div className="grid gap-4">
<AlertDialogAction <Form {...form}>
onClick={async () => { <form
await mutateAsync({ onSubmit={form.handleSubmit(onSubmit)}
mariadbId, id="hook-form-delete-mariadb"
}) className="grid w-full gap-4"
.then((data) => { >
push(`/dashboard/project/${data?.projectId}`); <FormField
toast.success("Database delete succesfully"); control={form.control}
}) name="projectName"
.catch(() => { render={({ field }) => (
toast.error("Error to delete the database"); <FormItem>
}); <FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter database name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}} }}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-mariadb"
type="submit"
variant="destructive"
> >
Confirm Confirm
</AlertDialogAction> </Button>
</AlertDialogFooter> </DialogFooter>
</AlertDialogContent> </DialogContent>
</AlertDialog> </Dialog>
); );
}; };

View File

@@ -1,62 +1,139 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod";
const deleteMongoSchema = z.object({
projectName: z.string().min(1, {
message: "Database name is required",
}),
});
type DeleteMongo = z.infer<typeof deleteMongoSchema>;
interface Props { interface Props {
mongoId: string; mongoId: string;
} }
// commen
export const DeleteMongo = ({ mongoId }: Props) => { export const DeleteMongo = ({ mongoId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.mongo.remove.useMutation(); const { mutateAsync, isLoading } = api.mongo.remove.useMutation();
const { data } = api.mongo.one.useQuery({ mongoId }, { enabled: !!mongoId });
const { push } = useRouter(); const { push } = useRouter();
const form = useForm<DeleteMongo>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteMongoSchema),
});
const onSubmit = async (formData: DeleteMongo) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ mongoId })
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the database");
});
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return ( return (
<AlertDialog> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " /> <TrashIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</AlertDialogTrigger> </DialogTrigger>
<AlertDialogContent> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<AlertDialogHeader> <DialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <DialogTitle>Are you absolutely sure?</DialogTitle>
<AlertDialogDescription> <DialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
database database. If you are sure please enter the database name to delete
</AlertDialogDescription> this database.
</AlertDialogHeader> </DialogDescription>
<AlertDialogFooter> </DialogHeader>
<AlertDialogCancel>Cancel</AlertDialogCancel> <div className="grid gap-4">
<AlertDialogAction <Form {...form}>
onClick={async () => { <form
await mutateAsync({ onSubmit={form.handleSubmit(onSubmit)}
mongoId, id="hook-form-delete-mongo"
}) className="grid w-full gap-4"
.then((data) => { >
push(`/dashboard/project/${data?.projectId}`); <FormField
toast.success("Database delete succesfully"); control={form.control}
}) name="projectName"
.catch(() => { render={({ field }) => (
toast.error("Error to delete the database"); <FormItem>
}); <FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter database name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}} }}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-mongo"
type="submit"
variant="destructive"
> >
Confirm Confirm
</AlertDialogAction> </Button>
</AlertDialogFooter> </DialogFooter>
</AlertDialogContent> </DialogContent>
</AlertDialog> </Dialog>
); );
}; };

View File

@@ -1,62 +1,138 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod";
const deleteMysqlSchema = z.object({
projectName: z.string().min(1, {
message: "Database name is required",
}),
});
type DeleteMysql = z.infer<typeof deleteMysqlSchema>;
interface Props { interface Props {
mysqlId: string; mysqlId: string;
} }
export const DeleteMysql = ({ mysqlId }: Props) => { export const DeleteMysql = ({ mysqlId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.mysql.remove.useMutation(); const { mutateAsync, isLoading } = api.mysql.remove.useMutation();
const { data } = api.mysql.one.useQuery({ mysqlId }, { enabled: !!mysqlId });
const { push } = useRouter(); const { push } = useRouter();
const form = useForm<DeleteMysql>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteMysqlSchema),
});
const onSubmit = async (formData: DeleteMysql) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ mysqlId })
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the database");
});
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return ( return (
<AlertDialog> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " /> <TrashIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</AlertDialogTrigger> </DialogTrigger>
<AlertDialogContent> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<AlertDialogHeader> <DialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <DialogTitle>Are you absolutely sure?</DialogTitle>
<AlertDialogDescription> <DialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
database database. If you are sure please enter the database name to delete
</AlertDialogDescription> this database.
</AlertDialogHeader> </DialogDescription>
<AlertDialogFooter> </DialogHeader>
<AlertDialogCancel>Cancel</AlertDialogCancel> <div className="grid gap-4">
<AlertDialogAction <Form {...form}>
onClick={async () => { <form
await mutateAsync({ onSubmit={form.handleSubmit(onSubmit)}
mysqlId, id="hook-form-delete-mysql"
}) className="grid w-full gap-4"
.then((data) => { >
push(`/dashboard/project/${data?.projectId}`); <FormField
toast.success("Database delete succesfully"); control={form.control}
}) name="projectName"
.catch(() => { render={({ field }) => (
toast.error("Error to delete the database"); <FormItem>
}); <FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter database name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}} }}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-mysql"
type="submit"
variant="destructive"
> >
Confirm Confirm
</AlertDialogAction> </Button>
</AlertDialogFooter> </DialogFooter>
</AlertDialogContent> </DialogContent>
</AlertDialog> </Dialog>
); );
}; };

View File

@@ -1,62 +1,141 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod";
const deletePostgresSchema = z.object({
projectName: z.string().min(1, {
message: "Database name is required",
}),
});
type DeletePostgres = z.infer<typeof deletePostgresSchema>;
interface Props { interface Props {
postgresId: string; postgresId: string;
} }
export const DeletePostgres = ({ postgresId }: Props) => { export const DeletePostgres = ({ postgresId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.postgres.remove.useMutation(); const { mutateAsync, isLoading } = api.postgres.remove.useMutation();
const { data } = api.postgres.one.useQuery(
{ postgresId },
{ enabled: !!postgresId },
);
const { push } = useRouter(); const { push } = useRouter();
const form = useForm<DeletePostgres>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deletePostgresSchema),
});
const onSubmit = async (formData: DeletePostgres) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ postgresId })
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the database");
});
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return ( return (
<AlertDialog> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " /> <TrashIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</AlertDialogTrigger> </DialogTrigger>
<AlertDialogContent> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<AlertDialogHeader> <DialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <DialogTitle>Are you absolutely sure?</DialogTitle>
<AlertDialogDescription> <DialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
database database. If you are sure please enter the database name to delete
</AlertDialogDescription> this database.
</AlertDialogHeader> </DialogDescription>
<AlertDialogFooter> </DialogHeader>
<AlertDialogCancel>Cancel</AlertDialogCancel> <div className="grid gap-4">
<AlertDialogAction <Form {...form}>
onClick={async () => { <form
await mutateAsync({ onSubmit={form.handleSubmit(onSubmit)}
postgresId, id="hook-form-delete-postgres"
}) className="grid w-full gap-4"
.then((data) => { >
push(`/dashboard/project/${data?.projectId}`); <FormField
toast.success("Database delete succesfully"); control={form.control}
}) name="projectName"
.catch(() => { render={({ field }) => (
toast.error("Error to delete the database"); <FormItem>
}); <FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter database name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}} }}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-postgres"
type="submit"
variant="destructive"
> >
Confirm Confirm
</AlertDialogAction> </Button>
</AlertDialogFooter> </DialogFooter>
</AlertDialogContent> </DialogContent>
</AlertDialog> </Dialog>
); );
}; };

View File

@@ -1,62 +1,138 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react"; import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod";
const deleteRedisSchema = z.object({
projectName: z.string().min(1, {
message: "Database name is required",
}),
});
type DeleteRedis = z.infer<typeof deleteRedisSchema>;
interface Props { interface Props {
redisId: string; redisId: string;
} }
export const DeleteRedis = ({ redisId }: Props) => { export const DeleteRedis = ({ redisId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.redis.remove.useMutation(); const { mutateAsync, isLoading } = api.redis.remove.useMutation();
const { data } = api.redis.one.useQuery({ redisId }, { enabled: !!redisId });
const { push } = useRouter(); const { push } = useRouter();
const form = useForm<DeleteRedis>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteRedisSchema),
});
const onSubmit = async (formData: DeleteRedis) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ redisId })
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the database");
});
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return ( return (
<AlertDialog> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}> <Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " /> <TrashIcon className="size-4 text-muted-foreground" />
</Button> </Button>
</AlertDialogTrigger> </DialogTrigger>
<AlertDialogContent> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<AlertDialogHeader> <DialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <DialogTitle>Are you absolutely sure?</DialogTitle>
<AlertDialogDescription> <DialogDescription>
This action cannot be undone. This will permanently delete the This action cannot be undone. This will permanently delete the
database database. If you are sure please enter the database name to delete
</AlertDialogDescription> this database.
</AlertDialogHeader> </DialogDescription>
<AlertDialogFooter> </DialogHeader>
<AlertDialogCancel>Cancel</AlertDialogCancel> <div className="grid gap-4">
<AlertDialogAction <Form {...form}>
onClick={async () => { <form
await mutateAsync({ onSubmit={form.handleSubmit(onSubmit)}
redisId, id="hook-form-delete-redis"
}) className="grid w-full gap-4"
.then((data) => { >
push(`/dashboard/project/${data?.projectId}`); <FormField
toast.success("Database delete succesfully"); control={form.control}
}) name="projectName"
.catch(() => { render={({ field }) => (
toast.error("Error to delete the database"); <FormItem>
}); <FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter database name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}} }}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-redis"
type="submit"
variant="destructive"
> >
Confirm Confirm
</AlertDialogAction> </Button>
</AlertDialogFooter> </DialogFooter>
</AlertDialogContent> </DialogContent>
</AlertDialog> </Dialog>
); );
}; };