mirror of
https://gitea.tendokyu.moe/ppc/amnet.git
synced 2026-09-30 02:38:09 +03:00
add card creation form
This commit is contained in:
@@ -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'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>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user