Skip to content

Commit a822ed6

Browse files
authored
Merge pull request #18 from AIoT-Lab-BKAI:feat/refactor-admin-and-weather-map-state-management-with-zustand
feat: refactor admin and weather map state management with Zustand and remove context API
2 parents 249eae1 + 2c194c3 commit a822ed6

17 files changed

Lines changed: 266 additions & 138 deletions

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
@@ -59,7 +59,8 @@
5959
"tailwind-merge": "^3.3.1",
6060
"tailwindcss-animate": "^1.0.7",
6161
"vite-plugin-svgr": "^4.5.0",
62-
"zod": "^4.1.8"
62+
"zod": "^4.1.8",
63+
"zustand": "^5.0.8"
6364
},
6465
"devDependencies": {
6566
"@antfu/eslint-config": "^5.2.1",

src/app/provider.tsx

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
33
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
44
import { ConfigProvider } from "antd";
55
import { AxiosError } from "axios";
6-
import { AuthProvider } from "../features/auth/context";
76
import { router } from "./router";
87

98
export function AppProvider({ children }: { children: React.ReactNode }) {
@@ -30,21 +29,19 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
3029

3130
return (
3231
<QueryClientProvider client={queryClient}>
33-
<AuthProvider>
34-
<ConfigProvider
35-
theme={{
36-
token: {
37-
fontFamily: "Helvetica, sans-serif",
38-
fontSize: 16,
39-
colorPrimary: "#3881A2",
40-
},
41-
}}
42-
>
43-
{children}
44-
<TanStackRouterDevtools initialIsOpen={false} router={router} />
45-
<ReactQueryDevtools initialIsOpen={false} />
46-
</ConfigProvider>
47-
</AuthProvider>
32+
<ConfigProvider
33+
theme={{
34+
token: {
35+
fontFamily: "Helvetica, sans-serif",
36+
fontSize: 16,
37+
colorPrimary: "#3881A2",
38+
},
39+
}}
40+
>
41+
{children}
42+
<TanStackRouterDevtools initialIsOpen={false} router={router} />
43+
<ReactQueryDevtools initialIsOpen={false} />
44+
</ConfigProvider>
4845
</QueryClientProvider>
4946
);
5047
}

src/features/admin/components/header.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { useAuth } from "@/features/auth/context";
1+
import { Button } from "@/components/ui/button";
2+
import { useAuthStore } from "@/features/auth/store";
23
import { useNavigate } from "@tanstack/react-router";
34
import { Popover } from "antd";
45
import { ChevronDown, CircleUserIcon, LogOutIcon } from "lucide-react";
56
import { useState } from "react";
6-
import { useAdminLayout } from "../context";
7-
import { Button } from "@/components/ui/button";
7+
import { useAdminLayoutStore } from "../store";
8+
import { useAuth } from "@/features/auth/hooks/use-auth";
89

910
export function HeaderComponent() {
10-
const { user, logout } = useAuth();
11-
const { headerTitle } = useAdminLayout();
11+
const { user } = useAuthStore();
12+
const { logout } = useAuth();
13+
const { headerTitle } = useAdminLayoutStore();
1214
const navigate = useNavigate();
1315

1416
const [open, setOpen] = useState(false);

src/features/admin/components/sidebar.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Icon from "@mdi/react";
55
import { Link, LinkComponentProps } from "@tanstack/react-router";
66
import clsx from "clsx";
77
import { ChevronLeftIcon, ChevronRightIcon, UserIcon } from "lucide-react";
8-
import { useAdminLayout } from "../context";
8+
import { useAdminLayoutStore } from "../store";
99

1010
interface NavItemBase extends Omit<LinkComponentProps, "children"> {
1111
label: string;
@@ -47,7 +47,7 @@ export function Sidebar() {
4747
},
4848
];
4949

50-
const { sidebarCollapsed, toggleSidebar } = useAdminLayout();
50+
const { sidebarCollapsed, toggleSidebar } = useAdminLayoutStore();
5151

5252
return (
5353
<aside
@@ -97,7 +97,7 @@ export function Sidebar() {
9797
* NavigationItem -----------------------------------------------
9898
*/
9999
function NavigationItem(item: NavItem) {
100-
const { sidebarCollapsed, setHeaderTitle } = useAdminLayout();
100+
const { sidebarCollapsed, setHeaderTitle } = useAdminLayoutStore();
101101
const { icon, label, ...linkProps } = item;
102102

103103
const linkClass = clsx(
@@ -109,11 +109,15 @@ function NavigationItem(item: NavItem) {
109109
};
110110

111111
return (
112-
<Link {...linkProps} className={linkClass} activeProps={activeProps}>
112+
<Link
113+
{...linkProps}
114+
className={linkClass}
115+
activeProps={activeProps}
116+
onClick={() => {
117+
setHeaderTitle(label);
118+
}}
119+
>
113120
{({ isActive }) => {
114-
if (isActive) {
115-
setHeaderTitle(label);
116-
}
117121
return (
118122
<>
119123
<span className={clsx("flex justify-center p-1 rounded-full", isActive && "bg-main text-white")}>{icon}</span>

src/features/admin/context.tsx

Lines changed: 0 additions & 43 deletions
This file was deleted.

src/features/admin/layout.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import { Outlet } from "@tanstack/react-router";
22
import { HeaderComponent } from "./components/header";
33
import { Sidebar } from "./components/sidebar";
4-
import { AdminLayoutProvider } from "./context";
54

65
function AdminLayoutContent() {
76
return (
87
<div className="flex h-screen bg-gray-50">
98
<Sidebar />
10-
119
<main className="flex-1">
1210
<HeaderComponent />
13-
1411
<div className="h-[calc(100vh-var(--header-height))]">
1512
<Outlet />
1613
</div>
@@ -21,8 +18,6 @@ function AdminLayoutContent() {
2118

2219
export function AdminLayout() {
2320
return (
24-
<AdminLayoutProvider>
25-
<AdminLayoutContent />
26-
</AdminLayoutProvider>
21+
<AdminLayoutContent />
2722
);
2823
}

src/features/admin/pages/profile.page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { Button } from "@/components/ui/button";
22
import { Input } from "@/components/ui/input";
3-
import { useAuth } from "@/features/auth/context";
3+
import { useAuthStore } from "@/features/auth/store";
44
import { apiService } from "@/services/api.service";
55
import { useQueryClient } from "@tanstack/react-query";
66
import { notification } from "antd";
77
import { useEffect, useState } from "react";
88

99
export function DashboardProfilePage() {
10-
const { user } = useAuth();
10+
const { user } = useAuthStore();
1111
const queryClient = useQueryClient();
1212
const [formData, setFormData] = useState({
1313
name: "",

src/features/admin/store.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { create } from "zustand";
2+
import { devtools } from "zustand/middleware";
3+
4+
interface AdminLayoutState {
5+
sidebarCollapsed: boolean;
6+
headerTitle?: string;
7+
toggleSidebar: () => void;
8+
setSidebarCollapsed: (collapsed: boolean) => void;
9+
setHeaderTitle: (title?: string) => void;
10+
}
11+
12+
export const useAdminLayoutStore = create<AdminLayoutState>()(
13+
devtools(
14+
set => ({
15+
sidebarCollapsed: false,
16+
headerTitle: undefined,
17+
toggleSidebar: () => set(state => ({ sidebarCollapsed: !state.sidebarCollapsed })),
18+
setSidebarCollapsed: collapsed => set({ sidebarCollapsed: collapsed }),
19+
setHeaderTitle: title => set({ headerTitle: title }),
20+
}),
21+
{ name: "AdminLayoutStore" },
22+
),
23+
);
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
1+
import { LS_KEY_ACCESS_TOKEN } from "@/constants/ls-key.constant";
2+
import { useAuthStore } from "@/features/auth/store";
13
import { IUser } from "@/types/user";
24
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3-
import { createContext, ReactNode, use, useCallback, useMemo } from "react";
4-
import { getProfileApi } from "./apis/auth.api";
5-
6-
import { LS_KEY_ACCESS_TOKEN } from "@/constants/ls-key.constant";
5+
import { useEffect } from "react";
6+
import { getProfileApi } from "../apis/auth.api";
77

88
type LoginFn = () => Promise<{ token: string }>;
99

10-
export interface AuthContextType {
11-
user: IUser | null;
12-
isLoading: boolean;
13-
refreshUser: () => void;
14-
login: (fn: LoginFn) => Promise<void>;
15-
logout: () => void;
16-
}
17-
18-
export const AuthContext = createContext<AuthContextType | undefined>(undefined);
19-
20-
export function AuthProvider({ children }: { children: ReactNode }) {
10+
export function useAuth() {
2111
const queryClient = useQueryClient();
12+
const { setUser, setIsLoading } = useAuthStore();
2213

2314
const {
2415
data: user,
@@ -36,6 +27,16 @@ export function AuthProvider({ children }: { children: ReactNode }) {
3627
initialData: null,
3728
});
3829

30+
// Sync user data with Zustand store
31+
useEffect(() => {
32+
setUser(user);
33+
}, [user, setUser]);
34+
35+
// Sync loading state with Zustand store
36+
useEffect(() => {
37+
setIsLoading(isLoading);
38+
}, [isLoading, setIsLoading]);
39+
3940
const { mutateAsync: login } = useMutation({
4041
mutationFn: async (fn: LoginFn) => {
4142
const { token } = await fn();
@@ -47,33 +48,15 @@ export function AuthProvider({ children }: { children: ReactNode }) {
4748
},
4849
});
4950

50-
const logout = useCallback(() => {
51+
const logout = () => {
5152
localStorage.removeItem(LS_KEY_ACCESS_TOKEN);
5253
queryClient.setQueryData(["profile"], null);
53-
}, [queryClient]);
54-
55-
const authContextValue = useMemo<AuthContextType>(
56-
() => ({
57-
user,
58-
isLoading,
59-
refreshUser,
60-
login,
61-
logout,
62-
}),
63-
[user, isLoading, refreshUser, login, logout],
64-
);
65-
66-
return (
67-
<AuthContext value={authContextValue}>
68-
{children}
69-
</AuthContext>
70-
);
71-
};
72-
73-
export function useAuth() {
74-
const context = use(AuthContext);
75-
if (!context) {
76-
throw new Error("useAuth must be used within an AuthProvider");
77-
}
78-
return context;
54+
setUser(null);
55+
};
56+
57+
return {
58+
login,
59+
logout,
60+
refreshUser,
61+
};
7962
}

0 commit comments

Comments
 (0)