implemented 'create new list' button + dialog
This commit is contained in:
250
src/app/(home)/_components/create-new-button.tsx
Normal file
250
src/app/(home)/_components/create-new-button.tsx
Normal file
@@ -0,0 +1,250 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
useDialogControls,
|
||||
} from "~/app/_components/ui/dialog";
|
||||
import { Button } from "~/app/_components/ui/button";
|
||||
import { Input } from "~/app/_components/ui/input";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "~/app/_components/ui/form";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { api } from "~/trpc/react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
type ListCreationSchema,
|
||||
listCreationFormSchema,
|
||||
} from "~/lib/schemas/list-creation-form";
|
||||
import { RadioGroup, RadioGroupItem } from "~/app/_components/ui/radio-group";
|
||||
import { VARIANTS } from "~/lib/data/list-variants";
|
||||
import { LABELS } from "~/lib/data/task-labels";
|
||||
import { Checkbox } from "~/app/_components/ui/checkbox";
|
||||
import { Separator } from "~/app/_components/ui/separator";
|
||||
|
||||
export const CreateNewButton = () => {
|
||||
const [isOpen, onOpenChange] = useDialogControls();
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
form.reset();
|
||||
onOpenChange(open);
|
||||
};
|
||||
|
||||
const utils = api.useUtils();
|
||||
const { mutate } = api.list.create.useMutation({
|
||||
onSuccess: (_, variables) => {
|
||||
toast.success(`Created new list: ${variables.name}`);
|
||||
onOpenChange(false);
|
||||
form.reset();
|
||||
void utils.list.getAll.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<ListCreationSchema>({
|
||||
resolver: zodResolver(listCreationFormSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
variant: "",
|
||||
labels: [],
|
||||
id: false,
|
||||
idPrefix: "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (values: ListCreationSchema) => {
|
||||
console.log("SUBMIT", values);
|
||||
mutate(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>create a new list</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<DialogHeader>
|
||||
<DialogTitle>create new list</DialogTitle>
|
||||
<DialogDescription>
|
||||
customize the list to your needs.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
{/* Name */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="mb-2">
|
||||
<FormLabel>title</FormLabel>
|
||||
<FormDescription>
|
||||
this is the name of your list.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input placeholder="to-do" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Variants */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="variant"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-3">
|
||||
<div className="mb-2">
|
||||
<FormLabel>variant</FormLabel>
|
||||
<FormDescription>
|
||||
select the type of list you want to use.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="flex flex-col space-y-1"
|
||||
>
|
||||
{VARIANTS.map((v) => (
|
||||
<FormItem
|
||||
key={v}
|
||||
className="flex items-center space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<RadioGroupItem value={v} />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">{v}</FormLabel>
|
||||
</FormItem>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* ID */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="grid gap-1.5 leading-none">
|
||||
<FormLabel>enable task id</FormLabel>
|
||||
<FormDescription>
|
||||
assign an id number to each task.
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* ID Prefix */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="idPrefix"
|
||||
disabled={!form.getValues("id")}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="mb-2">
|
||||
<FormLabel>id prefix</FormLabel>
|
||||
<FormDescription>
|
||||
(optional) add a prefix to your id. ex:
|
||||
"ls-53"
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input placeholder="ls-" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Labels */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="labels"
|
||||
render={() => (
|
||||
<FormItem>
|
||||
<div className="mb-2">
|
||||
<FormLabel>labels</FormLabel>
|
||||
<FormDescription>
|
||||
(optional) select the task labels you want to be able to
|
||||
use.
|
||||
</FormDescription>
|
||||
</div>
|
||||
{LABELS.map((label) => (
|
||||
<FormField
|
||||
key={label}
|
||||
control={form.control}
|
||||
name="labels"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
key={label}
|
||||
className="flex flex-row items-start space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(label)}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([...field.value, label])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) => value !== label,
|
||||
),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="text-sm font-normal">
|
||||
{label}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import { auth } from "~/server/auth";
|
||||
import { UserNav } from "~/app/_components/user-nav";
|
||||
import { Button } from "~/app/_components/ui/button";
|
||||
import { UserNav } from "~/app/_components/macro/user-nav";
|
||||
import { api } from "~/trpc/server";
|
||||
import { ListButton } from "~/app/(home)/_components/list-button";
|
||||
import { CreateNewButton } from "~/app/(home)/_components/create-new-button";
|
||||
|
||||
export default async function Home() {
|
||||
const session = await auth();
|
||||
@@ -23,7 +23,7 @@ export default async function Home() {
|
||||
<div className="mx-auto max-w-6xl px-6">
|
||||
<div className="flex h-full flex-1 flex-col space-y-8 p-8">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<Button>create a new list</Button>
|
||||
<CreateNewButton />
|
||||
<UserNav />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 sm:rounded-lg dark:border-neutral-800 dark:bg-neutral-950",
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] dark:border-neutral-800 dark:bg-neutral-950 sm:rounded-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -120,3 +120,10 @@ export {
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
};
|
||||
|
||||
export const useDialogControls = () => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const onOpenChange = (open: boolean) => setIsOpen(open);
|
||||
|
||||
return [isOpen, onOpenChange] as const;
|
||||
};
|
||||
|
||||
@@ -165,7 +165,7 @@ const FormMessage = React.forwardRef<
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn(
|
||||
"text-gruv-red-bg dark:text-gruv-red-fg text-[0.8rem] font-medium",
|
||||
"text-[0.8rem] font-medium text-gruv-red-bg dark:text-gruv-red-fg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -28,13 +28,13 @@ const RadioGroupItem = React.forwardRef<
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"aspect-square h-4 w-4 rounded-full border border-neutral-200 border-neutral-900 text-neutral-900 shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-50 dark:border-neutral-800 dark:text-neutral-50 dark:focus-visible:ring-neutral-300",
|
||||
"aspect-square h-4 w-4 rounded-full border border-neutral-900 text-neutral-900 shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-50 dark:text-neutral-50 dark:focus-visible:ring-neutral-300",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
||||
<Circle className="fill-primary h-3.5 w-3.5" />
|
||||
<Circle className="h-3.5 w-3.5 fill-inherit" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user