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 { auth } from "~/server/auth";
|
||||||
import { UserNav } from "~/app/_components/user-nav";
|
import { UserNav } from "~/app/_components/macro/user-nav";
|
||||||
import { Button } from "~/app/_components/ui/button";
|
|
||||||
import { api } from "~/trpc/server";
|
import { api } from "~/trpc/server";
|
||||||
import { ListButton } from "~/app/(home)/_components/list-button";
|
import { ListButton } from "~/app/(home)/_components/list-button";
|
||||||
|
import { CreateNewButton } from "~/app/(home)/_components/create-new-button";
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
@@ -23,7 +23,7 @@ export default async function Home() {
|
|||||||
<div className="mx-auto max-w-6xl px-6">
|
<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 h-full flex-1 flex-col space-y-8 p-8">
|
||||||
<div className="flex items-center justify-between space-y-2">
|
<div className="flex items-center justify-between space-y-2">
|
||||||
<Button>create a new list</Button>
|
<CreateNewButton />
|
||||||
<UserNav />
|
<UserNav />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
|
|||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -120,3 +120,10 @@ export {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogDescription,
|
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}
|
ref={ref}
|
||||||
id={formMessageId}
|
id={formMessageId}
|
||||||
className={cn(
|
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,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ const RadioGroupItem = React.forwardRef<
|
|||||||
<RadioGroupPrimitive.Item
|
<RadioGroupPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
<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.Indicator>
|
||||||
</RadioGroupPrimitive.Item>
|
</RadioGroupPrimitive.Item>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user