diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json
index 2d584d4..1188ff9 100644
--- a/Frontend/package-lock.json
+++ b/Frontend/package-lock.json
@@ -12,8 +12,7 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.6.0",
- "react-router-dom": "^7.13.1",
- "tailwindcss": "^4.2.1"
+ "react-router-dom": "^7.13.1"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
@@ -21,10 +20,13 @@
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
+ "autoprefixer": "^10.4.27",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
+ "postcss": "^8.5.8",
+ "tailwindcss": "^4.2.1",
"vite": "^7.3.1"
}
},
@@ -2097,6 +2099,43 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/autoprefixer": {
+ "version": "10.4.27",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz",
+ "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.1",
+ "caniuse-lite": "^1.0.30001774",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2672,6 +2711,20 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3410,6 +3463,13 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
diff --git a/Frontend/package.json b/Frontend/package.json
index 8708764..3ac4da7 100644
--- a/Frontend/package.json
+++ b/Frontend/package.json
@@ -16,8 +16,7 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.6.0",
- "react-router-dom": "^7.13.1",
- "tailwindcss": "^4.2.1"
+ "react-router-dom": "^7.13.1"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
@@ -25,10 +24,13 @@
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
+ "autoprefixer": "^10.4.27",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
+ "postcss": "^8.5.8",
+ "tailwindcss": "^4.2.1",
"vite": "^7.3.1"
}
}
diff --git a/Frontend/src/pages/login.jsx b/Frontend/src/pages/login.jsx
index 8deca66..269f240 100644
--- a/Frontend/src/pages/login.jsx
+++ b/Frontend/src/pages/login.jsx
@@ -12,6 +12,7 @@ import {
FaUser,
FaUsers,
FaUsersCog,
+ FaExclamationCircle,
} from "react-icons/fa";
const roleOptions = [
@@ -45,6 +46,28 @@ const roleOptions = [
},
];
+// Mock user database with credentials
+const mockUsers = [
+ {
+ username: "john_tenant",
+ password: "Tenant@123",
+ role: "tenant",
+ fullName: "John Tenant",
+ },
+ {
+ username: "sarah_owner",
+ password: "Owner@456",
+ role: "owner",
+ fullName: "Sarah Johnson",
+ },
+ {
+ username: "admin_user",
+ password: "Admin@789",
+ role: "admin",
+ fullName: "Admin User",
+ },
+];
+
function Login() {
const navigate = useNavigate();
const [isLogin, setIsLogin] = useState(true);
@@ -57,6 +80,90 @@ function Login() {
password: "",
confirmPassword: "",
});
+ const [errors, setErrors] = useState({});
+
+ // Validation functions
+ const validateUsername = (username) => {
+ if (!username.trim()) {
+ return "Username is required";
+ }
+ if (username.trim().length < 3) {
+ return "Username must be at least 3 characters long";
+ }
+ if (username.trim().length > 20) {
+ return "Username must not exceed 20 characters";
+ }
+ if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
+ return "Username can only contain letters, numbers, hyphens and underscores";
+ }
+ return "";
+ };
+
+ const validatePassword = (password) => {
+ if (!password) {
+ return "Password is required";
+ }
+ if (password.length < 6) {
+ return "Password must be at least 6 characters long";
+ }
+ if (password.length > 50) {
+ return "Password must not exceed 50 characters";
+ }
+ if (!/[a-z]/.test(password)) {
+ return "Password must contain at least one lowercase letter";
+ }
+ if (!/[A-Z]/.test(password)) {
+ return "Password must contain at least one uppercase letter";
+ }
+ if (!/[0-9]/.test(password)) {
+ return "Password must contain at least one number";
+ }
+ return "";
+ };
+
+ const validateEmail = (email) => {
+ if (!email.trim()) {
+ return "Email is required";
+ }
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
+ if (!emailRegex) {
+ return "Please enter a valid email address";
+ }
+ return "";
+ };
+
+ const validatePhone = (phone) => {
+ if (!phone.trim()) {
+ return "Phone number is required";
+ }
+ if (!/^[0-9+\-\s()]{10,}$/.test(phone)) {
+ return "Please enter a valid phone number";
+ }
+ return "";
+ };
+
+ const validateFullName = (fullName) => {
+ if (!fullName.trim()) {
+ return "Full name is required";
+ }
+ if (fullName.trim().length < 3) {
+ return "Full name must be at least 3 characters long";
+ }
+ if (!/^[a-zA-Z\s'-]+$/.test(fullName)) {
+ return "Full name can only contain letters, spaces, hyphens and apostrophes";
+ }
+ return "";
+ };
+
+ const validateConfirmPassword = (password, confirmPassword) => {
+ if (!confirmPassword) {
+ return "Please confirm your password";
+ }
+ if (password !== confirmPassword) {
+ return "Passwords do not match";
+ }
+ return "";
+ };
const activeRole = useMemo(
() =>
@@ -67,11 +174,77 @@ function Login() {
const handleInputChange = (event) => {
const { name, value } = event.target;
setFormData((prev) => ({ ...prev, [name]: value }));
+ // Clear error for this field when user starts typing
+ if (errors[name]) {
+ setErrors((prev) => ({ ...prev, [name]: "" }));
+ }
};
const handleSubmit = (event) => {
event.preventDefault();
- navigate(activeRole.route);
+ const newErrors = {};
+
+ // Validate username
+ const usernameError = validateUsername(formData.username);
+ if (usernameError) newErrors.username = usernameError;
+
+ // Validate password
+ const passwordError = validatePassword(formData.password);
+ if (passwordError) newErrors.password = passwordError;
+
+ // Validation for signup
+ if (!isLogin) {
+ const fullNameError = validateFullName(formData.fullName);
+ if (fullNameError) newErrors.fullName = fullNameError;
+
+ const emailError = validateEmail(formData.email);
+ if (emailError) newErrors.email = emailError;
+
+ const phoneError = validatePhone(formData.phone);
+ if (phoneError) newErrors.phone = phoneError;
+
+ const confirmPasswordError = validateConfirmPassword(
+ formData.password,
+ formData.confirmPassword
+ );
+ if (confirmPasswordError) newErrors.confirmPassword = confirmPasswordError;
+ }
+
+ if (Object.keys(newErrors).length > 0) {
+ setErrors(newErrors);
+ return;
+ }
+
+ // If login mode, authenticate against mock users
+ if (isLogin) {
+ const user = mockUsers.find(
+ (u) => u.username === formData.username && u.password === formData.password
+ );
+
+ if (!user) {
+ setErrors({
+ username: "Invalid username or password",
+ password: "Invalid username or password",
+ });
+ return;
+ }
+
+ // Check if selected role matches user's role
+ if (user.role !== selectedRole) {
+ setErrors({
+ username: `This account is for ${user.role} role, but you selected ${selectedRole}`,
+ });
+ return;
+ }
+
+ // Authentication successful
+ setErrors({});
+ navigate(activeRole.route);
+ } else {
+ // Signup - just navigate after validation passes
+ setErrors({});
+ navigate(activeRole.route);
+ }
};
return (
@@ -179,6 +352,7 @@ function Login() {
value={formData.fullName}
placeholder="Full name"
onChange={handleInputChange}
+ error={errors.fullName}
/>
)}
@@ -188,6 +362,7 @@ function Login() {
value={formData.username}
placeholder="Username"
onChange={handleInputChange}
+ error={errors.username}
/>
{!isLogin && (
@@ -199,6 +374,7 @@ function Login() {
value={formData.email}
placeholder="Email address"
onChange={handleInputChange}
+ error={errors.email}
/>
Selected role: {activeRole.label}
+ + {isLogin && ( +Demo Credentials:
+