feat(frontend): create volume

This commit is contained in:
Nicolas Meienberger
2025-08-16 14:22:24 +02:00
parent 03a1203f7e
commit d13763995e
7 changed files with 254 additions and 33 deletions

View File

@@ -0,0 +1,123 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Plus } from "lucide-react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useCreateVolume } from "~/hooks/useCreateVolume";
import { slugify } from "~/lib/utils";
import { Button } from "./ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "./ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "./ui/form";
import { Input } from "./ui/input";
type Props = {
open: boolean;
setOpen: (open: boolean) => void;
};
const formSchema = z.object({
name: z
.string()
.min(2, {
message: "Volume name must be at least 2 characters long",
})
.max(32, {
message: "Volume name must be at most 32 characters long",
}),
});
export const CreateVolumeDialog = ({ open, setOpen }: Props) => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
},
});
const nameValue = form.watch("name");
const createVolume = useCreateVolume();
const onSubmit = (values: { name: string }) => {
createVolume.mutate(values, {
onSuccess: () => {
form.reset();
setOpen(false);
},
});
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className="bg-blue-900 hover:bg-blue-800">
<Plus size={16} />
Create volume
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create volume</DialogTitle>
<DialogDescription>
Enter a name for the new volume.
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input
placeholder="Volume name"
value={field.value ?? ""}
onChange={(e) => field.onChange(slugify(e.target.value))}
max={32}
min={1}
/>
</FormControl>
<FormDescription>
Unique identifier for the volume.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{createVolume.error && (
<div className="text-red-500 text-sm">
{createVolume.error.message}
</div>
)}
<DialogFooter>
<Button variant="secondary" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button
type="submit"
disabled={createVolume.status === "pending" || !nameValue}
>
Create
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,11 +1,11 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "~/lib/utils";
const buttonVariants = cva(
"inline-flex cursor-pointer uppercase border items-center justify-center dark:border-white dark:sbg-secondary dark:text-white gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"inline-flex cursor-pointer uppercase border items-center justify-center dark:border-white dark:bg-secondary dark:text-white gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {