110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
import type { ActionArgs } from "@remix-run/node";
|
|
import { json, redirect } from "@remix-run/node";
|
|
import { Form, useActionData } from "@remix-run/react";
|
|
import { useEffect, useRef } from "react";
|
|
|
|
import { createNote } from "~/models/note.server";
|
|
import { requireUserId } from "~/session.server";
|
|
|
|
export const action = async ({ request }: ActionArgs) => {
|
|
const userId = await requireUserId(request);
|
|
|
|
const formData = await request.formData();
|
|
const title = formData.get("title");
|
|
const body = formData.get("body");
|
|
|
|
if (typeof title !== "string" || title.length === 0) {
|
|
return json(
|
|
{ errors: { body: null, title: "Title is required" } },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
if (typeof body !== "string" || body.length === 0) {
|
|
return json(
|
|
{ errors: { body: "Body is required", title: null } },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const note = await createNote({ body, title, userId });
|
|
|
|
return redirect(`/notes/${note.id}`);
|
|
};
|
|
|
|
export default function NewNotePage() {
|
|
const actionData = useActionData<typeof action>();
|
|
const titleRef = useRef<HTMLInputElement>(null);
|
|
const bodyRef = useRef<HTMLTextAreaElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (actionData?.errors?.title) {
|
|
titleRef.current?.focus();
|
|
} else if (actionData?.errors?.body) {
|
|
bodyRef.current?.focus();
|
|
}
|
|
}, [actionData]);
|
|
|
|
return (
|
|
<Form
|
|
method="post"
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: 8,
|
|
width: "100%",
|
|
}}
|
|
>
|
|
<div>
|
|
<label className="flex w-full flex-col gap-1">
|
|
<span>Title: </span>
|
|
<input
|
|
ref={titleRef}
|
|
name="title"
|
|
className="flex-1 rounded-md border-2 border-blue-500 px-3 text-lg leading-loose"
|
|
aria-invalid={actionData?.errors?.title ? true : undefined}
|
|
aria-errormessage={
|
|
actionData?.errors?.title ? "title-error" : undefined
|
|
}
|
|
/>
|
|
</label>
|
|
{actionData?.errors?.title ? (
|
|
<div className="pt-1 text-red-700" id="title-error">
|
|
{actionData.errors.title}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="flex w-full flex-col gap-1">
|
|
<span>Body: </span>
|
|
<textarea
|
|
ref={bodyRef}
|
|
name="body"
|
|
rows={8}
|
|
className="w-full flex-1 rounded-md border-2 border-blue-500 px-3 py-2 text-lg leading-6"
|
|
aria-invalid={actionData?.errors?.body ? true : undefined}
|
|
aria-errormessage={
|
|
actionData?.errors?.body ? "body-error" : undefined
|
|
}
|
|
/>
|
|
</label>
|
|
{actionData?.errors?.body ? (
|
|
<div className="pt-1 text-red-700" id="body-error">
|
|
{actionData.errors.body}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="text-right">
|
|
<button
|
|
type="submit"
|
|
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 focus:bg-blue-400"
|
|
>
|
|
Save
|
|
</button>
|
|
</div>
|
|
</Form>
|
|
);
|
|
}
|