Compare commits
2 Commits
5ec22c2a5b
...
b02ec19d5c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b02ec19d5c | ||
|
|
2a32470758 |
@@ -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())) {
|
||||
|
||||
@@ -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.",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user