feat: user store 생성(zustand)

This commit is contained in:
2025-03-17 14:50:18 +09:00
parent 39bf03c4f5
commit c81735b8b8
14 changed files with 184 additions and 135 deletions
+2 -1
View File
@@ -25,7 +25,8 @@
"react-chartjs-2": "^5.3.0",
"react-hook-form": "^7.54.2",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2"
"zod": "^3.24.2",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
+9 -10
View File
@@ -1,19 +1,18 @@
"use client";
import { ConfirmProvider } from "@frontend-opensource/use-react-hooks";
import { AuthProvider } from "@/entities/auth";
import { CookieProvider } from "@/shared/lib";
import { QueryProvider } from "./query-provider";
import { AuthWrapper } from "@/entities/auth";
import { QueryProvider, StoreProvider } from "@/shared/providers";
const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
return (
<QueryProvider>
<CookieProvider>
<AuthProvider>
<ConfirmProvider>{children}</ConfirmProvider>
</AuthProvider>
</CookieProvider>
</QueryProvider>
<StoreProvider>
<QueryProvider>
<ConfirmProvider>
<AuthWrapper>{children}</AuthWrapper>
</ConfirmProvider>
</QueryProvider>
</StoreProvider>
);
};
+1 -1
View File
@@ -1,2 +1,2 @@
export { LoginApi } from "./api/login-service";
export { AuthProvider } from "./lib/auth-provider";
export { AuthWrapper } from "./ui/auth-wrapper";
@@ -1,92 +0,0 @@
"use client";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { createContext, useState } from "react";
import { client } from "@/shared/api";
import { ENDPOINT, AUTH_USER_ID } from "@/shared/config";
import { getCookie } from "@/shared/lib";
import { LoginApi } from "../api/login-service";
interface IUserInfo {
regDt: string;
mdfcnDt: string;
rgtr: string;
mdfr: string;
userUnqId: string;
userId: string;
userName: string;
email: string;
encryptedPassword: string;
refreshToken: string;
userStateCode: string;
lastLoginDate: string;
loginFailCount: number;
pswdHnt: string;
pswdAns: string;
mphn: string;
telno: string;
zip: string;
addr: string;
daddr: string;
laeSeCd: string;
ogdpSeCd: string;
aprvYn: string;
autzrId: string;
expryYmd: string;
lastPswdChgDt: string;
googleId: string;
kakaoId: string;
naverId: string;
isSocialUser: boolean;
hasPassword: boolean;
}
export interface AuthContextType {
userInfo: IUserInfo | null;
}
export const AuthContext = createContext<AuthContextType | null>(null);
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const router = useRouter();
const [userInfo, setUserInfo] = useState<IUserInfo | null>(null);
useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS],
queryFn: async () => {
try {
const userId = getCookie(AUTH_USER_ID);
if (!userId) {
throw new Error("Missing required authentication tokens");
}
const response = await client.get<IUserInfo>(
`${ENDPOINT.USER_SERVICE.USERS}/${userId}`,
);
setUserInfo(response.data);
return response;
} catch (error) {
console.error(error);
try {
await LoginApi.silentRefresh();
} catch (refreshError) {
console.error("Token refresh failed:", refreshError);
setUserInfo(null);
router.push("/auth/login");
}
}
},
});
const authContextValue: AuthContextType = {
userInfo,
};
return (
<AuthContext.Provider value={authContextValue}>
{children}
</AuthContext.Provider>
);
};
@@ -0,0 +1,46 @@
"use client";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { client } from "@/shared/api";
import { ENDPOINT, AUTH_USER_ID } from "@/shared/config";
import { getCookie, useUserStore } from "@/shared/lib";
import type { IUserInfo } from "@/shared/store";
import { LoginApi } from "../api/login-service";
const AuthWrapper = ({ children }: { children: React.ReactNode }) => {
const router = useRouter();
const { setUserInfo } = useUserStore((state) => state);
useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS],
queryFn: async () => {
try {
const userId = getCookie(AUTH_USER_ID);
if (!userId) {
throw new Error("Missing required authentication tokens");
}
const response = await client.get<IUserInfo>(
`${ENDPOINT.USER_SERVICE.USERS}/${userId}`,
);
setUserInfo(response.data);
return response;
} catch (error) {
console.error(error);
try {
await LoginApi.silentRefresh();
} catch (refreshError) {
console.error("Token refresh failed:", refreshError);
setUserInfo(null);
router.push("/auth/login");
}
}
},
});
return <>{children}</>;
};
export { AuthWrapper };
@@ -1,30 +0,0 @@
"use client";
import { createContext } from "react";
import { getCookie, setCookie, removeCookie } from "./cookie";
export interface CookieContextType {
getCookie: (name: string) => string | undefined;
setCookie: (
name: string,
value: string,
options: Cookies.CookieAttributes,
) => void;
removeCookie: (name: string, options: Cookies.CookieAttributes) => void;
}
export const CookieContext = createContext<CookieContextType | null>(null);
export function CookieProvider({ children }: { children: React.ReactNode }) {
const cookieContextValue: CookieContextType = {
getCookie,
setCookie,
removeCookie,
};
return (
<CookieContext.Provider value={cookieContextValue}>
{children}
</CookieContext.Provider>
);
}
+1 -1
View File
@@ -1,2 +1,2 @@
export { CookieProvider } from "./cookie-provider";
export { getCookie, setCookie, removeCookie } from "./cookie";
export { useUserStore } from "./use-user-store";
+14
View File
@@ -0,0 +1,14 @@
import { useStore } from "zustand";
import { useContext } from "react";
import { type UserStore } from "@/shared/store/user-store";
import { StoreContext } from "../providers/store-provider";
export const useUserStore = <T>(selector: (store: UserStore) => T): T => {
const storeContext = useContext(StoreContext);
if (!storeContext) {
throw new Error("useUserStore must be used within StoreProvider");
}
return useStore(storeContext.userStore, selector);
};
+2
View File
@@ -0,0 +1,2 @@
export { QueryProvider } from "./query-provider";
export { StoreProvider } from "./store-provider";
@@ -0,0 +1,27 @@
"use client";
import { createContext, useRef } from "react";
import { createUserStore } from "@/shared/store/user-store";
type StoreContextType = {
userStore: ReturnType<typeof createUserStore>;
};
export const StoreContext = createContext<StoreContextType | undefined>(
undefined,
);
export const StoreProvider = ({ children }: { children: React.ReactNode }) => {
const storeRef = useRef<StoreContextType | null>(null);
if (storeRef.current === null) {
storeRef.current = {
userStore: createUserStore(),
};
}
return (
<StoreContext.Provider value={storeRef.current}>
{children}
</StoreContext.Provider>
);
};
+1
View File
@@ -0,0 +1 @@
export { createUserStore, type IUserInfo } from "./user-store";
+56
View File
@@ -0,0 +1,56 @@
import { createStore } from "zustand/vanilla";
export interface IUserInfo {
regDt: string;
mdfcnDt: string;
rgtr: string;
mdfr: string;
userUnqId: string;
userId: string;
userName: string;
email: string;
encryptedPassword: string;
refreshToken: string;
userStateCode: string;
lastLoginDate: string;
loginFailCount: number;
pswdHnt: string;
pswdAns: string;
mphn: string;
telno: string;
zip: string;
addr: string;
daddr: string;
laeSeCd: string;
ogdpSeCd: string;
aprvYn: string;
autzrId: string;
expryYmd: string;
lastPswdChgDt: string;
googleId: string;
kakaoId: string;
naverId: string;
isSocialUser: boolean;
hasPassword: boolean;
}
export type UserState = {
userInfo: IUserInfo | null;
};
export type UserActions = {
setUserInfo: (userInfo: IUserInfo | null) => void;
};
export type UserStore = UserState & UserActions;
export const defaultInitState: UserState = {
userInfo: null,
};
export const createUserStore = (initState: UserState = defaultInitState) => {
return createStore<UserStore>()((set) => ({
...initState,
setUserInfo: (userInfo: IUserInfo | null) => set(() => ({ userInfo })),
}));
};
+25
View File
@@ -87,6 +87,9 @@ importers:
zod:
specifier: ^3.24.2
version: 3.24.2
zustand:
specifier: ^5.0.3
version: 5.0.3(@types/react@18.3.18)(react@18.3.1)
devDependencies:
'@eslint/eslintrc':
specifier: ^3
@@ -7982,3 +7985,25 @@ packages:
/zod@3.24.2:
resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==}
/zustand@5.0.3(@types/react@18.3.18)(react@18.3.1):
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': ^18.3.18
immer: '>=9.0.6'
react: ^18.3.1
use-sync-external-store: '>=1.2.0'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
dependencies:
'@types/react': 18.3.18
react: 18.3.1
dev: false