Skip to content

Commit c308cd4

Browse files
Implement authentication and state management with Zustand; add useAuth hook, update Header and HomePage components, and create auth and preferences stores.
1 parent 4cabc14 commit c308cd4

File tree

18 files changed

+384
-32
lines changed

18 files changed

+384
-32
lines changed

package-lock.json

Lines changed: 31 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"sonner": "^2.0.3",
3131
"tailwind-merge": "^3.3.0",
3232
"tailwindcss": "^4.1.7",
33-
"zod": "^3.25.28"
33+
"zod": "^3.25.28",
34+
"zustand": "^5.0.5"
3435
},
3536
"devDependencies": {
3637
"@eslint/js": "^9.25.0",

src/components/layout/header.tsx

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { Link } from "react-router";
1+
import { Link, useNavigate } from "react-router";
2+
import { Button } from "../ui/button";
3+
import { useAuth } from "@/hooks";
24

35
const Header = () => {
6+
const { isAuthenticated, user, logout } = useAuth();
7+
const navigate = useNavigate();
48
return (
59
<header className="bg-white shadow-sm border-b">
610
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@@ -33,12 +37,27 @@ const Header = () => {
3337
</nav>
3438

3539
<div className="flex items-center space-x-4">
36-
<Link
37-
to="/login"
38-
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
39-
>
40-
Login
41-
</Link>
40+
{isAuthenticated ? (
41+
<>
42+
<span className="text-sm text-gray-700">
43+
Hello, {user?.name}
44+
</span>
45+
<Button
46+
variant="outline"
47+
onClick={() => logout(navigate)}
48+
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
49+
>
50+
Logout
51+
</Button>
52+
</>
53+
) : (
54+
<Link
55+
to="/login"
56+
className="text-gray-500 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium"
57+
>
58+
Login
59+
</Link>
60+
)}
4261
</div>
4362
</div>
4463
</div>

src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as useAuth } from "@/hooks/use-auth";

src/hooks/use-auth.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { useAuthStore } from "@/store";
2+
3+
const useAuth = () => {
4+
const authState = useAuthStore();
5+
// We can add any additional logic here if needed
6+
return { ...authState };
7+
};
8+
9+
export default useAuth;

src/lib/utils/with-guards.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import type { RouteGuard, RouteMode } from "@/models/route-type";
2-
import { getCurrentUser, hasPermission } from "@/services/auth-service";
3-
1+
import type { RouteGuard, RouteMode } from "@/types/route-type";
42
import type { ComponentType } from "react";
53
import { Navigate } from "react-router";
64
import { ROUTE_CONSTANTS } from "../constants";
5+
import { useAuth } from "@/hooks";
76

87
type GuardOptions = {
98
redirectPath?: string;
@@ -18,10 +17,10 @@ const withGuards = <P extends object>(
1817
const { mode = "private", redirectPath } = options;
1918

2019
const GuardedRouteComponent = (props: P) => {
21-
const user = getCurrentUser();
20+
const { isAuthenticated, hasPermission } = useAuth();
2221

2322
if (mode === "private") {
24-
if (!user) {
23+
if (!isAuthenticated) {
2524
return <Navigate to={ROUTE_CONSTANTS.LOGIN} />;
2625
}
2726

@@ -30,7 +29,7 @@ const withGuards = <P extends object>(
3029
}
3130
}
3231

33-
if (mode === "auth" && user) {
32+
if (mode === "auth" && isAuthenticated) {
3433
return <Navigate to={redirectPath || "/"} />;
3534
}
3635

src/pages/home/home-page.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import MetaData from "@/components/meta-data";
22
import { Button } from "@/components/ui/button";
3-
import { logout } from "@/services/api/auth-api";
4-
import { getCurrentUser } from "@/services/auth-service";
3+
import { useAuth } from "@/hooks";
54
import { Link, useNavigate } from "react-router";
65

76
const HomePage = () => {
8-
const user = getCurrentUser();
7+
const { user, logout } = useAuth();
98
const navigate = useNavigate();
10-
// console.log(user);
9+
10+
const handleLogout = () => {
11+
logout(navigate);
12+
};
1113
return (
1214
<>
1315
<MetaData />
@@ -32,7 +34,7 @@ const HomePage = () => {
3234
</Link>
3335

3436
<Button
35-
onClick={() => logout(navigate)}
37+
onClick={handleLogout}
3638
variant="destructive"
3739
className="mt-4"
3840
>

src/pages/login/components/login-form.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import {
1111
FormLabel,
1212
FormMessage,
1313
} from "@/components/ui/form";
14-
import { login } from "@/services/api/auth-api";
1514
import { useNavigate } from "react-router";
1615
import { ROUTE_CONSTANTS } from "@/lib/constants";
16+
import { useState } from "react";
17+
import { toast } from "sonner";
18+
import { useAuth } from "@/hooks";
1719

1820
const loginSchema = z.object({
1921
email: z.string().email("Invalid email"),
@@ -24,6 +26,9 @@ type LoginSchema = z.infer<typeof loginSchema>;
2426

2527
export function LoginForm() {
2628
const navigate = useNavigate();
29+
const { login, isLoading } = useAuth();
30+
const [error, setError] = useState<string | null>(null);
31+
2732
const form = useForm<LoginSchema>({
2833
resolver: zodResolver(loginSchema),
2934
defaultValues: {
@@ -32,13 +37,22 @@ export function LoginForm() {
3237
},
3338
});
3439

35-
const onSubmit = (values: LoginSchema) => {
36-
console.log("Login values:", values);
37-
login(values.email, values.password)
38-
.then(() => {
40+
const onSubmit = async (values: LoginSchema) => {
41+
setError(null);
42+
try {
43+
const result = await login(values.email, values.password);
44+
if (result.success) {
45+
toast.success("Login successful");
3946
navigate(ROUTE_CONSTANTS.HOME);
40-
})
41-
.catch(() => {});
47+
} else {
48+
setError(result.message || "Invalid credentials");
49+
toast.error(result.message || "Login failed");
50+
}
51+
} catch (err) {
52+
const errorMessage = err instanceof Error ? err.message : "Login failed";
53+
setError(errorMessage);
54+
toast.error(errorMessage);
55+
}
4256
};
4357

4458
// Helper function to autofill and submit
@@ -82,22 +96,26 @@ export function LoginForm() {
8296
)}
8397
/>
8498

85-
<Button type="submit" className="w-full">
86-
Login
99+
{error && <div className="text-red-500 text-sm">{error}</div>}
100+
101+
<Button type="submit" className="w-full" disabled={isLoading}>
102+
{isLoading ? "Logging in..." : "Login"}
87103
</Button>
88104

89105
<div className="flex justify-between mt-4">
90106
<Button
91107
type="button"
92108
variant="outline"
93109
onClick={() => loginAs("user@gmail.com", "password")}
110+
disabled={isLoading}
94111
>
95112
Login as User
96113
</Button>
97114
<Button
98115
type="button"
99116
variant="outline"
100117
onClick={() => loginAs("admin@gmail.com", "password")}
118+
disabled={isLoading}
101119
>
102120
Login as Admin
103121
</Button>

src/services/api/auth-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import LOCAL_STORAGE_CONSTANTS from "@/lib/constants/local-storage-constants";
22
import httpService from "../http-service";
3-
import type { User } from "@/models/user-type";
3+
import type { User } from "@/types/user-type";
44
import { ROUTE_CONSTANTS } from "@/lib/constants";
55

66
export const login = async (email: string, password: string) => {

src/services/auth-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
User,
55
UserRole,
66
UserScope,
7-
} from "@/models/user-type";
7+
} from "@/types/user-type";
88
import { jwtDecode } from "jwt-decode";
99

1010
/**

0 commit comments

Comments
 (0)