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;
+}