add delete confirm modal to resources and sites

This commit is contained in:
Milo Schwartz
2024-11-11 23:00:51 -05:00
parent 36bbb412dd
commit 93ea7e4620
6 changed files with 379 additions and 131 deletions

View File

@@ -96,99 +96,113 @@ export default function GeneralForm() {
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
This is the display name of the resource.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="siteId"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Site</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-[350px] justify-between",
!field.value &&
"text-muted-foreground"
)}
>
{field.value
? sites.find(
(site) =>
site.siteId ===
field.value
)?.name
: "Select site"}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-[350px] p-0">
<Command>
<CommandInput placeholder="Search site..." />
<CommandList>
<CommandEmpty>
No site found.
</CommandEmpty>
<CommandGroup>
{sites.map((site) => (
<CommandItem
value={site.name}
key={site.siteId}
onSelect={() => {
form.setValue(
"siteId",
site.siteId
);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
site.siteId ===
field.value
? "opacity-100"
: "opacity-0"
)}
/>
{site.name}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<FormDescription>
This is the site that will be used in the
dashboard.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Update Resource</Button>
</form>
</Form>
<>
<div className="space-y-0.5 select-none mb-6">
<h2 className="text-2xl font-bold tracking-tight">
General Settings
</h2>
<p className="text-muted-foreground">
Configure the general settings for this resource
</p>
</div>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
This is the display name of the resource.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="siteId"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Site</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-[350px] justify-between",
!field.value &&
"text-muted-foreground"
)}
>
{field.value
? sites.find(
(site) =>
site.siteId ===
field.value
)?.name
: "Select site"}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-[350px] p-0">
<Command>
<CommandInput placeholder="Search site..." />
<CommandList>
<CommandEmpty>
No site found.
</CommandEmpty>
<CommandGroup>
{sites.map((site) => (
<CommandItem
value={site.name}
key={site.siteId}
onSelect={() => {
form.setValue(
"siteId",
site.siteId
);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
site.siteId ===
field.value
? "opacity-100"
: "opacity-0"
)}
/>
{site.name}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<FormDescription>
This is the site that will be used in the
dashboard.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Update Resource</Button>
</form>
</Form>
</>
);
}

View File

@@ -25,11 +25,9 @@ const isValidIPAddress = (ip: string) => {
return ipv4Regex.test(ip);
};
export default function ReverseProxyTargets(
props: {
params: Promise<{ resourceId: number }>;
}
) {
export default function ReverseProxyTargets(props: {
params: Promise<{ resourceId: number }>;
}) {
const params = use(props.params);
const [targets, setTargets] = useState<ListTargetsResponse["targets"]>([]);
const [nextId, setNextId] = useState(1);
@@ -39,7 +37,7 @@ export default function ReverseProxyTargets(
if (typeof window !== "undefined") {
const fetchSites = async () => {
const res = await api.get<AxiosResponse<ListTargetsResponse>>(
`/resource/${params.resourceId}/targets`,
`/resource/${params.resourceId}/targets`
);
setTargets(res.data.data.targets);
};
@@ -93,7 +91,7 @@ export default function ReverseProxyTargets(
})
.then((res) => {
setTargets(
targets.filter((target) => target.targetId !== targetId),
targets.filter((target) => target.targetId !== targetId)
);
});
};
@@ -103,8 +101,8 @@ export default function ReverseProxyTargets(
targets.map((target) =>
target.targetId === targetId
? { ...target, enabled: !target.enabled }
: target,
),
: target
)
);
api.post(`/target/${targetId}`, {
enabled: !targets.find((target) => target.targetId === targetId)
@@ -115,7 +113,14 @@ export default function ReverseProxyTargets(
};
return (
<div className="space-y-6">
<div>
<div className="space-y-0.5 select-none mb-6">
<h2 className="text-2xl font-bold tracking-tight">Targets</h2>
<p className="text-muted-foreground">
Setup the targets for the reverse proxy
</p>
</div>
<form
onSubmit={(e) => {
e.preventDefault();
@@ -192,9 +197,7 @@ export default function ReverseProxyTargets(
</Select>
</div>
</div>
<Button type="submit">
<PlusCircle className="mr-2 h-4 w-4" /> Add Target
</Button>
<Button type="submit">Add Target</Button>
</form>
<div className="space-y-4">

View File

@@ -15,6 +15,8 @@ import { useRouter } from "next/navigation";
import api from "@app/api";
import CreateResourceForm from "./CreateResourceForm";
import { useState } from "react";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { set } from "zod";
export type ResourceRow = {
id: number;
@@ -33,6 +35,20 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
const router = useRouter();
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedResource, setSelectedResource] =
useState<ResourceRow | null>();
const deleteResource = (resourceId: number) => {
api.delete(`/resource/${resourceId}`)
.catch((e) => {
console.error("Error deleting resource", e);
})
.then(() => {
router.refresh();
setIsDeleteModalOpen(false);
});
};
const columns: ColumnDef<ResourceRow>[] = [
{
@@ -78,16 +94,6 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
const resourceRow = row.original;
const deleteResource = (resourceId: number) => {
api.delete(`/resource/${resourceId}`)
.catch((e) => {
console.error("Error deleting resource", e);
})
.then(() => {
router.refresh();
});
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -106,9 +112,10 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
</DropdownMenuItem>
<DropdownMenuItem>
<button
onClick={() =>
deleteResource(resourceRow.id)
}
onClick={() => {
setSelectedResource(resourceRow);
setIsDeleteModalOpen(true);
}}
className="text-red-600 hover:text-red-800 hover:underline cursor-pointer"
>
Delete
@@ -128,6 +135,43 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
setOpen={setIsCreateModalOpen}
/>
{selectedResource && (
<ConfirmDeleteDialog
open={isDeleteModalOpen}
setOpen={(val) => {
setIsDeleteModalOpen(val);
setSelectedResource(null);
}}
dialog={
<div>
<p className="mb-2">
Are you sure you want to remove the resource{" "}
<b>
{selectedResource?.name ||
selectedResource?.id}
</b>{" "}
from the organization?
</p>
<p className="mb-2">
Once removed, the resource will no longer be
accessible. All targets attached to the resource
will be removed.
</p>
<p>
To confirm, please type the name of the resource
below.
</p>
</div>
}
buttonText="Confirm delete resource"
onConfirm={async () => deleteResource(selectedResource!.id)}
string={selectedResource.name}
title="Delete resource"
/>
)}
<ResourcesDataTable
columns={columns}
data={resources}