Skip to content

Commit 3e78b77

Browse files
Merge pull request #126 from rajesh-puripanda/fix/login-authentication
fix(auth): add login validation, error handling, and fix ProtectedRoute bypass #63
2 parents 46d10ea + 02395a4 commit 3e78b77

6 files changed

Lines changed: 70 additions & 20 deletions

File tree

src/features/Auth/v1/Pages/LoginPage.tsx

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,75 @@ import { Link, useNavigate } from "react-router";
44

55
import { useAuth } from "../hooks/useAuth";
66
import Input from "@/Component/ui/Input";
7-
import { useCallback } from "react";
7+
import { useCallback, useState } from "react";
88

99
const LoginPage = () => {
1010
const { loginMutation } = useAuth();
1111

1212
const navigate = useNavigate();
1313

14+
const [fieldErrors, setFieldErrors] = useState<{ email?: string; password?: string }>({});
15+
const [serverError, setServerError] = useState<string | null>(null);
16+
17+
const validateFields = useCallback((email: string, password: string): boolean => {
18+
const errors: { email?: string; password?: string } = {};
19+
20+
if (!email.trim()) {
21+
errors.email = "Email is required";
22+
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
23+
errors.email = "Please enter a valid email address";
24+
}
25+
26+
if (!password) {
27+
errors.password = "Password is required";
28+
}
29+
30+
setFieldErrors(errors);
31+
return Object.keys(errors).length === 0;
32+
}, []);
33+
1434
const handleLogin = useCallback(
1535
async (e: React.FormEvent<HTMLFormElement>) => {
1636
e.preventDefault();
37+
setServerError(null);
1738

1839
const formData = new FormData(e.currentTarget);
1940

2041
const email = formData.get("email") as string;
2142
const password = formData.get("password") as string;
2243

44+
if (!validateFields(email, password)) {
45+
return;
46+
}
47+
2348
try {
2449
const response = await loginMutation.mutateAsync({
2550
email,
2651
password,
2752
});
2853

2954
const Role = response.data.role;
30-
console.log("User role:", Role);
3155

3256
if (Role === "organization") {
3357
navigate("/org/dashboard");
3458
return;
3559
}
3660

37-
console.log("Login successful:", response);
61+
if (Role === "member") {
62+
navigate("/member/dashboard");
63+
return;
64+
}
3865

39-
// redirect / save token / navigate
40-
} catch (error) {
41-
console.error("Login failed:", error);
66+
navigate("/");
67+
} catch (error: any) {
68+
const message =
69+
error?.response?.data?.message ||
70+
error?.message ||
71+
"Login failed. Please check your credentials and try again.";
72+
setServerError(message);
4273
}
4374
},
44-
[loginMutation],
75+
[loginMutation, navigate, validateFields],
4576
);
4677

4778
return (
@@ -62,7 +93,12 @@ const LoginPage = () => {
6293
<div className="w-[80%]">
6394
<h2 className="text-3xl mb-2 inter text-gray-700">Sign in</h2>
6495
<p className="text-gray-500 mb-6 inter">Please login to your account to continue.</p>
65-
<form className="space-y-4 mt-[7vh]" onSubmit={handleLogin}>
96+
<form className="space-y-4 mt-[7vh]" onSubmit={handleLogin} noValidate>
97+
{serverError && (
98+
<div className="bg-red-50 border border-red-300 text-red-700 px-4 py-3 rounded-md text-sm inter">
99+
{serverError}
100+
</div>
101+
)}
66102
<div className="flex flex-col gap-2 text-md">
67103
<label
68104
htmlFor="email"
@@ -72,10 +108,15 @@ const LoginPage = () => {
72108
</label>
73109
<Input
74110
name="email"
75-
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
111+
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
112+
fieldErrors.email ? "border-red-500" : "border-gray-300"
113+
}`}
76114
placeholder="Enter your email"
77115
type="email"
78116
/>
117+
{fieldErrors.email && (
118+
<p className="text-red-500 text-xs inter">{fieldErrors.email}</p>
119+
)}
79120
</div>
80121
<div className="flex flex-col gap-2">
81122
<label
@@ -93,16 +134,22 @@ const LoginPage = () => {
93134

94135
<Input
95136
name="password"
96-
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
137+
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
138+
fieldErrors.password ? "border-red-500" : "border-gray-300"
139+
}`}
97140
placeholder="Enter your password"
98141
type="password"
99142
/>
143+
{fieldErrors.password && (
144+
<p className="text-red-500 text-xs inter">{fieldErrors.password}</p>
145+
)}
100146
</div>
101147
<button
102148
type="submit"
103-
className="w-full bg-[#4f46e5] text-white py-2 hover:bg-blue-600 transition duration-200 inter py-[1.5vh] text-lg"
149+
disabled={loginMutation.isPending}
150+
className="w-full bg-[#4f46e5] text-white py-2 hover:bg-blue-600 transition duration-200 inter py-[1.5vh] text-lg disabled:opacity-50 disabled:cursor-not-allowed"
104151
>
105-
Sign In
152+
{loginMutation.isPending ? "Signing In..." : "Sign In"}
106153
</button>
107154

108155
<div className="flex justify-end">

src/features/Auth/v1/Store/Auth.Store.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ const useAuthStore = create<AuthState>()(
99
token: null,
1010
user: null,
1111

12-
setAuthData: (user: User) =>
12+
setAuthData: (user: User, token?: string) =>
1313
set({
1414
user,
15+
token: token ?? null,
1516
}),
1617

1718
clearAuthData: () =>
1819
set({
1920
user: null,
21+
token: null,
2022
}),
2123
}),
2224
{

src/features/Auth/v1/Types/Auth.type.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ export interface User {
66
}
77

88
export interface AuthState {
9-
// token: string | null;
9+
token: string | null;
1010
user: User | null;
1111

12-
setAuthData: (user: User) => void;
12+
setAuthData: (user: User, token?: string) => void;
1313
clearAuthData: () => void;
1414
}

src/features/Auth/v1/hooks/useAuth.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ const useLoginMutation = () => {
5858

5959
onSuccess: async (response) => {
6060
const user = response.data;
61+
const token = response.token;
6162

6263
console.log("Login successful:", user);
6364

6465
// Save auth first
65-
useAuthStore.getState().setAuthData(user);
66+
useAuthStore.getState().setAuthData(user, token);
6667

6768
// Fetch organization if needed
6869
if (user.role === "organization") {

src/routes/OrgRoute.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import TaskDetailPage from "@/features/Tasks/v1/pages/TaskDetailPage";
1414
import TaskManagementPage from "@/features/Tasks/v1/pages/TaskManagementPage";
1515

1616
import ProtectedRoute from "./ProtectedRoute";
17-
import { dashboardData } from "@/features/Member/v1/mock/dashboardData";
1817

1918
// Lazy-loaded Webhook pages
2019
const WebhookListPage = lazy(() => import("@/features/Webhooks/v1/pages/WebhookListPage"));
@@ -60,7 +59,6 @@ const OrgRoute = () => {
6059
path="dashboard/webhooks/*"
6160
element={
6261
<ProtectedRoute
63-
user={dashboardData.user}
6462
allowedRoles={["CommunityOwner", "Admin", "Organizer"]}
6563
>
6664
<Routes>

src/routes/ProtectedRoute.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Navigate } from "react-router-dom";
2+
import useAuthStore from "@/features/Auth/v1/Store/Auth.Store";
23

34
interface Props {
45
children: React.ReactNode;
5-
user: { role: string } | null;
66
allowedRoles: string[];
77
}
88

9-
export default function ProtectedRoute({ children, user, allowedRoles }: Props) {
9+
export default function ProtectedRoute({ children, allowedRoles }: Props) {
10+
const user = useAuthStore((state) => state.user);
11+
1012
// Not logged in
1113
if (!user) {
1214
return <Navigate to="/" replace />;

0 commit comments

Comments
 (0)