Update + Delete articles

This commit is contained in:
cdricms
2025-02-25 17:49:37 +01:00
parent 793e3748f9
commit a3f716446c
16 changed files with 764 additions and 3357 deletions

View File

@@ -0,0 +1,27 @@
"use client";
import { useApi } from "@/hooks/use-api";
import { Blog } from "@/types/types";
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
import { useParams } from "next/navigation";
const BlogEditor = dynamic(
() => import("@/components/article/edit").then((mod) => mod.default),
{
ssr: false,
loading: () => <Loader2 className="animate-spin" />,
},
);
export default function Page() {
const params = useParams<{ uuid: string }>();
const {
data: blog,
error,
mutate,
success,
isLoading,
} = useApi<Blog>(`/blogs/${params.uuid}`, {}, false, false);
return <BlogEditor blog={blog} />;
}

View File

@@ -1,184 +0,0 @@
"use client";
import EditableText from "@/components/editable-text";
import { LocalEditor } from "@/components/editor";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Dialog, DialogTrigger, DialogContent } from "@/components/ui/dialog";
import useApiMutation, { useApi } from "@/hooks/use-api";
import sluggify from "@/lib/sluggify";
import { Category, NewBlog } from "@/types/types";
import { useEffect, useMemo, useState } from "react";
import { DialogTitle } from "@radix-ui/react-dialog";
import { useToast } from "@/hooks/use-toast";
import {
ActionButton,
ActionButtonDefault,
ActionButtonError,
ActionButtonLoading,
ActionButtonSuccess,
} from "@/components/action-button";
import ComboBox from "@/components/ui/combobox";
export default function BlogEditor() {
const { toast } = useToast();
const [title, setTitle] = useState("");
const [imageUrl, setImageUrl] = useState<string>("/placeholder.svg");
const [category, setCategory] = useState("");
const { data } = useApi<Category[]>(
"/blogs/categories",
undefined,
false,
false,
);
const [categories, setCategories] = useState<string[]>([]);
useEffect(() => {
if (data) setCategories(data.map((c) => c.category) ?? []);
}, [data]);
const content = localStorage.getItem("blog_draft") ?? "";
useEffect(() => {
const localImage = localStorage.getItem("blog_draft_image");
setImageUrl(
localImage && localImage.length > 0
? localImage
: "/placeholder.svg",
);
}, []);
const [summary, setSummary] = useState("");
const slug = useMemo(() => sluggify(title), [title]);
const {
trigger: newBlog,
isMutating: isSending,
isSuccess,
error,
} = useApiMutation<undefined, NewBlog>(
"/blogs/new",
{},
"POST",
true,
false,
);
return (
<section className="m-10 flex flex-col gap-4">
<div className="flex flex-col gap-4">
<EditableText onChange={setTitle}>
<h1>
{title.length > 0 ? title : "Un titre doit-être fourni"}
</h1>
</EditableText>
<p className="italic">{slug}</p>
<Dialog>
<DialogTrigger>
<img
src={imageUrl}
alt="Blog cover"
className="w-full h-60 object-cover cursor-pointer rounded-lg"
/>
</DialogTrigger>
<DialogTitle></DialogTitle>
<DialogContent>
<Input
type="text"
placeholder="Enter image URL"
value={imageUrl}
onChange={(e) => {
setImageUrl(e.target.value);
localStorage.setItem(
"blog_draft_image",
e.currentTarget.value,
);
}}
/>
</DialogContent>
</Dialog>
<div className="flex gap-4">
<Input
type="text"
placeholder="Enter a summary"
value={summary}
onChange={(e) => setSummary(e.target.value)}
/>
<ComboBox
value={category}
setValue={setCategory}
key={categories.join(",")}
elements={categories}
trigger={(value) => (
<Button>
{value ?? "Selectionner une catégorie"}
</Button>
)}
onSubmit={(value) => {
setCategories((prev) => {
if (prev.includes(value)) return prev;
return [...prev, value];
});
setCategory(value);
}}
>
{(Item, element) => (
<Item value={element} key={element} label={element}>
{element}
</Item>
)}
</ComboBox>
</div>
</div>
<div className="flex">
<div className="flex-1">
<LocalEditor content={content} setTitle={setTitle} />
</div>
</div>
<ActionButton
isLoading={isSending}
isSuccess={isSuccess}
error={error}
onClick={async () => {
try {
const blogContent = localStorage.getItem("blog_draft");
if (!blogContent) return;
if (title.length < 1) return;
const res = await newBlog({
title,
summary,
image: imageUrl,
slug,
content: blogContent,
category,
});
if (!res) {
toast({ title: "Aucune réponse du serveur." });
return;
}
if (res.status === "Error") {
toast({
title: "Erreur.",
content: "Une erreur est survenue.",
});
}
if (res.data) console.log(res.data);
return res;
} catch (error: any) {
toast({
title: "Erreur.",
content: "Une erreur est survenue.",
});
}
}}
>
<ActionButtonDefault>Publier</ActionButtonDefault>
<ActionButtonSuccess />
<ActionButtonError />
<ActionButtonLoading />
</ActionButton>
</section>
);
}

View File

@@ -3,10 +3,13 @@
import { Loader2 } from "lucide-react";
import dynamic from "next/dynamic";
const BlogEditor = dynamic(() => import("./new").then((mod) => mod.default), {
ssr: false,
loading: () => <Loader2 className="animate-spin" />,
});
const BlogEditor = dynamic(
() => import("@/components/article/edit").then((mod) => mod.default),
{
ssr: false,
loading: () => <Loader2 className="animate-spin" />,
},
);
export default function Page() {
return <BlogEditor />;