📚
【NextJs14】NextJs14 と 便利なライブラリ【#30Create Todo Form】
【#30Create Todo Form】
YouTube: https://youtu.be/5SH2X-o-iPQ
今回はTodoを作成するフォームを実装します。
app/(main)/account/page.tsx
import { CheckCircle, Trash2 } from "lucide-react";
import { formatDistanceToNow } from "date-fns";
import { db } from "@/lib/db";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { TodoForm } from "./_components/todo-form";
const getTodos = async () => {
const todos = await db.todo.findMany({
orderBy: {
createdAt: "asc",
},
});
return todos;
};
const AccountPage = async () => {
const todos = await getTodos();
return (
<div className="p-6 w-full h-full">
<div className="w-full h-full flex flex-col gap-y-3">
<div className="flex flex-col md:flex-row items-center justify-between gap-2">
<h2 className="text-center md:text-left text-3xl font-bold">Todos</h2>
<TodoForm />
</div>
<Separator />
<ul className="w-full space-y-3">
{todos.map((todo, idx) => (
<li
key={todo.id}
className="flex flex-col md:flex-row items-center justify-between gap-x-4 gap-y-2"
>
<div className="flex flex-col md:flex-row flex-1 items-center justify-between gap-x-3">
<p className="text-xl font-semibold">
<span className="pr-2">{idx + 1}</span>
<span className={todo.isCompleted ? "line-through" : ""}>
{todo.title}
</span>
</p>
<p>
{formatDistanceToNow(todo.createdAt, { addSuffix: true })}
</p>
</div>
<div className="flex items-center gap-x-2">
<Button variant={todo.isCompleted ? "Completed" : "secondary"}>
<CheckCircle className="h-5 w-5" />
</Button>
<Button variant="destructive">
<Trash2 className="h-5 w-5" />
</Button>
</div>
</li>
))}
</ul>
</div>
</div>
);
};
export default AccountPage;
app/(main)/account/_components/todo-form.tsx
"use client";
import * as z from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { CreateTodoSchema } from "@/types/schema";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
export const TodoForm = () => {
const form = useForm<z.infer<typeof CreateTodoSchema>>({
resolver: zodResolver(CreateTodoSchema),
defaultValues: {
title: "",
},
});
const onSubmit = (values: z.infer<typeof CreateTodoSchema>) => {
alert(JSON.stringify(values));
};
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex items-center gap-x-1 w-full max-w-xl"
>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem className="w-full">
<FormControl>
<Input {...field} type="text" placeholder="Enter your todo" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" variant="primary">
Create
</Button>
</form>
</Form>
);
};
types/schema.ts
import * as z from "zod";
export const CreateTodoSchema = z.object({
title: z.string().min(1, {
message: "Title is required.",
}),
});
Discussion