😽
【Convex】NextJs14 と Convex【#21 Convex Sonner】
【#21 Convex Sonner】
YouTube: https://youtu.be/8QhqFxuJ8EM
今回はshadcn-uiから「sonner」をインストールして、
カードを作成した際にポップアップで
メッセージが表示されるように実装を進めていきます。
npx shadcn-ui@latest add sonner
app/layout.tsx
import { Suspense } from "react";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ConvexClientProvider } from "@/components/providers/convex-client-provider";
import { Toaster } from "@/components/ui/sonner";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<Toaster />
<Suspense fallback={"Loading..."}>
<ConvexClientProvider>{children}</ConvexClientProvider>
</Suspense>
</body>
</html>
);
}
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={true} 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>
);
};
Discussion