From 99673c7c0c852a35ddf3ceba405889a7ddb9f590 Mon Sep 17 00:00:00 2001 From: adicuco Date: Sun, 18 Jun 2023 16:23:06 +0200 Subject: [PATCH] assignment(post): update & delete --- app/models/post.server.ts | 11 +++ app/routes/posts.admin.$slug.tsx | 163 +++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 app/routes/posts.admin.$slug.tsx diff --git a/app/models/post.server.ts b/app/models/post.server.ts index 77cf5bd..4c7a025 100644 --- a/app/models/post.server.ts +++ b/app/models/post.server.ts @@ -14,3 +14,14 @@ export async function createPost( ) { return prisma.post.create({ data: post }); } + +export async function updatePost( + slug: Post["slug"], + post: Pick +) { + return prisma.post.update({ where: { slug }, data: post }); +} + +export async function deletePost(slug: Post["slug"]) { + return prisma.post.delete({ where: { slug } }); +} diff --git a/app/routes/posts.admin.$slug.tsx b/app/routes/posts.admin.$slug.tsx new file mode 100644 index 0000000..079d5e3 --- /dev/null +++ b/app/routes/posts.admin.$slug.tsx @@ -0,0 +1,163 @@ +import type { ActionArgs, LoaderArgs } from "@remix-run/node"; +import { json, redirect } from "@remix-run/node"; +import { + Form, + useActionData, + useLoaderData, + useNavigation, +} from "@remix-run/react"; +import invariant from "tiny-invariant"; +import { deletePost, getPost, updatePost } from "~/models/post.server"; + +const inputClassName = `w-full rounded border border-gray-500 px-2 py-1 text-lg`; + +export const loader = async ({ params }: LoaderArgs) => { + invariant(params.slug, `params.slug is required`); + + const post = await getPost(params.slug); + invariant(post, `Post not found: ${params.slug}`); + + return json({ post }); +}; + +export const action = async ({ request }: ActionArgs) => { + const formData = await request.formData(); + + const title = formData.get("title"); + const slug = formData.get("slug"); + const slugOriginal = formData.get("slug-original"); + const markdown = formData.get("markdown"); + const intent = formData.get("intent"); + + await new Promise((res) => setTimeout(res, 1000)); + + invariant(typeof slugOriginal === "string", "slug must be a string"); + + switch (intent) { + case "delete": { + await deletePost(slugOriginal); + return redirect("/posts/admin"); + } + case "update": { + const errors = { + title: title ? null : "Title is required", + slug: slug ? null : "Slug is required", + markdown: markdown ? null : "Markdown is required", + }; + + const hasErrors = Object.values(errors).some( + (errorMessage) => errorMessage + ); + if (hasErrors) { + return json(errors); + } + + invariant(typeof title === "string", "title must be a string"); + invariant(typeof slug === "string", "slug must be a string"); + invariant(typeof markdown === "string", "markdown must be a string"); + + await updatePost(slugOriginal, { title, slug, markdown }); + + return redirect(`/posts/admin/${slug}`); + } + default: { + throw new Error(`Invalid intent: ${intent}`); + } + } +}; + +export default function EditPost() { + const { post } = useLoaderData(); + const errors = useActionData(); + + const navigation = useNavigation(); + const isUpdating = + navigation.formMethod === "PUT" && + Boolean(navigation.state === "submitting"); + const isDeleting = + navigation.formMethod === "DELETE" && + Boolean(navigation.state === "submitting"); + + return ( + <> +
+

+ +

+

+ +

+

+ +
+