From 047d630b14aecbe5619018ddc7d845e6b1793451 Mon Sep 17 00:00:00 2001 From: Ategon <benjamin@barbeau.net> Date: Thu, 16 Jan 2025 01:42:36 -0500 Subject: [PATCH] Add post creation --- src/app/create-post/page.tsx | 108 ++++++++++++++++++++++++++++++ src/app/login/page.tsx | 4 +- src/app/signup/page.tsx | 15 ++++- src/components/navbar/index.tsx | 34 +++++++--- src/components/posts/PostCard.tsx | 7 +- src/types/UserType.ts | 7 ++ 6 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 src/app/create-post/page.tsx create mode 100644 src/types/UserType.ts diff --git a/src/app/create-post/page.tsx b/src/app/create-post/page.tsx new file mode 100644 index 0000000..1c54262 --- /dev/null +++ b/src/app/create-post/page.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { getCookies, hasCookie } from "@/helpers/cookie"; +import { Button, Form, Input, Textarea } from "@nextui-org/react"; +import { redirect } from "next/navigation"; +import { useState } from "react"; +import { toast } from "react-toastify"; + +export default function CreatePostPage() { + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + const [errors, setErrors] = useState({}); + + return ( + <div className="absolute flex items-center justify-center top-0 left-0 w-screen h-screen"> + <Form + className="w-full max-w-xs flex flex-col gap-4" + validationErrors={errors} + onReset={() => { + setTitle(""); + setContent(""); + }} + onSubmit={async (e) => { + e.preventDefault(); + + if (!title && !content) { + setErrors({ + title: "Please enter a valid title", + content: "Please enter valid content", + }); + return; + } + + if (!title) { + setErrors({ title: "Please enter a valid title" }); + return; + } + + if (!content) { + setErrors({ content: "Please enter valid content" }); + return; + } + + if (!hasCookie()) { + setErrors({ content: "You are not logged in" }); + return; + } + + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? "https://d2jam.com/api/v1/post" + : "http://localhost:3005/api/v1/post", + { + body: JSON.stringify({ + title: title, + content: content, + username: getCookies().user, + }), + method: "POST", + headers: { + "Content-Type": "application/json", + authorization: `Bearer ${getCookies().token}`, + }, + } + ); + + if (response.status == 401) { + setErrors({ content: "Invalid user" }); + return; + } + + toast.success("Successfully created post"); + + redirect("/app"); + }} + > + <Input + isRequired + label="Title" + labelPlacement="outside" + name="title" + placeholder="Enter a title" + type="text" + value={title} + onValueChange={setTitle} + /> + + <Textarea + isRequired + label="Content" + labelPlacement="outside" + name="content" + placeholder="Enter the post body" + value={content} + onValueChange={setContent} + /> + <div className="flex gap-2"> + <Button color="primary" type="submit"> + Create + </Button> + <Button type="reset" variant="flat"> + Reset + </Button> + </div> + </Form> + </div> + ); +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 41ee4c5..15a9106 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -57,8 +57,10 @@ export default function UserPage() { return; } - const token = await response.json(); + const { token, user } = await response.json(); + document.cookie = `token=${token}`; + document.cookie = `user=${user.slug}`; toast.success("Successfully logged in"); diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index 90fb0f6..d108b89 100644 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -48,6 +48,12 @@ export default function UserPage() { return; } + if (username.length > 32) { + setPassword2(""); + setErrors({ password: "Usernames can be maximum 32 characters" }); + return; + } + if (password.length < 8) { setPassword2(""); setErrors({ password: "Password must be minimum 8 characters" }); @@ -71,8 +77,15 @@ export default function UserPage() { } ); - const token = await response.json(); + if (response.status == 409) { + setErrors({ username: "User already exists" }); + setPassword2(""); + return; + } + + const { token, user } = await response.json(); document.cookie = `token=${token}`; + document.cookie = `user=${user.slug}`; toast.success("Successfully signed up"); diff --git a/src/components/navbar/index.tsx b/src/components/navbar/index.tsx index a274260..1609b54 100644 --- a/src/components/navbar/index.tsx +++ b/src/components/navbar/index.tsx @@ -16,39 +16,41 @@ import { DropdownMenu, DropdownTrigger, Image, + Spacer, Tooltip, } from "@nextui-org/react"; import { SiDiscord, SiForgejo, SiGithub } from "@icons-pack/react-simple-icons"; -import { LogInIcon, NotebookPen } from "lucide-react"; +import { LogInIcon, NotebookPen, SquarePen } from "lucide-react"; import { useEffect, useState } from "react"; import { hasCookie, getCookies } from "@/helpers/cookie"; import { usePathname } from "next/navigation"; +import { UserType } from "@/types/UserType"; export default function Navbar() { - const [user, setUser] = useState(""); + const [user, setUser] = useState<UserType>(); const pathname = usePathname(); useEffect(() => { loadUser(); async function loadUser() { if (!hasCookie()) { - setUser(""); + setUser(undefined); return; } const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" - ? "https://d2jam.com/api/v1/self" - : "http://localhost:3005/api/v1/self", + ? `https://d2jam.com/api/v1/self?username=${getCookies().user}` + : `http://localhost:3005/api/v1/self?username=${getCookies().user}`, { headers: { authorization: `Bearer ${getCookies().token}` }, } ); - if ((await response.text()) == "ok") { - setUser("ok"); + if (response.status == 200) { + setUser(await response.json()); } else { - setUser(""); + setUser(undefined); } } }, [pathname]); @@ -78,6 +80,20 @@ export default function Navbar() { </NavbarItem> </NavbarContent> <NavbarContent justify="end"> + {user && ( + <NavbarItem> + <Link href="/create-post"> + <Button + endContent={<SquarePen />} + className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" + variant="bordered" + > + Create Post + </Button> + </Link> + <Spacer x={32} /> + </NavbarItem> + )} <NavbarItem> <Tooltip delay={1000} @@ -154,7 +170,7 @@ export default function Navbar() { ) : ( <Dropdown> <DropdownTrigger> - <Avatar /> + <Avatar src={user.profilePicture} /> </DropdownTrigger> <DropdownMenu> {/* <DropdownItem diff --git a/src/components/posts/PostCard.tsx b/src/components/posts/PostCard.tsx index e544fc4..2e1b8c3 100644 --- a/src/components/posts/PostCard.tsx +++ b/src/components/posts/PostCard.tsx @@ -1,5 +1,4 @@ -import { Avatar, Button, Card, CardBody, Spacer } from "@nextui-org/react"; -import { Heart, MessageCircle } from "lucide-react"; +import { Avatar, Card, CardBody, Spacer } from "@nextui-org/react"; import { formatDistance } from "date-fns"; import Link from "next/link"; import { PostType } from "@/types/PostType"; @@ -36,14 +35,14 @@ export default function PostCard({ post }: { post: PostType }) { <Spacer y={4} /> - <div className="flex gap-3"> + {/* <div className="flex gap-3"> <Button size="sm"> <Heart size={16} /> {post.likers.length} </Button> <Button size="sm"> <MessageCircle size={16} /> {0} </Button> - </div> + </div> */} </CardBody> </Card> ); diff --git a/src/types/UserType.ts b/src/types/UserType.ts new file mode 100644 index 0000000..2735f94 --- /dev/null +++ b/src/types/UserType.ts @@ -0,0 +1,7 @@ +export interface UserType { + id: number; + slug: string; + name: string; + profilePicture: string; + createdAt: Date; +}