beta/components/create-project.tsx
Christopher Arraya bcf3f113ad i'm done
2023-11-05 07:34:37 -05:00

289 lines
8.8 KiB
TypeScript

"use client";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm, useFieldArray } from "react-hook-form";
import { useState } from "react";
import { useRouter } from "next/navigation";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
import { Textarea } from "./ui/textarea";
const formSchema = z.object({
name: z.string().min(2, {
message: "Name must be at least 2 characters.",
}),
description: z.string().min(2, {
message: "Description must be at least 2 characters.",
}),
stack: z.array(z.string()).min(1, {
message: "Project tech stack must have at least one item.",
}),
questions: z.array(
z.object({
question: z.string().optional(),
})
),
});
export function CreateProject({ workspaceID }: { workspaceID: string }) {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
mode: "all",
defaultValues: {
name: "",
description: "",
stack: [],
questions: [],
},
});
const { setValue, formState } = form;
const [stackInput, setStackInput] = useState("");
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [formStep, setFormStep] = useState(0);
const [questions, setQuestions] = useState([]); // TODO: [] as Question[]
const router = useRouter();
const { fields, append, remove } = useFieldArray({
name: "questions",
control: form.control,
});
async function onSubmit(values: z.infer<typeof formSchema>) {
try {
const res = await fetch(`/w/${workspaceID}/p`, {
method: "POST",
body: JSON.stringify({
...values,
questionsText: questions,
}),
});
const data = await res.json();
console.log("===>", res);
if (!res.ok) throw new Error("Something went wrong.");
router.push(`/w/${workspaceID}/p/${data.project.id}`);
setIsDialogOpen(false);
return res;
} catch (err) {
console.error(err);
}
}
async function generateQuestions() {
try {
const res = await fetch(`/w/${workspaceID}/p/gen`, {
method: "POST",
body: JSON.stringify(form.getValues()),
});
if (!res.ok) throw new Error("Something went wrong.");
const data = await res.json();
console.log("===>", data.questions);
setQuestions(data.questions);
append(data.questions);
return res;
} catch (err) {
console.error(err);
}
}
const keyHandler = (e: React.KeyboardEvent<HTMLInputElement>) => {
if ((e.key === "Enter" || e.key === "Tab") && stackInput !== "") {
e.preventDefault();
setValue("stack", [...form.getValues("stack"), stackInput]);
setStackInput("");
}
};
return (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button variant="outline">Create Project</Button>
</DialogTrigger>
<DialogContent>
{formStep == 0 && (
<DialogHeader>
<DialogTitle>Create a Project</DialogTitle>
<DialogDescription>
Give your project a name, description, and tech stack.
</DialogDescription>
</DialogHeader>
)}
{formStep == 1 && (
<DialogHeader>
<DialogTitle>Extra Questions</DialogTitle>
<DialogDescription>
We&apos;ve like to know some more about the specifics of your
project, feel free to answer any of the following additional
questions.
</DialogDescription>
</DialogHeader>
)}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className={cn(formStep == 0 && "space-y-8")}
>
<div className={cn(formStep !== 0 && "sr-only")}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
This is your project name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Input placeholder="" {...field} />
</FormControl>
<FormDescription>
This is your project description.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="stack"
render={() => (
<FormItem>
<FormLabel>Tech Stack</FormLabel>
<FormControl>
<Input
value={stackInput}
onChange={(e) => setStackInput(e.target.value)}
onKeyDown={keyHandler}
/>
</FormControl>
<FormDescription>
This is your project tech stack.
</FormDescription>
<div>
{form.getValues("stack").map((stack) => (
<Badge
key={stack}
className="mr-2 font-normal rounded-md"
variant="outline"
>
<span className="mr-1">{stack}</span>
<X
className="inline font-light text-red-500"
size={16}
onClick={() =>
setValue(
"stack",
form
.getValues("stack")
.filter((s) => s !== stack)
)
}
/>
</Badge>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<Button
type="button"
variant="outline"
onClick={() => {
setFormStep(1);
generateQuestions();
}}
>
Next
</Button>
</div>
<div className={cn(formStep !== 1 && "sr-only")}>
{questions.length == 0 && (
<p className="text-gray-400 text-center">
Generating questions...
</p>
)}
{fields.map((field, index) => (
<FormField
control={form.control}
key={field.id}
name={`questions.${index}.question`}
render={({ field }) => (
<FormItem>
<FormLabel>Question {index + 1} </FormLabel>
<FormDescription>
<span className="text-gray-400">
{questions[index]}
</span>
</FormDescription>
<FormControl>
<Textarea {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
))}
<div className="flex p-4 gap-4 pl-0">
<Button
type="button"
variant="outline"
onClick={() => {
setFormStep(0);
setQuestions([]);
remove();
}}
>
Back
</Button>
{questions.length != 0 && <Button type="submit">Submit</Button>}
</div>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
}