💬
【Convex】NextJs14 と Convex【#22 Convex New Card Button】
【#22 Convex New Card Button】
YouTube: https://youtu.be/ip_fmFZJBDI
今回はモーダルを開いてカードを作成するボタンを実装します。
現在モーダルを強制的に開いた状態にしていますので、
「true」の値を「isOpen」に戻して
正常な状態にします。
components/modals/create-card-modal.tsx
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { toast } from "sonner";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Form, FormControl, FormField } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { useCreateCardModal } from "@/store/use-create-card-modals";
import { useApiMutation } from "@/hooks/use-api-mutation";
import { api } from "@/convex/_generated/api";
const formSchema = z.object({
title: z
.string()
.min(2, { message: "Title must be at least 2 characters." })
.max(50, { message: "Title must be less than 50 characters." }),
cardType: z.string({
required_error: "Card type is required.",
invalid_type_error: "Card type is required.",
}),
});
export const CreateCardModal = () => {
const { pending, mutate } = useApiMutation(api.card.create);
const { isOpen, onClose, initialValues } = useCreateCardModal();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
title: "",
cardType: "comment",
},
});
const onSubmit = (values: z.infer<typeof formSchema>) => {
mutate({
orgId: initialValues.id,
title: values.title,
cardType: values.cardType,
})
.then(() => {
toast.success("Card created");
onClose();
})
.catch((error) => {
toast.error("Failed to create card");
console.log(error);
});
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>Create Card</DialogTitle>
<DialogDescription>
Select card type & Enter card title
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="cardType"
render={({ field }) => (
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Card type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="comment">Comment</SelectItem>
<SelectItem value="board">Board</SelectItem>
</SelectContent>
</Select>
)}
/>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<Input {...field} placeholder="Card title" />
)}
/>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="outline">
Cancel
</Button>
</DialogClose>
<Button type="submit">Create</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};
カード作成用のボタンはカードのリストの先頭に配置するので、
カードを表示するコンポーネントをpage.tsxに設定します。
app/(main)/page.tsx
"use client";
import { useOrganization } from "@clerk/nextjs";
import { CardList } from "./_components/card-list";
export default function Home() {
const { organization } = useOrganization();
return (
<div className="p-4 h-full">
{!organization ? null : <CardList orgId={organization.id} />}
</div>
);
}
app/(main)/_components/card-list.tsx
"use client";
import { NewCardButton } from "./new-card-button";
interface Props {
orgId: string;
}
export const CardList = ({ orgId }: Props) => {
return (
<div>
<h2>Card List</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-5 mt-8 pb-10">
<NewCardButton orgId={orgId} />
</div>
</div>
);
};
app/(main)/_components/new-card-button.tsx
"use client";
import { useCreateCardModal } from "@/store/use-create-card-modals";
import { Plus } from "lucide-react";
interface Props {
orgId: string;
}
export const NewCardButton = ({ orgId }: Props) => {
const { onOpen } = useCreateCardModal();
return (
<button
className="col-span-1 aspect-[100/128] bg-blue-700 rounded-lg hover:bg-blue-900 flex flex-col items-center justify-center py-6"
type="button"
onClick={() => onOpen(orgId)}
>
<Plus className="h-12 w-12 text-white stroke-1" />
<p className="text-sm text-white font-light">New Card</p>
</button>
);
};
Discussion