Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 59 additions & 12 deletions src/features/Auth/v1/Pages/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,75 @@ import { Link, useNavigate } from "react-router";

import { useAuth } from "../hooks/useAuth";
import Input from "@/Component/ui/Input";
import { useCallback } from "react";
import { useCallback, useState } from "react";

const LoginPage = () => {
const { loginMutation } = useAuth();

const navigate = useNavigate();

const [fieldErrors, setFieldErrors] = useState<{ email?: string; password?: string }>({});
const [serverError, setServerError] = useState<string | null>(null);

const validateFields = useCallback((email: string, password: string): boolean => {
const errors: { email?: string; password?: string } = {};

if (!email.trim()) {
errors.email = "Email is required";
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
errors.email = "Please enter a valid email address";
}

if (!password) {
errors.password = "Password is required";
}

setFieldErrors(errors);
return Object.keys(errors).length === 0;
}, []);

const handleLogin = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setServerError(null);

const formData = new FormData(e.currentTarget);

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

if (!validateFields(email, password)) {
return;
}

try {
const response = await loginMutation.mutateAsync({
email,
password,
});

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

if (Role === "organization") {
navigate("/org/dashboard");
return;
}

console.log("Login successful:", response);
if (Role === "member") {
navigate("/member/dashboard");
return;
}

// redirect / save token / navigate
} catch (error) {
console.error("Login failed:", error);
navigate("/");
} catch (error: any) {
const message =
error?.response?.data?.message ||
error?.message ||
"Login failed. Please check your credentials and try again.";
setServerError(message);
}
},
[loginMutation],
[loginMutation, navigate, validateFields],
);

return (
Expand All @@ -62,7 +93,12 @@ const LoginPage = () => {
<div className="w-[80%]">
<h2 className="text-3xl mb-2 inter text-gray-700">Sign in</h2>
<p className="text-gray-500 mb-6 inter">Please login to your account to continue.</p>
<form className="space-y-4 mt-[7vh]" onSubmit={handleLogin}>
<form className="space-y-4 mt-[7vh]" onSubmit={handleLogin} noValidate>
{serverError && (
<div className="bg-red-50 border border-red-300 text-red-700 px-4 py-3 rounded-md text-sm inter">
{serverError}
</div>
)}
<div className="flex flex-col gap-2 text-md">
<label
htmlFor="email"
Expand All @@ -72,10 +108,15 @@ const LoginPage = () => {
</label>
<Input
name="email"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
fieldErrors.email ? "border-red-500" : "border-gray-300"
}`}
placeholder="Enter your email"
type="email"
/>
{fieldErrors.email && (
<p className="text-red-500 text-xs inter">{fieldErrors.email}</p>
)}
</div>
<div className="flex flex-col gap-2">
<label
Expand All @@ -93,16 +134,22 @@ const LoginPage = () => {

<Input
name="password"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
fieldErrors.password ? "border-red-500" : "border-gray-300"
}`}
placeholder="Enter your password"
type="password"
/>
{fieldErrors.password && (
<p className="text-red-500 text-xs inter">{fieldErrors.password}</p>
)}
</div>
<button
type="submit"
className="w-full bg-[#4f46e5] text-white py-2 hover:bg-blue-600 transition duration-200 inter py-[1.5vh] text-lg"
disabled={loginMutation.isPending}
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"
>
Sign In
{loginMutation.isPending ? "Signing In..." : "Sign In"}
</button>

<div className="flex justify-end">
Expand Down
4 changes: 3 additions & 1 deletion src/features/Auth/v1/Store/Auth.Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ const useAuthStore = create<AuthState>()(
token: null,
user: null,

setAuthData: (user: User) =>
setAuthData: (user: User, token?: string) =>
set({
user,
token: token ?? null,
}),

clearAuthData: () =>
set({
user: null,
token: null,
}),
}),
{
Expand Down
4 changes: 2 additions & 2 deletions src/features/Auth/v1/Types/Auth.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ export interface User {
}

export interface AuthState {
// token: string | null;
token: string | null;
user: User | null;

setAuthData: (user: User) => void;
setAuthData: (user: User, token?: string) => void;
clearAuthData: () => void;
}
3 changes: 2 additions & 1 deletion src/features/Auth/v1/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ const useLoginMutation = () => {

onSuccess: async (response) => {
const user = response.data;
const token = response.token;

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

// Save auth first
useAuthStore.getState().setAuthData(user);
useAuthStore.getState().setAuthData(user, token);

// Fetch organization if needed
if (user.role === "organization") {
Expand Down
2 changes: 0 additions & 2 deletions src/routes/OrgRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import TaskDetailPage from "@/features/Tasks/v1/pages/TaskDetailPage";
import TaskManagementPage from "@/features/Tasks/v1/pages/TaskManagementPage";

import ProtectedRoute from "./ProtectedRoute";
import { dashboardData } from "@/features/Member/v1/mock/dashboardData";

// Lazy-loaded Webhook pages
const WebhookListPage = lazy(() => import("@/features/Webhooks/v1/pages/WebhookListPage"));
Expand Down Expand Up @@ -60,7 +59,6 @@ const OrgRoute = () => {
path="dashboard/webhooks/*"
element={
<ProtectedRoute
user={dashboardData.user}
allowedRoles={["CommunityOwner", "Admin", "Organizer"]}
>
<Routes>
Expand Down
6 changes: 4 additions & 2 deletions src/routes/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Navigate } from "react-router-dom";
import useAuthStore from "@/features/Auth/v1/Store/Auth.Store";

interface Props {
children: React.ReactNode;
user: { role: string } | null;
allowedRoles: string[];
}

export default function ProtectedRoute({ children, user, allowedRoles }: Props) {
export default function ProtectedRoute({ children, allowedRoles }: Props) {
const user = useAuthStore((state) => state.user);

// Not logged in
if (!user) {
return <Navigate to="/" replace />;
Expand Down
Loading