skalara-web/app/projects/page.tsx
2023-07-30 02:13:35 -04:00

191 lines
5.7 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import { Project } from "@/types/models";
import { Button, buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
const projectSchema = z.object({
title: z.string().min(3, "Title must be at least 3 characters"),
description: z.string().min(10, "Description must be at least 10 characters"),
github: z.string().url("Invalid URL").optional(),
stack: z.string().array(),
});
export default function Projects() {
const [projects, setProjects] = useState<Project[]>([]);
const [stackInput, setStackInput] = useState("");
const [loading, setLoading] = useState(true);
const [open, setOpen] = useState(false);
const form = useForm<z.infer<typeof projectSchema>>({
resolver: zodResolver(projectSchema),
defaultValues: {
title: "",
description: "",
github: "",
stack: [],
},
});
const { setValue } = form;
const fetchProjects = async () => {
try {
setLoading(true);
const res = await fetch("/api/projects");
if (!res.ok) throw new Error("HTTP status " + res.status);
const data = await res.json();
setProjects(data);
setLoading(false);
} catch (err) {
console.error("Failed to fetch projects:", err);
}
};
useEffect(() => {
fetchProjects();
}, []);
async function handleSubmit(values: z.infer<typeof projectSchema>) {
try {
const res = await fetch("/api/projects", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
});
if (!res.ok) throw new Error("HTTP status " + res.status);
const newProject = await res.json();
setProjects([...projects, newProject]);
setOpen(false);
} catch (err) {
console.error("Failed to create project:", err);
}
}
const keyHandler = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" || e.key === "Tab") {
e.preventDefault();
setValue("stack", [...form.getValues("stack"), stackInput]);
setStackInput("");
}
};
return (
<div>
<h1>Projects</h1>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger className={buttonVariants({ variant: "default" })}>
Open
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add Project</DialogTitle>
<DialogDescription>
<Form {...form}>
<form
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-8"
>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Project Title</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Project Description</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="github"
render={({ field }) => (
<FormItem>
<FormLabel>Github Repository URL</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="stack"
render={({ field }) => (
<FormItem>
<FormLabel>Tech Stack</FormLabel>
<FormControl>
<Input
value={stackInput}
onChange={(e) => setStackInput(e.target.value)}
onKeyDown={keyHandler}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
{loading ? (
<p>Loading...</p>
) : (
<div>
{projects.map((project) => (
<div key={project.id}>
<h2>
<Link href={`/projects/${project.id}`}>{project.title}</Link>
</h2>
<p>{project.description}</p>
</div>
))}
</div>
)}
</div>
);
}