diff --git a/package.json b/package.json index 7e74f292..35ff5cad 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "sockjs-client": "^1.6.1", "swiper": "^12.0.3", "tailwind-merge": "^3.3.1", - "zod": "^4.1.13" + "zod": "^4.1.13", + "zustand": "^5.0.11" }, "devDependencies": { "@commitlint/cli": "^20.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6badda1..18cc80e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ importers: zod: specifier: ^4.1.13 version: 4.1.13 + zustand: + specifier: ^5.0.11 + version: 5.0.11(@types/react@19.2.2)(immer@9.0.21)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: '@commitlint/cli': specifier: ^20.1.0 @@ -7060,6 +7063,24 @@ packages: zod@4.1.13: resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} + zustand@5.0.11: + resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@adobe/css-tools@4.4.4': {} @@ -15588,3 +15609,10 @@ snapshots: zod: 4.1.13 zod@4.1.13: {} + + zustand@5.0.11(@types/react@19.2.2)(immer@9.0.21)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): + optionalDependencies: + '@types/react': 19.2.2 + immer: 9.0.21 + react: 19.2.3 + use-sync-external-store: 1.6.0(react@19.2.3) diff --git a/src/components/layout/gnb/index.tsx b/src/components/layout/gnb/index.tsx index 4558de1b..64de01c3 100644 --- a/src/components/layout/gnb/index.tsx +++ b/src/components/layout/gnb/index.tsx @@ -4,12 +4,12 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { Icon } from '@/components/icon'; -import { useAuth } from '@/providers'; +import { useAuthStore } from '@/stores'; export const GNB = () => { const pathname = usePathname(); - const { isAuthenticated } = useAuth(); + const { isAuthenticated } = useAuthStore(); const highLightPath = (path: string) => { if (path === '/') { diff --git a/src/components/layout/header/index.tsx b/src/components/layout/header/index.tsx index 4a2a4a73..1b18be77 100644 --- a/src/components/layout/header/index.tsx +++ b/src/components/layout/header/index.tsx @@ -5,10 +5,10 @@ import Link from 'next/link'; import { Icon } from '@/components/icon'; import { CowBell } from '@/components/layout/header/cow-bell'; import { HeaderLogin } from '@/components/layout/header/header-login'; -import { useAuth } from '@/providers'; +import { useAuthStore } from '@/stores'; export const Header = () => { - const { isAuthenticated } = useAuth(); + const { isAuthenticated } = useAuthStore(); return (
diff --git a/src/components/pages/auth/login/login-toast-effect/index.tsx b/src/components/pages/auth/login/login-toast-effect/index.tsx index a8a46ce2..c4bafdef 100644 --- a/src/components/pages/auth/login/login-toast-effect/index.tsx +++ b/src/components/pages/auth/login/login-toast-effect/index.tsx @@ -4,7 +4,7 @@ import { useEffect, useRef } from 'react'; import { Toast } from '@/components/ui'; import { useToast } from '@/components/ui/toast/core'; -import { useAuth } from '@/providers'; +import { useAuthStore } from '@/stores'; type Props = { error?: string | string[]; @@ -12,7 +12,7 @@ type Props = { export const LoginToastEffect = ({ error }: Props) => { const { run } = useToast(); - const { setIsAuthenticated } = useAuth(); + const { setIsAuthenticated } = useAuthStore(); const lastErrorRef = useRef(''); useEffect(() => { diff --git a/src/hooks/use-auth/use-auth-login/index.ts b/src/hooks/use-auth/use-auth-login/index.ts index 45165489..38a305d2 100644 --- a/src/hooks/use-auth/use-auth-login/index.ts +++ b/src/hooks/use-auth/use-auth-login/index.ts @@ -8,7 +8,7 @@ import axios, { AxiosError } from 'axios'; import { API } from '@/api'; import { normalizePath } from '@/lib/auth/utils'; -import { useAuth } from '@/providers'; +import { useAuthStore } from '@/stores'; import { LoginRequest } from '@/types/service/auth'; import { CommonErrorResponse } from '@/types/service/common'; @@ -29,7 +29,7 @@ export const useLogin = () => { const [loginError, setLoginError] = useState(null); const clearLoginError = useCallback(() => setLoginError(null), []); - const { setIsAuthenticated } = useAuth(); + const { setIsAuthenticated } = useAuthStore(); const handleLogin = async (payload: LoginRequest, formApi: { reset: () => void }) => { setLoginError(null); diff --git a/src/hooks/use-auth/use-auth-logout/index.ts b/src/hooks/use-auth/use-auth-logout/index.ts index 905fcd78..85507978 100644 --- a/src/hooks/use-auth/use-auth-logout/index.ts +++ b/src/hooks/use-auth/use-auth-logout/index.ts @@ -4,12 +4,12 @@ import { useQueryClient } from '@tanstack/react-query'; import { API } from '@/api'; import { userKeys } from '@/lib/query-key/query-key-user'; -import { useAuth } from '@/providers'; +import { useAuthStore } from '@/stores'; export const useLogout = () => { const queryClient = useQueryClient(); - const { setIsAuthenticated } = useAuth(); + const { setIsAuthenticated } = useAuthStore(); const handleLogout = async () => { try { diff --git a/src/hooks/use-notification/use-notification-get-unread-count/index.ts b/src/hooks/use-notification/use-notification-get-unread-count/index.ts index 51741e09..c81f851f 100644 --- a/src/hooks/use-notification/use-notification-get-unread-count/index.ts +++ b/src/hooks/use-notification/use-notification-get-unread-count/index.ts @@ -2,10 +2,10 @@ import { useQuery } from '@tanstack/react-query'; import { API } from '@/api'; import { notificationKeys } from '@/lib/query-key/query-key-notification'; -import { useAuth } from '@/providers'; +import { useAuthStore } from '@/stores'; export const useGetNotificationUnreadCount = () => { - const { isAuthenticated } = useAuth(); + const { isAuthenticated } = useAuthStore(); const queryResult = useQuery({ queryKey: notificationKeys.unReadCount(), queryFn: () => API.notificationService.getUnreadCount(), @@ -14,10 +14,10 @@ export const useGetNotificationUnreadCount = () => { retry: false, }); - const finalData = isAuthenticated ? (queryResult.data ?? 0) : 0; + const unReadCount = isAuthenticated ? (queryResult.data ?? 0) : 0; return { ...queryResult, - data: finalData, + unReadCount, }; }; diff --git a/src/providers/index.ts b/src/providers/index.ts index 7d96907e..7ecc1093 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,4 +1,4 @@ -export { AuthProvider, useAuth } from './provider-auth'; +export { AuthProvider } from './provider-auth'; export { LazyMotionProvider } from './provider-lazy-motion'; export { MSWProvider } from './provider-msw'; export { NotificationProvider, useNotification } from './provider-notification'; diff --git a/src/providers/provider-auth/index.tsx b/src/providers/provider-auth/index.tsx index 902ae969..8b181cb5 100644 --- a/src/providers/provider-auth/index.tsx +++ b/src/providers/provider-auth/index.tsx @@ -1,17 +1,7 @@ -import React, { createContext, SetStateAction, useContext, useState } from 'react'; +'use client'; +import { useEffect } from 'react'; -interface AuthContextType { - isAuthenticated: boolean; - setIsAuthenticated: React.Dispatch>; -} - -const AuthContext = createContext(null); - -export const useAuth = () => { - const context = useContext(AuthContext); - if (!context) throw new Error('useAuth must be used in AuthProvider'); - return context; -}; +import { useAuthStore } from '@/stores'; interface Props { children: React.ReactNode; @@ -19,11 +9,11 @@ interface Props { } export const AuthProvider = ({ children, hasRefreshToken }: Props) => { - const [isAuthenticated, setIsAuthenticated] = useState(hasRefreshToken); + const { setIsAuthenticated } = useAuthStore(); + + useEffect(() => { + setIsAuthenticated(hasRefreshToken); + }, [hasRefreshToken, setIsAuthenticated]); - return ( - - {children} - - ); + return <>{children}; }; diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 00000000..80c4f6c2 --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,2 @@ +export { useAuthStore } from './useAuthStore'; +export { useNotificationStore } from './useNotificationStore'; diff --git a/src/stores/useAuthStore/index.ts b/src/stores/useAuthStore/index.ts new file mode 100644 index 00000000..251ed7f8 --- /dev/null +++ b/src/stores/useAuthStore/index.ts @@ -0,0 +1,11 @@ +import { create } from 'zustand'; + +interface AuthState { + isAuthenticated: boolean; + setIsAuthenticated: (value: boolean) => void; +} + +export const useAuthStore = create((set) => ({ + isAuthenticated: false, + setIsAuthenticated: (value) => set({ isAuthenticated: value }), +})); diff --git a/src/stores/useNotificationStore/index.ts b/src/stores/useNotificationStore/index.ts new file mode 100644 index 00000000..0e144b87 --- /dev/null +++ b/src/stores/useNotificationStore/index.ts @@ -0,0 +1,17 @@ +import { create } from 'zustand'; + +import { NotificationItem } from '@/types/service/notification'; + +interface NotificationState { + receivedData: NotificationItem | null; + setReceivedData: (value: NotificationItem | null) => void; + hasNewNotification: boolean; + setHasNewNotification: (value: boolean) => void; +} + +export const useNotificationStore = create((set) => ({ + receivedData: null, + setReceivedData: (value) => set({ receivedData: value }), + hasNewNotification: false, + setHasNewNotification: (value) => set({ hasNewNotification: value }), +}));