add card creation form

This commit is contained in:
ppc
2024-08-07 20:18:38 +01:00
parent f3fdbd08f2
commit b23dd812c3
2 changed files with 167 additions and 0 deletions
@@ -0,0 +1,148 @@
"use client";
import {z} from "zod";
import React from "react";
import {Form, FormProvider, useForm} from "react-hook-form";
import {FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
import {generateAimeCardId, validateAimeCardId} from "@/lib/aime-card";
import {useMediaQuery} from "@/hooks/use-media-query";
import {zodResolver} from "@hookform/resolvers/zod";
import {Button} from "@/components/ui/button";
import {Input} from "@/components/ui/input";
import {AimeCard, db} from "@/lib/database";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger
} from "@/components/ui/dialog";
import {
Drawer, DrawerClose,
DrawerContent,
DrawerDescription, DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger
} from "@/components/ui/drawer";
import {Check, X} from "lucide-react";
const schema = z.object({
name: z.string().min(1, {
message: "A name/description must be provided"
}),
id: z.string().refine(validateAimeCardId, {
message: "Card id must be 20 digits and not start with the number 3"
})
});
export function CardCreationDialog(props: { children: Readonly<React.ReactNode> }) {
const [open, setOpen] = React.useState(false);
const isDesktop = useMediaQuery("(min-width: 768px)");
if (isDesktop) {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
{props.children}
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Add new card</DialogTitle>
</DialogHeader>
<AimeCardCreationForm callback={() => setOpen(false)}/>
</DialogContent>
</Dialog>
);
}
return (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild>
{props.children}
</DrawerTrigger>
<DrawerContent>
<DrawerHeader className="text-left">
<DrawerTitle>Add card</DrawerTitle>
<DrawerDescription>
Enter the card details below, and press submit to add it to your profile.
</DrawerDescription>
</DrawerHeader>
<div className="px-4">
<AimeCardCreationForm callback={() => setOpen(false)}/>
</div>
<DrawerFooter className="pt-2">
<DrawerClose asChild>
<Button variant="outline">
<X className="w-4 h-4 mr-2"/> Cancel
</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}
function AimeCardCreationForm(props: { callback?: () => void }) {
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema)
});
async function onSubmit(values: z.infer<typeof schema>) {
const card: AimeCard = {
name: values.name,
id: values.id.replaceAll(" ", "")
};
const isDuplicate = await db.cards.where("id").equals(card.id).limit(1).count();
if (!isDuplicate) {
await db.cards.add(card);
} else {
// todo toast
}
if (props.callback) {
props.callback();
}
}
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField name="name" control={form.control}
render={({field}) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Card #1" {...field} />
</FormControl>
<FormMessage/>
</FormItem>
)}/>
<FormField name="id" control={form.control}
render={({field}) => (
<FormItem>
<FormLabel>Card Number</FormLabel>
<FormControl>
<Input placeholder="0000 0000 0000 0000 0000" {...field} />
</FormControl>
<FormDescription>
If you&apos;ve got a card, enter the matrix code here. If not, you can
<span className="font-bold hover:cursor-pointer ml-1"
onClick={() => form.setValue("id", generateAimeCardId())}>
generate a new one.
</span>
</FormDescription>
<FormMessage/>
</FormItem>
)}/>
<Button type="submit" className="w-full">
<Check className="w-4 h-4 mr-2"/> Submit
</Button>
</form>
</FormProvider>
);
}
+19
View File
@@ -0,0 +1,19 @@
import * as React from "react"
export function useMediaQuery(query: string) {
const [value, setValue] = React.useState(false)
React.useEffect(() => {
function onChange(event: MediaQueryListEvent) {
setValue(event.matches)
}
const result = matchMedia(query)
result.addEventListener("change", onChange)
setValue(result.matches)
return () => result.removeEventListener("change", onChange)
}, [query])
return value
}