refactor: delete template logic
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { type ApiError, fetchApi } from "@/api";
|
import { type ApiError, fetchApi } from "@/api";
|
||||||
import { promiseOrThrow } from "@/lib/errors";
|
import { promiseOrThrow } from "@/lib/errors";
|
||||||
|
import type { QueryStatus } from "@/lib/query";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import useSWR, { useSWRConfig } from "swr";
|
import useSWR, { useSWRConfig } from "swr";
|
||||||
import type {
|
import type {
|
||||||
@@ -8,7 +9,7 @@ import type {
|
|||||||
TemplateImage,
|
TemplateImage,
|
||||||
TemplateMeta,
|
TemplateMeta,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { QueryStatus } from "@/lib/query";
|
import { setDefaultAutoSelectFamilyAttemptTimeout } from "net";
|
||||||
|
|
||||||
function useTemplates() {
|
function useTemplates() {
|
||||||
return useSWR(
|
return useSWR(
|
||||||
@@ -27,22 +28,32 @@ function useTemplate(name: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useDeleteTemplate() {
|
function useDeleteTemplate() {
|
||||||
|
const [status, setStatus] = useState<QueryStatus<ApiError>>({ type: "idle" });
|
||||||
const { mutate } = useSWRConfig();
|
const { mutate } = useSWRConfig();
|
||||||
|
|
||||||
const deleteTemplate = useCallback(
|
const deleteTemplate = useCallback(
|
||||||
async (templateName: string) => {
|
async (templateName: string) => {
|
||||||
mutate(
|
setStatus({ type: "loading" });
|
||||||
"/templates",
|
try {
|
||||||
fetchApi(`/templates/${templateName}`, { method: "DELETE" }),
|
await mutate(
|
||||||
{
|
"/templates",
|
||||||
populateCache: (_, templates) =>
|
fetchApi(`/templates/${templateName}`, { method: "DELETE" }),
|
||||||
templates?.filter((it: TemplateMeta) => it.name !== templateName),
|
{
|
||||||
revalidate: false,
|
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],
|
[mutate],
|
||||||
);
|
);
|
||||||
return deleteTemplate;
|
|
||||||
|
return { deleteTemplate, status };
|
||||||
}
|
}
|
||||||
|
|
||||||
function useCreateTemplate() {
|
function useCreateTemplate() {
|
||||||
|
@@ -4,24 +4,10 @@ import { Dialog, DialogTrigger } from "@/components/ui/dialog";
|
|||||||
import { PageHeader } from "@/components/ui/page-header.tsx";
|
import { PageHeader } from "@/components/ui/page-header.tsx";
|
||||||
import { Page } from "@/components/ui/page.tsx";
|
import { Page } from "@/components/ui/page.tsx";
|
||||||
import { SidebarProvider } from "@/components/ui/sidebar.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 { Toaster } from "@/components/ui/toaster";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { Plus } from "lucide-react";
|
||||||
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 { NewTemplateDialog } from "./new-template-dialog";
|
import { NewTemplateDialog } from "./new-template-dialog";
|
||||||
|
import { TemplateTable } from "./template-table";
|
||||||
|
|
||||||
function TemplatesDashboard() {
|
function TemplatesDashboard() {
|
||||||
return (
|
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 };
|
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