diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..996bfac --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { Button, Form, Input } from "@nextui-org/react"; +import { redirect } from "next/navigation"; +import { useState } from "react"; + +export default function UserPage() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = 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" + validationBehavior="native" + onReset={() => { + setUsername(""); + setPassword(""); + }} + onSubmit={async (e) => { + e.preventDefault(); + const response = await fetch( + process.env.MODE === "PROD" + ? "https://d2jam.com/api/v1/login" + : "http://localhost:3005/api/v1/login", + { + body: JSON.stringify({ username: username, password: password }), + method: "POST", + headers: { "Content-Type": "application/json" }, + } + ); + + if (response.status == 401) { + setError("Invalid username or password"); + setPassword(""); + return; + } + + const token = await response.json(); + document.cookie = `token=${token}`; + + redirect("/"); + }} + > + <Input + isRequired + errorMessage="Please enter a valid username" + label="Username" + labelPlacement="outside" + name="username" + placeholder="Enter your username" + type="text" + value={username} + onValueChange={setUsername} + /> + + <Input + isRequired + errorMessage="Please enter a valid password" + label="Password" + labelPlacement="outside" + name="password" + placeholder="Enter your password" + type="password" + value={password} + onValueChange={setPassword} + /> + <div className="flex gap-2"> + <Button color="primary" type="submit"> + Submit + </Button> + <Button type="reset" variant="flat"> + Reset + </Button> + </div> + <p>Sign up is being worked on currently</p> + {error && <p className="text-red-500">{error}</p>} + {} + </Form> + </div> + ); +} diff --git a/src/app/logout/page.tsx b/src/app/logout/page.tsx new file mode 100644 index 0000000..1b02ac4 --- /dev/null +++ b/src/app/logout/page.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { redirect } from "next/navigation"; +import React, { useEffect } from "react"; + +export default function UserPage() { + useEffect(() => { + document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + + redirect("/"); + }); + + return ( + <div className="absolute flex items-center justify-center top-0 left-0 w-screen h-screen"> + <p>Logging out...</p> + </div> + ); +} diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/navbar/index.tsx b/src/components/navbar/index.tsx index c0caf44..0009a95 100644 --- a/src/components/navbar/index.tsx +++ b/src/components/navbar/index.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Navbar as NavbarBase, NavbarBrand, @@ -6,10 +8,51 @@ import { } from "@nextui-org/navbar"; import { Link } from "@nextui-org/link"; import { Divider } from "@nextui-org/divider"; -import { Image, Tooltip } from "@nextui-org/react"; +import { + Avatar, + Button, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownTrigger, + Image, + Tooltip, +} from "@nextui-org/react"; import { SiDiscord, SiForgejo, SiGithub } from "@icons-pack/react-simple-icons"; +import { LogInIcon } from "lucide-react"; +import { useEffect, useState } from "react"; +import { hasCookie, getCookies } from "@/helpers/cookie"; +import { usePathname } from "next/navigation"; export default function Navbar() { + const [user, setUser] = useState(""); + const pathname = usePathname(); + + useEffect(() => { + loadUser(); + async function loadUser() { + if (!hasCookie()) { + setUser(""); + return; + } + + const response = await fetch( + process.env.MODE === "PROD" + ? "https://d2jam.com/api/v1/self" + : "http://localhost:3005/api/v1/self", + { + headers: { authorization: `Bearer ${getCookies().token}` }, + } + ); + + if ((await response.text()) == "ok") { + setUser("ok"); + } else { + setUser(""); + } + } + }, [pathname]); + return ( <NavbarBase shouldHideOnScroll @@ -83,15 +126,63 @@ export default function Navbar() { </Link> </NavbarItem> <Divider orientation="vertical" className="h-1/2" /> - {/* <NavbarItem> - <Button - endContent={<LogInIcon />} - 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" - > - Log In - </Button> - </NavbarItem> */} + {!user ? ( + <div className="flex gap-3 items-center"> + <NavbarItem> + <Link href="/login"> + <Button + endContent={<LogInIcon />} + 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" + > + Log In + </Button> + </Link> + </NavbarItem> + {/* <NavbarItem> + <Link href="/signup"> + <Button + endContent={<NotebookPen />} + 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" + > + Sign up + </Button> + </Link> + </NavbarItem> */} + </div> + ) : ( + <Dropdown> + <DropdownTrigger> + <Avatar /> + </DropdownTrigger> + <DropdownMenu> + {/* <DropdownItem + key="profile" + className="text-black" + href="/profile" + > + Profile + </DropdownItem> + <DropdownItem + showDivider + key="settings" + className="text-black" + href="/settings" + > + Settings + </DropdownItem> */} + <DropdownItem + key="logout" + color="danger" + className="text-danger" + href="/logout" + > + Logout + </DropdownItem> + </DropdownMenu> + </Dropdown> + )} </NavbarContent> </NavbarBase> ); diff --git a/src/helpers/cookie.ts b/src/helpers/cookie.ts new file mode 100644 index 0000000..30903ad --- /dev/null +++ b/src/helpers/cookie.ts @@ -0,0 +1,20 @@ +export function getCookies() { + const pairs = document.cookie.split(";"); + const cookies: Record<string, string> = {}; + for (let i = 0; i < pairs.length; i++) { + const pair = pairs[i].split("="); + cookies[(pair[0] + "").trim()] = unescape(pair.slice(1).join("=")); + } + return cookies; +} + +export function hasCookie() { + const pairs = document.cookie.split(";"); + for (let i = 0; i < pairs.length; i++) { + const pair = pairs[i].split("="); + if (pair[0] == "token") { + return true; + } + } + return false; +}