Merge pull request #777 from 190km/delete-app-input-validation

feat: Add an input box to enter the project name for a second confirmation when deleting the application.
This commit is contained in:
Mauricio Siu
2024-11-28 20:36:40 -06:00
committed by GitHub
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();
return ( const form = useForm<DeleteApplication>({
<AlertDialog> defaultValues: {
<AlertDialogTrigger asChild> projectName: "",
<Button variant="ghost" isLoading={isLoading}> },
<TrashIcon className="size-4 text-muted-foreground" /> resolver: zodResolver(deleteApplicationSchema),
</Button> });
</AlertDialogTrigger>
<AlertDialogContent> const onSubmit = async (formData: DeleteApplication) => {
<AlertDialogHeader> const expectedName = `${data?.name}/${data?.appName}`;
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> if (formData.projectName === expectedName) {
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
application
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({ await mutateAsync({
applicationId, applicationId,
}) })
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Application deleted successfully");
toast.success("Application delete succesfully"); setIsOpen(false);
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete Application"); toast.error("Error deleting the application");
}); });
} else {
form.setError("projectName", {
message: "Project name does not match",
});
}
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
application. If you are sure please enter the application name to
delete this application.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-application"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<FormItem>
<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();
return ( const form = useForm<DeleteMariadb>({
<AlertDialog> defaultValues: {
<AlertDialogTrigger asChild> projectName: "",
<Button variant="ghost" isLoading={isLoading}> },
<TrashIcon className="size-4 text-muted-foreground " /> resolver: zodResolver(deleteMariadbSchema),
</Button> });
</AlertDialogTrigger>
<AlertDialogContent> const onSubmit = async (formData: DeleteMariadb) => {
<AlertDialogHeader> const expectedName = `${data?.name}/${data?.appName}`;
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> if (formData.projectName === expectedName) {
<AlertDialogDescription> await mutateAsync({ mariadbId })
This action cannot be undone. This will permanently delete the
database
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
mariadbId,
})
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully"); toast.success("Database deleted successfully");
setIsOpen(false);
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete the database"); toast.error("Error deleting the database");
}); });
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
database. If you are sure please enter the database name to delete
this database.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-mariadb"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<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();
return ( const form = useForm<DeleteMongo>({
<AlertDialog> defaultValues: {
<AlertDialogTrigger asChild> projectName: "",
<Button variant="ghost" isLoading={isLoading}> },
<TrashIcon className="size-4 text-muted-foreground " /> resolver: zodResolver(deleteMongoSchema),
</Button> });
</AlertDialogTrigger>
<AlertDialogContent> const onSubmit = async (formData: DeleteMongo) => {
<AlertDialogHeader> const expectedName = `${data?.name}/${data?.appName}`;
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> if (formData.projectName === expectedName) {
<AlertDialogDescription> await mutateAsync({ mongoId })
This action cannot be undone. This will permanently delete the
database
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
mongoId,
})
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully"); toast.success("Database deleted successfully");
setIsOpen(false);
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete the database"); toast.error("Error deleting the database");
}); });
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
database. If you are sure please enter the database name to delete
this database.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-mongo"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<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();
return ( const form = useForm<DeleteMysql>({
<AlertDialog> defaultValues: {
<AlertDialogTrigger asChild> projectName: "",
<Button variant="ghost" isLoading={isLoading}> },
<TrashIcon className="size-4 text-muted-foreground " /> resolver: zodResolver(deleteMysqlSchema),
</Button> });
</AlertDialogTrigger>
<AlertDialogContent> const onSubmit = async (formData: DeleteMysql) => {
<AlertDialogHeader> const expectedName = `${data?.name}/${data?.appName}`;
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> if (formData.projectName === expectedName) {
<AlertDialogDescription> await mutateAsync({ mysqlId })
This action cannot be undone. This will permanently delete the
database
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
mysqlId,
})
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully"); toast.success("Database deleted successfully");
setIsOpen(false);
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete the database"); toast.error("Error deleting the database");
}); });
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
database. If you are sure please enter the database name to delete
this database.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-mysql"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<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();
return ( const form = useForm<DeletePostgres>({
<AlertDialog> defaultValues: {
<AlertDialogTrigger asChild> projectName: "",
<Button variant="ghost" isLoading={isLoading}> },
<TrashIcon className="size-4 text-muted-foreground " /> resolver: zodResolver(deletePostgresSchema),
</Button> });
</AlertDialogTrigger>
<AlertDialogContent> const onSubmit = async (formData: DeletePostgres) => {
<AlertDialogHeader> const expectedName = `${data?.name}/${data?.appName}`;
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> if (formData.projectName === expectedName) {
<AlertDialogDescription> await mutateAsync({ postgresId })
This action cannot be undone. This will permanently delete the
database
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
postgresId,
})
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully"); toast.success("Database deleted successfully");
setIsOpen(false);
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete the database"); toast.error("Error deleting the database");
}); });
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
database. If you are sure please enter the database name to delete
this database.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-postgres"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<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();
return ( const form = useForm<DeleteRedis>({
<AlertDialog> defaultValues: {
<AlertDialogTrigger asChild> projectName: "",
<Button variant="ghost" isLoading={isLoading}> },
<TrashIcon className="size-4 text-muted-foreground " /> resolver: zodResolver(deleteRedisSchema),
</Button> });
</AlertDialogTrigger>
<AlertDialogContent> const onSubmit = async (formData: DeleteRedis) => {
<AlertDialogHeader> const expectedName = `${data?.name}/${data?.appName}`;
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> if (formData.projectName === expectedName) {
<AlertDialogDescription> await mutateAsync({ redisId })
This action cannot be undone. This will permanently delete the
database
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
redisId,
})
.then((data) => { .then((data) => {
push(`/dashboard/project/${data?.projectId}`); push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully"); toast.success("Database deleted successfully");
setIsOpen(false);
}) })
.catch(() => { .catch(() => {
toast.error("Error to delete the database"); toast.error("Error deleting the database");
}); });
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
database. If you are sure please enter the database name to delete
this database.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-redis"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<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>
); );
}; };