Skip to content

Commit 4ed302e

Browse files
committed
Working on polishing my frontend. Adjusted my TokenDashboardPage to keep its Token Rotation History panel user-scoped but in a client-side sense (still same localStorage setup, this feature is purely cosmetic and for demo purposes so won't be maping it to a user-scoped audit table, like you would in a production setting, because it's really besides the point). Thin frontend, thick backend (the latter is what's being showcased).
1 parent 833202d commit 4ed302e

10 files changed

Lines changed: 197 additions & 101 deletions

File tree

springqpro-backend/src/main/resources/application.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ spring:
1616
redis:
1717
host: ${REDIS_HOST:localhost}
1818
port: ${REDIS_PORT:6379}
19+
password: ""
20+
ssl: false
1921
timeout: 2000ms
2022
lettuce:
2123
pool:

springqpro-frontend/src/api/api.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
/* api.ts:
2+
----------
3+
Pretty standard stuff - this file is a thin, stateless wrapper around fetch.
4+
It's just the intermediary layer for frontend-to-backend interaction.
5+
***
6+
NOTE: Definitely some things to tidy up stylistically and conventions-wise (see getRedisTokenStatus comment) but low priority.
7+
*/
8+
19
export const API_BASE = import.meta.env.VITE_API_BASE.replace(/\/+$/, "");
210

311
// Auth-related API calls to AuthenticationController.java in the backend:

springqpro-frontend/src/components/NavBar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// NavBar.tsx - Component post-login on GUI that lets you logout and navigate to other frontend pages:
12
import React from "react";
23
import { Link, useNavigate } from "react-router-dom";
34
import { useAuth } from "../utility/auth/AuthContext.tsx";

springqpro-frontend/src/pages/LoginPage.tsx

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,20 @@ import { loginUser } from "../api/api.ts";
33
import { useAuth } from "../utility/auth/AuthContext";
44
import { useNavigate } from "react-router-dom";
55

6-
/* 2025-12-02-NOTE: To be frank, I'm on a time crunch so I'm just going to be copying
7-
my Login Page design from my "Hack MD Clone" CMDE Project and adjusting it slightly aesthetically.
8-
TO-DO: Maybe replace this with a more original design later - make a new branch and overhaul the frontend
9-
whenever I've got the time to work on my atrocious frontend HTML/CSS skills (make this look nice).
10-
*/
116
export default function LoginPage() {
7+
// React Refs:
128
const emailRef = useRef<HTMLInputElement>(null);
139
const passRef = useRef<HTMLInputElement>(null);
1410
const signInBtnRef = useRef<HTMLButtonElement>(null);
11+
// Local UI state:
1512
const [emailError, setEmailError] = useState(false);
1613
const [passwordError, setPasswordError] = useState(false);
1714
const [serverError, setServerError] = useState<string | null>(null);
15+
// External hook(s) for authentication + routing:
1816
const { login } = useAuth();
1917
const navigate = useNavigate();
2018

21-
// Validate fields are filled:
19+
// Validate fields are filled helper (client-side guardrail):
2220
const checkFormsFilled = () => {
2321
const email = emailRef.current?.value ?? "";
2422
const pass = passRef.current?.value ?? "";
@@ -35,31 +33,18 @@ export default function LoginPage() {
3533
return true;
3634
};
3735

38-
// ENTER key submits login:
39-
useEffect(() => {
40-
const handleEnterKey = (e: KeyboardEvent) => {
41-
if (e.key === "Enter") signInBtnRef.current?.click();
42-
};
43-
44-
const emailInput = emailRef.current;
45-
const passInput = passRef.current;
46-
if (emailInput) emailInput.addEventListener("keydown", handleEnterKey);
47-
if (passInput) passInput.addEventListener("keydown", handleEnterKey);
48-
49-
return () => {
50-
if (emailInput) emailInput.removeEventListener("keydown", handleEnterKey);
51-
if (passInput) passInput.removeEventListener("keydown", handleEnterKey);
52-
};
53-
}, []);
54-
36+
// Login handler (core behavior on this page):
5537
const handleLogin = async(e: React.FormEvent) => {
5638
e.preventDefault();
39+
// Validate inputs:
5740
setServerError(null);
5841
if(!checkFormsFilled()) return;
42+
// Read values from refs:
5943
const email = emailRef.current!.value;
6044
const password = passRef.current!.value;
6145

6246
try {
47+
// Call backend and, on success, store tokens via AuthContext and navigate to the dashboard (post-auth landing page).
6348
const result = await loginUser(email, password);
6449
if (result.accessToken) {
6550
login(result.accessToken, result.refreshToken); // localStorage of accessToken and refreshToken will take place in AuthContext.tsx
@@ -71,7 +56,24 @@ export default function LoginPage() {
7156
setServerError("[Server/Network Error] Login failed unexpectedly. Try again.");
7257
}
7358
}
74-
59+
60+
// UseEffect hook to handle Login submission via ENTER key:
61+
useEffect(() => {
62+
const handleEnterKey = (e: KeyboardEvent) => {
63+
if (e.key === "Enter") signInBtnRef.current?.click();
64+
};
65+
66+
const emailInput = emailRef.current;
67+
const passInput = passRef.current;
68+
if (emailInput) emailInput.addEventListener("keydown", handleEnterKey);
69+
if (passInput) passInput.addEventListener("keydown", handleEnterKey);
70+
71+
return () => {
72+
if (emailInput) emailInput.removeEventListener("keydown", handleEnterKey);
73+
if (passInput) passInput.removeEventListener("keydown", handleEnterKey);
74+
};
75+
}, []);
76+
7577
return(
7678
<div style={{
7779
height: "100vh",
@@ -81,7 +83,6 @@ export default function LoginPage() {
8183
display: "flex",
8284
justifyContent: "center",
8385
alignItems: "center",
84-
/*paddingTop: "40px"*/
8586
}}>
8687
{/* Outer-most <div> element containing the "Sign in to SoringQueuePro" box. Should be centered in the middle of the screen: */}
8788
<div style={{
@@ -104,6 +105,7 @@ export default function LoginPage() {
104105
<div style={{ color: "#cc0000", fontSize: "14px" }}>{serverError}</div>
105106
)}
106107

108+
{/* The core Login form area: */}
107109
<form
108110
onSubmit={handleLogin}
109111
style={{

springqpro-frontend/src/pages/RegisterPage.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,26 @@ import React, { useRef, useState } from "react";
22
import { useNavigate, Link } from "react-router-dom";
33
import { registerUser } from "../api/api";
44

5-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
6-
7-
/* 2025-12-02-NOTE: To be frank, I'm on a time crunch so I'm just going to be copying
8-
my Register Page design from my "Hack MD Clone" CMDE Project and adjusting it slightly aesthetically.
9-
TO-DO: Maybe replace this with a more original design later - make a new branch and overhaul the frontend
10-
whenever I've got the time to work on my atrocious frontend HTML/CSS skills (make this look nice).
5+
/* NOTE-TO-SELF: (for when I return to my frontend code after an absence...)
6+
* - AuthContext is not used in RegisterPage.tsx (which is mainly for Login purposes w/ token identity storage).
117
*/
8+
9+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // Email formatting regex (validation is purely syntax and structural).
10+
1211
export default function RegisterPage() {
12+
// React Refs:
1313
const emailRef = useRef<HTMLInputElement | null>(null);
1414
const passwordRef = useRef<HTMLInputElement | null>(null);
15+
// Local UI state:
1516
const [emailError, setEmailError] = useState<string | null>(null);
1617
const [passwordError, setPasswordError] = useState<string | null>(null);
1718
const [submitError, setSubmitError] = useState<string | null>(null);
1819
const [isSubmitting, setIsSubmitting] = useState(false);
20+
// External hook(s) for routing:
1921
const navigate = useNavigate();
2022

23+
// Helper method for setting the local UI state variables (NOTE: here, but not LoginPage.tsx, just because they're more frequently used).
24+
// NOTE:+TO-DO: Maybe I can abstract this and anything else I find into a helper methods file...
2125
const showTransient = (
2226
setter: React.Dispatch<React.SetStateAction<string | null>>,
2327
message: string
@@ -26,7 +30,8 @@ export default function RegisterPage() {
2630
setTimeout(() => setter(null), 2500);
2731
};
2832

29-
// EDIT: I now realize that <input> w/ type="email" handles most of this itself, but carrying these over too anyways.
33+
// Validation logic for Registration form (client-side guard to prevent unnecessary calls to the server):
34+
// NOTE: I now realize that <input> w/ type="email" handles most of this itself, but carrying this over anyways.
3035
const validateForm = (): boolean => {
3136
const email = emailRef.current?.value.trim() ?? "";
3237
const password = passwordRef.current?.value ?? "";
@@ -49,19 +54,21 @@ export default function RegisterPage() {
4954
return true;
5055
};
5156

57+
// Registration Handler (core behavior on this page):
5258
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
5359
e.preventDefault();
60+
// Validate inputs:
5461
setSubmitError(null);
5562
if (!validateForm()) return;
56-
63+
// Read value from Refs:
5764
const email = emailRef.current!.value.trim();
5865
const password = passwordRef.current!.value;
5966

6067
try {
68+
// Call backend for registration and, on success, navigate to the Login page (else display error):
6169
setIsSubmitting(true);
6270
const result = await registerUser(email, password);
6371

64-
// Adjust this check depending on how your API wrapper is implemented
6572
if (result?.status === "registered") {
6673
// NOTE:+TO-DO: When I have the time I want a pop-up box in the top-right or bottom-right corner that goes "You've successfully registered!" (disappears after).
6774
navigate("/login");
@@ -124,6 +131,7 @@ export default function RegisterPage() {
124131
</div>
125132
)}
126133

134+
{/* The core Registration form: */}
127135
<form
128136
onSubmit={handleSubmit}
129137
style={{

0 commit comments

Comments
 (0)