Compare commits

...

2 Commits

Author SHA1 Message Date
jackiettran
b02ec19d5c navbar menu styling 2025-12-23 23:08:36 -05:00
jackiettran
2a32470758 text changes, error styling, navbar menu styling 2025-12-23 23:08:22 -05:00
6 changed files with 160 additions and 33 deletions

View File

@@ -81,7 +81,7 @@ const validateRegistration = [
.withMessage("Password must be between 8 and 128 characters") .withMessage("Password must be between 8 and 128 characters")
.matches(passwordStrengthRegex) .matches(passwordStrengthRegex)
.withMessage( .withMessage(
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character" "Password does not meet requirements"
) )
.custom((value) => { .custom((value) => {
if (commonPasswords.includes(value.toLowerCase())) { if (commonPasswords.includes(value.toLowerCase())) {
@@ -275,7 +275,7 @@ const validateResetPassword = [
.withMessage("Password must be between 8 and 128 characters") .withMessage("Password must be between 8 and 128 characters")
.matches(passwordStrengthRegex) .matches(passwordStrengthRegex)
.withMessage( .withMessage(
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character" "Password does not meet requirements"
) )
.custom((value) => { .custom((value) => {
if (commonPasswords.includes(value.toLowerCase())) { if (commonPasswords.includes(value.toLowerCase())) {

View File

@@ -206,8 +206,7 @@ router.post(
if (!user) { if (!user) {
return res.status(401).json({ return res.status(401).json({
error: error: "Please check your email and password, or create an account.",
"Unable to log in. Please check your email and password, or create an account.",
}); });
} }
@@ -226,8 +225,7 @@ router.post(
// Increment login attempts // Increment login attempts
await user.incLoginAttempts(); await user.incLoginAttempts();
return res.status(401).json({ return res.status(401).json({
error: error: "Please check your email and password, or create an account.",
"Unable to log in. Please check your email and password, or create an account.",
}); });
} }

View File

@@ -10,7 +10,7 @@
content="Village Share - Life is too expensive. Rent or borrow from your neighbors" content="Village Share - Life is too expensive. Rent or borrow from your neighbors"
/> />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Village Share - Community Rental Marketplace</title> <title>Village Share</title>
<link <link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet" rel="stylesheet"

View File

@@ -112,9 +112,32 @@ const AuthModal: React.FC<AuthModalProps> = ({
const handleEmailSubmit = async (e: React.FormEvent) => { const handleEmailSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setLoading(true);
setError(""); setError("");
// Custom validation to match app's error styling
if (mode === "signup") {
if (!firstName.trim()) {
setError("Please enter your first name");
return;
}
if (!lastName.trim()) {
setError("Please enter your last name");
return;
}
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email.trim()) {
setError("Please enter your email address");
return;
}
if (!emailRegex.test(email)) {
setError("Please enter a valid email address");
return;
}
setLoading(true);
try { try {
if (mode === "login") { if (mode === "login") {
await login(email, password); await login(email, password);
@@ -132,7 +155,10 @@ const AuthModal: React.FC<AuthModalProps> = ({
// Don't call onHide() - keep modal context for verification // Don't call onHide() - keep modal context for verification
} }
} catch (err: any) { } catch (err: any) {
setError(err.response?.data?.error || "An error occurred"); // Show first specific validation error if available, otherwise generic error
const details = err.response?.data?.details;
const specificError = details?.[0]?.message;
setError(specificError || err.response?.data?.error || "An error occurred");
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -202,7 +228,6 @@ const AuthModal: React.FC<AuthModalProps> = ({
className="form-control" className="form-control"
value={firstName} value={firstName}
onChange={(e) => setFirstName(e.target.value)} onChange={(e) => setFirstName(e.target.value)}
required
/> />
</div> </div>
<div className="col"> <div className="col">
@@ -212,7 +237,6 @@ const AuthModal: React.FC<AuthModalProps> = ({
className="form-control" className="form-control"
value={lastName} value={lastName}
onChange={(e) => setLastName(e.target.value)} onChange={(e) => setLastName(e.target.value)}
required
/> />
</div> </div>
</div> </div>
@@ -222,11 +246,12 @@ const AuthModal: React.FC<AuthModalProps> = ({
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Email</label> <label className="form-label">Email</label>
<input <input
type="email" type="text"
className="form-control" className="form-control"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
required placeholder="name@example.com"
autoComplete="email"
/> />
</div> </div>
@@ -298,6 +323,7 @@ const AuthModal: React.FC<AuthModalProps> = ({
className="text-primary text-decoration-none" className="text-primary text-decoration-none"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
setError("");
setMode(mode === "login" ? "signup" : "login"); setMode(mode === "login" ? "signup" : "login");
}} }}
> >

View File

@@ -25,9 +25,21 @@ const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setLoading(true);
setError(""); setError("");
// Custom email validation to match app's error styling
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email.trim()) {
setError("Please enter your email address");
return;
}
if (!emailRegex.test(email)) {
setError("Please enter a valid email address");
return;
}
setLoading(true);
try { try {
await authAPI.forgotPassword(email); await authAPI.forgotPassword(email);
setSuccess(true); setSuccess(true);
@@ -124,12 +136,12 @@ const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Email Address</label> <label className="form-label">Email Address</label>
<input <input
type="email" type="text"
className="form-control" className="form-control"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com" placeholder="your@email.com"
required autoComplete="email"
/> />
</div> </div>

View File

@@ -107,6 +107,36 @@ const Navbar: React.FC = () => {
return ( return (
<> <>
<style>
{`
@media (max-width: 991.98px) {
#navbarNav {
position: absolute;
top: calc(100% - 8px);
right: 0.5rem;
left: auto;
z-index: 1000;
min-width: 10rem;
padding: 0;
background-color: #fff;
border: 1px solid rgba(0,0,0,.15);
border-radius: 0.375rem;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,.175);
}
#navbarNav .mobile-menu {
padding: 0;
}
#navbarNav .dropdown-item {
padding: 0.25rem 1rem;
}
#navbarNav .dropdown-divider {
margin: 0.5rem 0;
border-top: 1px solid rgba(0,0,0,.15);
opacity: 1;
}
}
`}
</style>
<nav className="navbar navbar-expand-lg navbar-light bg-white shadow-sm"> <nav className="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div className="container-fluid" style={{ maxWidth: "1800px" }}> <div className="container-fluid" style={{ maxWidth: "1800px" }}>
<Link className="navbar-brand fw-bold" to="/"> <Link className="navbar-brand fw-bold" to="/">
@@ -190,11 +220,67 @@ const Navbar: React.FC = () => {
)} )}
</button> </button>
<div className="collapse navbar-collapse" id="navbarNav"> <div className="collapse navbar-collapse" id="navbarNav">
<div className="d-flex align-items-center justify-content-center justify-content-lg-end w-100"> <div
className="d-flex align-items-center justify-content-center justify-content-lg-end w-100 py-3 py-lg-0 mobile-menu"
>
<ul className="navbar-nav flex-column flex-lg-row"> <ul className="navbar-nav flex-column flex-lg-row">
{user ? ( {user ? (
<> <>
<li className="nav-item dropdown"> {/* Mobile menu - show items directly */}
<li className="d-lg-none">
<Link className="dropdown-item" to="/profile">
<i className="bi bi-person me-2"></i>Profile
</Link>
</li>
<li className="d-lg-none">
<Link className="dropdown-item" to="/renting">
<i className="bi bi-calendar-check me-2"></i>Renting
</Link>
</li>
<li className="d-lg-none">
<Link className="dropdown-item" to="/owning">
<i className="bi bi-list-ul me-2"></i>Owning
{pendingRequestsCount > 0 && (
<span className="badge bg-danger rounded-pill ms-2">
{pendingRequestsCount}
</span>
)}
</Link>
</li>
<li className="d-lg-none">
<Link className="dropdown-item" to="/forum">
<i className="bi bi-chat-dots me-2"></i>Forum
</Link>
</li>
<li className="d-lg-none">
<Link className="dropdown-item" to="/earnings">
<i className="bi bi-cash-coin me-2"></i>Earnings
</Link>
</li>
<li className="d-lg-none">
<Link className="dropdown-item" to="/messages">
<i className="bi bi-envelope me-2"></i>Messages
{unreadMessagesCount > 0 && (
<span className="badge bg-danger rounded-pill ms-2">
{unreadMessagesCount}
</span>
)}
</Link>
</li>
<li className="d-lg-none">
<hr className="dropdown-divider" />
</li>
<li className="d-lg-none">
<button
className="dropdown-item"
onClick={handleLogout}
>
<i className="bi bi-box-arrow-right me-2"></i>Logout
</button>
</li>
{/* Desktop dropdown */}
<li className="nav-item dropdown d-none d-lg-block">
<a <a
className="nav-link dropdown-toggle" className="nav-link dropdown-toggle"
href="#" href="#"
@@ -301,29 +387,34 @@ const Navbar: React.FC = () => {
</> </>
) : ( ) : (
<> <>
<li className="nav-item text-center text-lg-end"> {/* Mobile menu items */}
<Link <li className="d-lg-none">
className="nav-link d-lg-none" <Link className="dropdown-item" to="/forum">
to="/forum" <i className="bi bi-chat-dots me-2"></i>Forum
>
Forum
</Link> </Link>
</li>
<li className="d-lg-none">
<button
className="dropdown-item fw-bold"
style={{ color: "#000000" }}
onClick={() => openAuthModal("login")}
>
<i className="bi bi-box-arrow-in-right me-2"></i>Login or Sign Up
</button>
</li>
{/* Desktop buttons */}
<li className="nav-item d-none d-lg-block">
<Link <Link
className="btn btn-outline-primary btn-sm text-nowrap d-none d-lg-inline-block me-2" className="btn btn-outline-primary btn-sm text-nowrap me-2"
to="/forum" to="/forum"
> >
Forum Forum
</Link> </Link>
</li> </li>
<li className="nav-item text-center text-lg-end"> <li className="nav-item d-none d-lg-block">
<button <button
className="nav-link d-lg-none w-100 border-0 bg-transparent" className="btn btn-primary btn-sm text-nowrap"
onClick={() => openAuthModal("login")}
>
Login or Sign Up
</button>
<button
className="btn btn-primary btn-sm text-nowrap d-none d-lg-inline-block"
onClick={() => openAuthModal("login")} onClick={() => openAuthModal("login")}
> >
Login or Sign Up Login or Sign Up