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")
.matches(passwordStrengthRegex)
.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) => {
if (commonPasswords.includes(value.toLowerCase())) {
@@ -275,7 +275,7 @@ const validateResetPassword = [
.withMessage("Password must be between 8 and 128 characters")
.matches(passwordStrengthRegex)
.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) => {
if (commonPasswords.includes(value.toLowerCase())) {

View File

@@ -206,8 +206,7 @@ router.post(
if (!user) {
return res.status(401).json({
error:
"Unable to log in. Please check your email and password, or create an account.",
error: "Please check your email and password, or create an account.",
});
}
@@ -226,8 +225,7 @@ router.post(
// Increment login attempts
await user.incLoginAttempts();
return res.status(401).json({
error:
"Unable to log in. Please check your email and password, or create an account.",
error: "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"
/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Village Share - Community Rental Marketplace</title>
<title>Village Share</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet"

View File

@@ -112,9 +112,32 @@ const AuthModal: React.FC<AuthModalProps> = ({
const handleEmailSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
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 {
if (mode === "login") {
await login(email, password);
@@ -132,7 +155,10 @@ const AuthModal: React.FC<AuthModalProps> = ({
// Don't call onHide() - keep modal context for verification
}
} 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 {
setLoading(false);
}
@@ -202,7 +228,6 @@ const AuthModal: React.FC<AuthModalProps> = ({
className="form-control"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
required
/>
</div>
<div className="col">
@@ -212,7 +237,6 @@ const AuthModal: React.FC<AuthModalProps> = ({
className="form-control"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
required
/>
</div>
</div>
@@ -222,11 +246,12 @@ const AuthModal: React.FC<AuthModalProps> = ({
<div className="mb-3">
<label className="form-label">Email</label>
<input
type="email"
type="text"
className="form-control"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
placeholder="name@example.com"
autoComplete="email"
/>
</div>
@@ -298,6 +323,7 @@ const AuthModal: React.FC<AuthModalProps> = ({
className="text-primary text-decoration-none"
onClick={(e) => {
e.preventDefault();
setError("");
setMode(mode === "login" ? "signup" : "login");
}}
>

View File

@@ -25,9 +25,21 @@ const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
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 {
await authAPI.forgotPassword(email);
setSuccess(true);
@@ -124,12 +136,12 @@ const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({
<div className="mb-3">
<label className="form-label">Email Address</label>
<input
type="email"
type="text"
className="form-control"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
required
autoComplete="email"
/>
</div>

View File

@@ -107,6 +107,36 @@ const Navbar: React.FC = () => {
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">
<div className="container-fluid" style={{ maxWidth: "1800px" }}>
<Link className="navbar-brand fw-bold" to="/">
@@ -190,11 +220,67 @@ const Navbar: React.FC = () => {
)}
</button>
<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">
{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
className="nav-link dropdown-toggle"
href="#"
@@ -301,29 +387,34 @@ const Navbar: React.FC = () => {
</>
) : (
<>
<li className="nav-item text-center text-lg-end">
<Link
className="nav-link d-lg-none"
to="/forum"
>
Forum
{/* Mobile menu items */}
<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">
<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
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"
>
Forum
</Link>
</li>
<li className="nav-item text-center text-lg-end">
<li className="nav-item d-none d-lg-block">
<button
className="nav-link d-lg-none w-100 border-0 bg-transparent"
onClick={() => openAuthModal("login")}
>
Login or Sign Up
</button>
<button
className="btn btn-primary btn-sm text-nowrap d-none d-lg-inline-block"
className="btn btn-primary btn-sm text-nowrap"
onClick={() => openAuthModal("login")}
>
Login or Sign Up