From 5b7cf87ec8c784290eefa24024a6c0999cb62468 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Thu, 2 Oct 2025 21:50:15 -0400 Subject: [PATCH 01/15] Add example.env The README was updated to include it in the setup instructions. --- README.md | 6 ++++++ example.env | 1 + 2 files changed, 7 insertions(+) create mode 100644 example.env diff --git a/README.md b/README.md index 2b001613..14a243b5 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ To clone and run this application, [Node.js](https://nodejs.org/en/download/) (w npm install npm@latest -g ``` +### `.env` Setup + +Create a file called `.env` in the root directory, copying the contents of `example.env`. + +Replace all values in the file with the relevant information. + ### Installation ```bash diff --git a/example.env b/example.env new file mode 100644 index 00000000..4ad98d0d --- /dev/null +++ b/example.env @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=https://example.com/ From 4a1a848dae3ab47d8a35d39d8191aa44d1c71fac Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Thu, 2 Oct 2025 22:03:22 -0400 Subject: [PATCH 02/15] Update example.env I'm removing the trailing slash to hopefully suggest to other developers to omit it, just in case it causes issues with the URL. --- example.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.env b/example.env index 4ad98d0d..d5f10262 100644 --- a/example.env +++ b/example.env @@ -1 +1 @@ -NEXT_PUBLIC_API_URL=https://example.com/ +NEXT_PUBLIC_API_URL=https://example.com From c3aa83701c29e0901a4da4c49cfea573ab220e51 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Thu, 2 Oct 2025 23:53:03 -0400 Subject: [PATCH 03/15] Add setup for local API testing Adding HTTPS and route redirecting is necessary to get cookies working on local testing. --- .gitignore | 2 ++ next.config.ts | 10 ++++++++++ package.json | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5ef6a520..3eb32276 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +certificates diff --git a/next.config.ts b/next.config.ts index 28f40ec5..a9f37824 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,16 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { allowedDevOrigins: ["129.161.139.75"], + + // Redirect API calls to the backend server for CORS stuff + async rewrites() { + return [ + { + source: "/api/:path*", + destination: `${process.env.NEXT_PUBLIC_API_URL}/:path*/`, + }, + ]; + }, }; export default nextConfig; diff --git a/package.json b/package.json index f537f45e..85bd1c35 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --turbopack", + "dev": "next dev --turbopack --experimental-https", "build": "next build", "start": "next start", "lint": "next lint", From 3fd594daa27a41d310162faaf19a723bdfe1c08b Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 01:07:15 -0400 Subject: [PATCH 04/15] Create API error format util function --- app/_utils/format-api-error.tsx | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 app/_utils/format-api-error.tsx diff --git a/app/_utils/format-api-error.tsx b/app/_utils/format-api-error.tsx new file mode 100644 index 00000000..3b06fc13 --- /dev/null +++ b/app/_utils/format-api-error.tsx @@ -0,0 +1,36 @@ +function snakeToTitleCase(str: string): string { + return str + .split("_") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +export default function formatApiError(errors: any): string { + let errorMessage = ""; + let generalMessage = ""; + errors = errors.error; + + if (errors.general) { + generalMessage = errors.general[0]; + } + + for (const field in errors) { + if (field !== "general" && Array.isArray(errors[field])) { + for (const msg of errors[field]) { + const fieldTitle = snakeToTitleCase(field); + errorMessage += `${fieldTitle}: ${msg}\n`; + } + } + } + + if (errorMessage) { + if (generalMessage) { + return generalMessage + "\n" + errorMessage.trim(); + } + return errorMessage.trim(); + } else if (generalMessage) { + return generalMessage; + } + + return "An unknown error has occurred."; +} From cd077df80d9bb72b7674f54ef00286846fc4ada9 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 01:41:16 -0400 Subject: [PATCH 05/15] Add login API functionality --- app/login/page.tsx | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/app/login/page.tsx b/app/login/page.tsx index 9fb51139..7673055a 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -3,22 +3,41 @@ import React, { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; +import formatApiError from "../_utils/format-api-error"; export default function Page() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const router = useRouter(); - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - console.log("Login attempt:", { email, password }); - // TODO: Replace with real authentication - if (email === "test" && password === "1234") { - router.push("/dashboard"); - } else { - alert("Invalid email or password"); + if (!email) { + alert("Missing email"); + return; } + if (!password) { + alert("Missing password"); + return; + } + + await fetch("/api/auth/login/", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }) + .then(async (res) => { + if (res.ok) { + router.push("/dashboard"); + } else { + alert(formatApiError(await res.json())); + } + }) + .catch((err) => { + console.error("Fetch error:", err); + alert("An error occurred. Please try again."); + }); }; return ( From 9cb9ecdf2648fcc2c9ee039d866fa61b94ff0565 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 01:43:52 -0400 Subject: [PATCH 06/15] Add login restriction This stops the user from sending another request while still waiting for one. --- app/login/page.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/login/page.tsx b/app/login/page.tsx index 7673055a..dc9fa7bb 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState } from "react"; +import React, { useRef, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import formatApiError from "../_utils/format-api-error"; @@ -8,11 +8,15 @@ import formatApiError from "../_utils/format-api-error"; export default function Page() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const isSubmitting = useRef(false); const router = useRouter(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + if (isSubmitting.current) return; + isSubmitting.current = true; + if (!email) { alert("Missing email"); return; @@ -38,6 +42,8 @@ export default function Page() { console.error("Fetch error:", err); alert("An error occurred. Please try again."); }); + + isSubmitting.current = false; }; return ( From 089953152ecb2f7d9952ecb19f82cc3a9576222a Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 13:02:05 -0400 Subject: [PATCH 07/15] Fix login button functionality If the user put in anything wrong and submitted, they would be locked out of submitting again. --- app/login/page.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/login/page.tsx b/app/login/page.tsx index dc9fa7bb..c5c44c06 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -19,10 +19,12 @@ export default function Page() { if (!email) { alert("Missing email"); + isSubmitting.current = false; return; } if (!password) { alert("Missing password"); + isSubmitting.current = false; return; } From 8fd8bcc2d4cce5ee0553d2ab47f954961b55d082 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 13:05:45 -0400 Subject: [PATCH 08/15] Add sign up API functionality --- app/sign-up/page.tsx | 54 ++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/app/sign-up/page.tsx b/app/sign-up/page.tsx index 9de47d29..cf1fed8a 100644 --- a/app/sign-up/page.tsx +++ b/app/sign-up/page.tsx @@ -3,30 +3,56 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import React, { useState } from "react"; +import formatApiError from "../_utils/format-api-error"; export default function Page() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); + const isSubmitting = React.useRef(false); const router = useRouter(); - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - console.log("Sign up attempt:", { - email, - password, - confirmPassword, - }); - // TODO: Replace with real sign up API logic - if (email && password && confirmPassword === password) { - // add the email to session storage to have it in the email-sent page without - // putting it in the URL - sessionStorage.setItem("sign_up_email", email); - router.push("/sign-up/email-sent"); - } else { - alert("WOMP WOMP NO ACCOUNT FOR YOU"); + if (isSubmitting.current) return; + isSubmitting.current = true; + + if (!email) { + alert("Missing email"); + isSubmitting.current = false; + return; + } + if (!password) { + alert("Missing password"); + isSubmitting.current = false; + return; + } + if (confirmPassword !== password) { + alert("Passwords do not match"); + isSubmitting.current = false; + return; } + + await fetch("/api/auth/register/", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }) + .then(async (res) => { + if (res.ok) { + sessionStorage.setItem("sign_up_email", email); + router.push("/sign-up/email-sent"); + } else { + alert(formatApiError(await res.json())); + } + }) + .catch((err) => { + console.error("Fetch error:", err); + alert("An error occurred. Please try again."); + }); + + isSubmitting.current = false; }; return ( From 7a5ae7e5e5ef9b751990fdd2039d30d9aa5a4411 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 13:10:33 -0400 Subject: [PATCH 09/15] Add resend verification email API functionality --- app/sign-up/email-sent/page.tsx | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/app/sign-up/email-sent/page.tsx b/app/sign-up/email-sent/page.tsx index 8f892600..32e3c9a2 100644 --- a/app/sign-up/email-sent/page.tsx +++ b/app/sign-up/email-sent/page.tsx @@ -1,5 +1,6 @@ "use client"; +import formatApiError from "@/app/_utils/format-api-error"; import { useRouter } from "next/navigation"; import { useEffect, useRef } from "react"; import MessagePage from "../../ui/layout/message-page"; @@ -23,7 +24,7 @@ export default function Page() { return null; } - const handleResendEmail = () => { + const handleResendEmail = async () => { const emailResendCooldown = 30000; // 30 seconds let timeLeft = (emailResendCooldown - (Date.now() - lastEmailResend.current)) / 1000; @@ -32,8 +33,24 @@ export default function Page() { alert(`Slow down! ${timeLeft} seconds until you can send again.`); return; } - // TODO: Replace with real resend email API logic - console.log("Resending email to:", email); + + await fetch("/api/auth/resend-register-email/", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email }), + }) + .then(async (res) => { + if (res.ok) { + alert("Email resent. Please check your inbox."); + } else { + alert(formatApiError(await res.json())); + } + }) + .catch((err) => { + console.error("Fetch error:", err); + alert("An error occurred. Please try again."); + }); + lastEmailResend.current = Date.now(); }; From 943382d08e36d91c04798a0592b54799e6680221 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 13:18:40 -0400 Subject: [PATCH 10/15] Add verify email API functionality --- app/verify-email/page.tsx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/app/verify-email/page.tsx b/app/verify-email/page.tsx index 2009f810..8289caa1 100644 --- a/app/verify-email/page.tsx +++ b/app/verify-email/page.tsx @@ -2,6 +2,7 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; +import formatApiError from "../_utils/format-api-error"; import MessagePage from "../ui/layout/message-page"; export default function Page() { @@ -14,18 +15,30 @@ export default function Page() { useEffect(() => { const verifyEmail = async () => { - // simulate network delay - await new Promise((resolve) => setTimeout(resolve, 1000)); - if (!token) { setVerifying(false); setEmailVerified(false); return; } - // TODO: Replace with an actual API call + await fetch("/api/auth/verify-email/", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ verification_code: token }), + }) + .then(async (res) => { + if (res.ok) { + setEmailVerified(true); + } else { + // don't alert anything, the page will say enough + } + }) + .catch((err) => { + console.error("Fetch error:", err); + alert("An error occurred. Please try again."); + }); + setVerifying(false); - setEmailVerified(true); }; verifyEmail(); From 9cc83d4a742bf8df9782689d902e78632c0e0bc5 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 13:34:43 -0400 Subject: [PATCH 11/15] Add forgot password API functionality --- app/forgot-password/page.tsx | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/app/forgot-password/page.tsx b/app/forgot-password/page.tsx index 806b9e1b..c05be2ab 100644 --- a/app/forgot-password/page.tsx +++ b/app/forgot-password/page.tsx @@ -1,23 +1,47 @@ "use client"; -import React, { useState } from "react"; import Link from "next/link"; -import MessagePage from "../ui/layout/message-page"; import { useRouter } from "next/navigation"; +import React, { useRef, useState } from "react"; +import formatApiError from "../_utils/format-api-error"; +import MessagePage from "../ui/layout/message-page"; export default function Page() { const [email, setEmail] = useState(""); const [emailSent, setEmailSent] = useState(false); + const isSubmitting = useRef(false); const router = useRouter(); - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + if (isSubmitting.current) return; + isSubmitting.current = true; + if (!email) { - alert("Please enter an email address."); + alert("Missing email"); + isSubmitting.current = false; return; } - setEmailSent(true); + + await fetch("/api/auth/start-password-reset/", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email }), + }) + .then(async (res) => { + if (res.ok) { + setEmailSent(true); + } else { + alert(formatApiError(await res.json())); + } + }) + .catch((err) => { + console.error("Fetch error:", err); + alert("An error occurred. Please try again."); + }); + + isSubmitting.current = false; }; return ( From e21ec077c90ac261e68dd89d780e3fecc37214d6 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 13:40:53 -0400 Subject: [PATCH 12/15] Add reset password API functionality --- app/reset-password/page.tsx | 43 ++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/app/reset-password/page.tsx b/app/reset-password/page.tsx index 15e94f87..455787da 100644 --- a/app/reset-password/page.tsx +++ b/app/reset-password/page.tsx @@ -1,35 +1,62 @@ "use client"; import { useRouter, useSearchParams } from "next/navigation"; -import React, { useState } from "react"; +import React, { useRef, useState } from "react"; +import formatApiError from "../_utils/format-api-error"; export default function Page() { const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); + const isSubmitting = useRef(false); const router = useRouter(); const searchParams = useSearchParams(); const pwdResetToken = searchParams.get("token"); - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + if (isSubmitting.current) return; + isSubmitting.current = true; + if (!pwdResetToken) { - alert("Invalid or missing password reset token."); + alert("This link is expired or invalid."); + isSubmitting.current = false; return; } - if (!newPassword || !confirmPassword) { - alert("Please fill in all fields."); + if (!newPassword) { + alert("Missing new password."); + isSubmitting.current = false; return; } - // TODO: Replace with real password reset API call if (newPassword !== confirmPassword) { alert("Passwords do not match."); - } else { - router.push("/reset-password/success"); + isSubmitting.current = false; + return; } + await fetch("/api/auth/reset-password/", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + reset_token: pwdResetToken, + new_password: newPassword, + }), + }) + .then(async (res) => { + if (res.ok) { + router.push("/reset-password/success"); + } else { + alert(formatApiError(await res.json())); + } + }) + .catch((err) => { + console.error("Fetch error:", err); + alert("An error occurred. Please try again."); + }); + + isSubmitting.current = false; }; return ( From d0509c07b5d2000db70741425931318a6ee1a222 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 13:57:44 -0400 Subject: [PATCH 13/15] Add logout button to dashboard page Very rudimentary, but it's just there to have one. --- app/dashboard/page.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index ceb0f03e..d5485992 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -60,6 +60,10 @@ export default function Page() { ); }; + const logout = async () => { + alert("Logging out..."); + }; + return (
{/* Events You Joined */} @@ -95,6 +99,16 @@ export default function Page() { ))}
+ + {/* Logout Button */} +
+ +
); } From 4e9349e76b181635b14602a4930457662057a0aa Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 3 Oct 2025 14:00:28 -0400 Subject: [PATCH 14/15] Add logout API functionality --- app/dashboard/page.tsx | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index d5485992..5733a087 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,4 +1,8 @@ -import React from "react"; +"use client"; + +import { useRouter } from "next/navigation"; +import { useRef } from "react"; +import formatApiError from "../_utils/format-api-error"; type Event = { id: string; @@ -7,6 +11,9 @@ type Event = { }; export default function Page() { + const router = useRouter(); + const isSubmitting = useRef(false); + // Mock data for testing const joinedEvents: Event[] = [ { @@ -61,7 +68,26 @@ export default function Page() { }; const logout = async () => { - alert("Logging out..."); + if (isSubmitting.current) return; + isSubmitting.current = true; + + await fetch("/api/auth/logout/", { + method: "POST", + headers: { "Content-Type": "application/json" }, + }) + .then(async (res) => { + if (res.ok) { + router.push("/login"); + } else { + alert(formatApiError(await res.json())); + } + }) + .catch((err) => { + console.error("Fetch error:", err); + alert("An error occurred. Please try again."); + }); + + isSubmitting.current = false; }; return ( From add88a7898b08e51a93c282331ad1b5ca3e336c2 Mon Sep 17 00:00:00 2001 From: Jack Zgombic <69125339+jzgom067@users.noreply.github.com> Date: Tue, 7 Oct 2025 22:57:16 -0400 Subject: [PATCH 15/15] Remove empty else block --- app/verify-email/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/verify-email/page.tsx b/app/verify-email/page.tsx index 8289caa1..e599c33d 100644 --- a/app/verify-email/page.tsx +++ b/app/verify-email/page.tsx @@ -29,8 +29,6 @@ export default function Page() { .then(async (res) => { if (res.ok) { setEmailVerified(true); - } else { - // don't alert anything, the page will say enough } }) .catch((err) => {