diff --git a/package-lock.json b/package-lock.json index 741b72e..7bdac71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "lucide-react": "^0.453.0", "next": "15.0.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-toastify": "^11.0.3" }, "devDependencies": { "@types/node": "^20", @@ -7605,6 +7606,28 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-toastify": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.3.tgz", + "integrity": "sha512-cbPtHJPfc0sGqVwozBwaTrTu1ogB9+BLLjd4dDXd863qYLj7DGrQ2sg5RAChjFUB4yc3w8iXOtWcJqPK/6xqRQ==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, + "node_modules/react-toastify/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 4d3c306..1e9ee7f 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "lucide-react": "^0.453.0", "next": "15.0.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-toastify": "^11.0.3" }, "devDependencies": { "@types/node": "^20", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9e4fdaf..274dd35 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,7 @@ import { Inter } from "next/font/google"; import "./globals.css"; import Navbar from "../components/navbar"; import Providers from "./providers"; +import { ToastContainer } from "react-toastify"; const inter = Inter({ subsets: ["latin"] }); @@ -24,6 +25,7 @@ export default function RootLayout({ <div className="bg-zinc-100 dark:bg-zinc-950 min-h-screen"> <Navbar /> <div className="max-w-8xl mx-auto">{children}</div> + <ToastContainer /> </div> </div> </Providers> diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 4444f3d..41ee4c5 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -3,23 +3,43 @@ import { Button, Form, Input } from "@nextui-org/react"; import { redirect } from "next/navigation"; import { useState } from "react"; +import { toast } from "react-toastify"; export default function UserPage() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); - const [error, setError] = 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" - validationBehavior="native" + validationErrors={errors} onReset={() => { setUsername(""); setPassword(""); }} onSubmit={async (e) => { e.preventDefault(); + + if (!username && !password) { + setErrors({ + username: "Please enter a valid username", + password: "Please enter a valid password", + }); + return; + } + + if (!username) { + setErrors({ username: "Please enter a valid username" }); + return; + } + + if (!password) { + setErrors({ password: "Please enter a valid password" }); + return; + } + const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? "https://d2jam.com/api/v1/login" @@ -32,7 +52,7 @@ export default function UserPage() { ); if (response.status == 401) { - setError("Invalid username or password"); + setErrors({ password: "Invalid username or password" }); setPassword(""); return; } @@ -40,12 +60,13 @@ export default function UserPage() { const token = await response.json(); document.cookie = `token=${token}`; + toast.success("Successfully logged in"); + redirect("/"); }} > <Input isRequired - errorMessage="Please enter a valid username" label="Username" labelPlacement="outside" name="username" @@ -57,7 +78,6 @@ export default function UserPage() { <Input isRequired - errorMessage="Please enter a valid password" label="Password" labelPlacement="outside" name="password" @@ -74,9 +94,6 @@ export default function UserPage() { 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 index 1b02ac4..7809ae8 100644 --- a/src/app/logout/page.tsx +++ b/src/app/logout/page.tsx @@ -2,11 +2,14 @@ import { redirect } from "next/navigation"; import React, { useEffect } from "react"; +import { toast } from "react-toastify"; export default function UserPage() { useEffect(() => { document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + toast.success("Successfully logged out"); + redirect("/"); }); diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index 2c405fe..90fb0f6 100644 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -3,28 +3,67 @@ import { Button, Form, Input } from "@nextui-org/react"; import { redirect } from "next/navigation"; import { useState } from "react"; +import { toast } from "react-toastify"; export default function UserPage() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); - //const [password2, setPassword2] = useState(""); - const [error, setError] = useState(""); + const [password2, setPassword2] = 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" - validationBehavior="native" + validationErrors={errors} onReset={() => { setUsername(""); setPassword(""); + setPassword2(""); }} onSubmit={async (e) => { e.preventDefault(); + + if (!password || !username || !password2) { + const localErrors: Record<string, string> = {}; + + if (password && password.length < 8) { + setPassword2(""); + setErrors({ password: "Password must be minimum 8 characters" }); + return; + } + + if (!password) { + localErrors["password"] = "Please enter a valid password"; + } + + if (!password2) { + localErrors["password2"] = "Please reenter your password"; + } + + if (!username) { + localErrors["username"] = "Please enter a valid username"; + } + setErrors(localErrors); + return; + } + + if (password.length < 8) { + setPassword2(""); + setErrors({ password: "Password must be minimum 8 characters" }); + return; + } + + if (password != password2) { + setPassword2(""); + setErrors({ password2: "Passwords do not match" }); + return; + } + const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" - ? "https://d2jam.com/api/v1/login" - : "http://localhost:3005/api/v1/login", + ? "https://d2jam.com/api/v1/signup" + : "http://localhost:3005/api/v1/signup", { body: JSON.stringify({ username: username, password: password }), method: "POST", @@ -32,21 +71,16 @@ export default function UserPage() { } ); - if (response.status == 401) { - setError("Invalid username or password"); - setPassword(""); - return; - } - const token = await response.json(); document.cookie = `token=${token}`; + toast.success("Successfully signed up"); + redirect("/"); }} > <Input isRequired - errorMessage="Please enter a valid username" label="Username" labelPlacement="outside" name="username" @@ -58,7 +92,6 @@ export default function UserPage() { <Input isRequired - errorMessage="Please enter a valid password" label="Password" labelPlacement="outside" name="password" @@ -69,14 +102,13 @@ export default function UserPage() { /> <Input isRequired - errorMessage="Please enter a valid password" - label="Password" + label="Password Confirmation" labelPlacement="outside" - name="password" - placeholder="Enter your password" + name="password2" + placeholder="Reenter your password" type="password" - value={password} - onValueChange={setPassword} + value={password2} + onValueChange={setPassword2} /> <div className="flex gap-2"> <Button color="primary" type="submit"> @@ -86,9 +118,6 @@ export default function UserPage() { 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/components/navbar/index.tsx b/src/components/navbar/index.tsx index 3eb26fe..a274260 100644 --- a/src/components/navbar/index.tsx +++ b/src/components/navbar/index.tsx @@ -19,7 +19,7 @@ import { Tooltip, } from "@nextui-org/react"; import { SiDiscord, SiForgejo, SiGithub } from "@icons-pack/react-simple-icons"; -import { LogInIcon } from "lucide-react"; +import { LogInIcon, NotebookPen } from "lucide-react"; import { useEffect, useState } from "react"; import { hasCookie, getCookies } from "@/helpers/cookie"; import { usePathname } from "next/navigation"; @@ -139,7 +139,7 @@ export default function Navbar() { </Button> </Link> </NavbarItem> - {/* <NavbarItem> + <NavbarItem> <Link href="/signup"> <Button endContent={<NotebookPen />} @@ -149,7 +149,7 @@ export default function Navbar() { Sign up </Button> </Link> - </NavbarItem> */} + </NavbarItem> </div> ) : ( <Dropdown>