refactor: delete template logic
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { type ApiError, fetchApi } from "@/api";
|
||||
import { promiseOrThrow } from "@/lib/errors";
|
||||
import type { QueryStatus } from "@/lib/query";
|
||||
import { useCallback, useState } from "react";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
import type {
|
||||
@@ -8,7 +9,7 @@ import type {
|
||||
TemplateImage,
|
||||
TemplateMeta,
|
||||
} from "./types";
|
||||
import { QueryStatus } from "@/lib/query";
|
||||
import { setDefaultAutoSelectFamilyAttemptTimeout } from "net";
|
||||
|
||||
function useTemplates() {
|
||||
return useSWR(
|
||||
@@ -27,22 +28,32 @@ function useTemplate(name: string) {
|
||||
}
|
||||
|
||||
function useDeleteTemplate() {
|
||||
const [status, setStatus] = useState<QueryStatus<ApiError>>({ type: "idle" });
|
||||
const { mutate } = useSWRConfig();
|
||||
|
||||
const deleteTemplate = useCallback(
|
||||
async (templateName: string) => {
|
||||
mutate(
|
||||
"/templates",
|
||||
fetchApi(`/templates/${templateName}`, { method: "DELETE" }),
|
||||
{
|
||||
populateCache: (_, templates) =>
|
||||
templates?.filter((it: TemplateMeta) => it.name !== templateName),
|
||||
revalidate: false,
|
||||
},
|
||||
);
|
||||
setStatus({ type: "loading" });
|
||||
try {
|
||||
await mutate(
|
||||
"/templates",
|
||||
fetchApi(`/templates/${templateName}`, { method: "DELETE" }),
|
||||
{
|
||||
populateCache: (_, templates) =>
|
||||
templates?.filter((it: TemplateMeta) => it.name !== templateName),
|
||||
revalidate: false,
|
||||
throwOnError: true,
|
||||
},
|
||||
);
|
||||
setStatus({ type: "ok" });
|
||||
} catch (error: unknown) {
|
||||
setStatus({ type: "error", error: error as ApiError });
|
||||
}
|
||||
},
|
||||
[mutate],
|
||||
);
|
||||
return deleteTemplate;
|
||||
|
||||
return { deleteTemplate, status };
|
||||
}
|
||||
|
||||
function useCreateTemplate() {
|
||||
|
@@ -4,24 +4,10 @@ import { Dialog, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { PageHeader } from "@/components/ui/page-header.tsx";
|
||||
import { Page } from "@/components/ui/page.tsx";
|
||||
import { SidebarProvider } from "@/components/ui/sidebar.tsx";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { ToastAction } from "@radix-ui/react-toast";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import dayjs from "dayjs";
|
||||
import { Pencil, Plus, Trash2 } from "lucide-react";
|
||||
import React from "react";
|
||||
import { useDeleteTemplate, useTemplates } from "./api";
|
||||
import { Plus } from "lucide-react";
|
||||
import { NewTemplateDialog } from "./new-template-dialog";
|
||||
import { TemplateTable } from "./template-table";
|
||||
|
||||
function TemplatesDashboard() {
|
||||
return (
|
||||
@@ -56,100 +42,4 @@ function Main() {
|
||||
);
|
||||
}
|
||||
|
||||
const TemplateTable = React.memo(_TemplateTable, () => true);
|
||||
function _TemplateTable() {
|
||||
const { data: templates, isLoading } = useTemplates();
|
||||
const deleteTemplate = useDeleteTemplate();
|
||||
const { toast } = useToast();
|
||||
|
||||
function placeholder() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="w-full py-2 space-y-2">
|
||||
<Skeleton className="w-full h-10" />
|
||||
<Skeleton className="w-full h-10" />
|
||||
<Skeleton className="w-full h-10" />
|
||||
<Skeleton className="w-full h-10" />
|
||||
<Skeleton className="w-full h-10" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (templates?.length === 0) {
|
||||
return <p className="text-center py-2 opacity-80">No templates found.</p>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function _deleteTemplate(templateName: string) {
|
||||
try {
|
||||
await deleteTemplate(templateName);
|
||||
toast({
|
||||
title: "Template deleted!",
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Failed to delete template.",
|
||||
action: (
|
||||
<ToastAction
|
||||
altText="Try again"
|
||||
onClick={() => _deleteTemplate(templateName)}
|
||||
>
|
||||
Try again
|
||||
</ToastAction>
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>Created at</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
{templates ? (
|
||||
<TableBody>
|
||||
{templates.map((template) => (
|
||||
<TableRow key={template.name}>
|
||||
<TableCell>{template.name}</TableCell>
|
||||
<TableCell>
|
||||
{template.description || "No description"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{dayjs(template.createdOn).format("YYYY/MM/DD")}
|
||||
</TableCell>
|
||||
<TableCell className="flex justify-end space-x-1">
|
||||
<Button variant="outline" size="icon" asChild>
|
||||
<Link to={`/templates/${template.name}`} preload="intent">
|
||||
<div>
|
||||
<Pencil />
|
||||
<span className="sr-only">Edit template</span>
|
||||
</div>
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => _deleteTemplate(template.name)}
|
||||
>
|
||||
<Trash2 className="text-destructive" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
) : null}
|
||||
</Table>
|
||||
{placeholder()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { TemplatesDashboard };
|
||||
|
124
web/src/templates/template-table.tsx
Normal file
124
web/src/templates/template-table.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableHead,
|
||||
TableBody,
|
||||
TableCell,
|
||||
} from "@/components/ui/table";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { ToastAction } from "@radix-ui/react-toast";
|
||||
import dayjs from "dayjs";
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTemplates, useDeleteTemplate } from "./api";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import type { TemplateMeta } from "./types";
|
||||
|
||||
const TemplateTable = React.memo(_TemplateTable, () => true);
|
||||
|
||||
function _TemplateTable() {
|
||||
const { data: templates, isLoading } = useTemplates();
|
||||
|
||||
function placeholder() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="w-full py-2 space-y-2">
|
||||
<Skeleton className="w-full h-10" />
|
||||
<Skeleton className="w-full h-10" />
|
||||
<Skeleton className="w-full h-10" />
|
||||
<Skeleton className="w-full h-10" />
|
||||
<Skeleton className="w-full h-10" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (templates?.length === 0) {
|
||||
return <p className="text-center py-2 opacity-80">No templates found.</p>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>Created at</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
{templates ? (
|
||||
<TableBody>
|
||||
{templates.map((template) => (
|
||||
<TemplateTableRow key={template.name} template={template} />
|
||||
))}
|
||||
</TableBody>
|
||||
) : null}
|
||||
</Table>
|
||||
{placeholder()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TemplateTableRow({ template }: { template: TemplateMeta }) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>{template.name}</TableCell>
|
||||
<TableCell>{template.description || "No description"}</TableCell>
|
||||
<TableCell>{dayjs(template.createdOn).format("YYYY/MM/DD")}</TableCell>
|
||||
<TableCell className="flex justify-end space-x-1">
|
||||
<Button variant="outline" size="icon" asChild>
|
||||
<Link to={`/templates/${template.name}`} preload="intent">
|
||||
<div>
|
||||
<Pencil />
|
||||
<span className="sr-only">Edit template</span>
|
||||
</div>
|
||||
</Link>
|
||||
</Button>
|
||||
<DeleteTemplateButton template={template} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteTemplateButton({ template }: { template: TemplateMeta }) {
|
||||
const { deleteTemplate, status } = useDeleteTemplate();
|
||||
const { toast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (status.type === "error") {
|
||||
let toastDescription: string;
|
||||
switch (status.error.type) {
|
||||
case "NETWORK":
|
||||
toastDescription = "Network error";
|
||||
break;
|
||||
default:
|
||||
toastDescription = "Unexpected error";
|
||||
break;
|
||||
}
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Failed to delete template",
|
||||
description: toastDescription,
|
||||
});
|
||||
}
|
||||
}, [status, toast]);
|
||||
|
||||
async function _deleteTemplate() {
|
||||
await deleteTemplate(template.name);
|
||||
toast({ title: "Template deleted!" });
|
||||
}
|
||||
|
||||
return (
|
||||
<Button variant="outline" size="icon" onClick={_deleteTemplate}>
|
||||
<Trash2 className="text-destructive" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export { TemplateTable };
|
Reference in New Issue
Block a user