From 57e9daf7723c8afdc6bd53d5f14721c3065c3484 Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 17 Mar 2025 14:50:18 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20user=20store=20=EC=83=9D=EC=84=B1(zusta?= =?UTF-8?q?nd)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/package.json | 3 +- apps/cs/src/app/providers/app-provider.tsx | 19 ++-- apps/cs/src/entities/auth/index.ts | 2 +- .../src/entities/auth/lib/auth-provider.tsx | 92 ------------------- apps/cs/src/entities/auth/ui/auth-wrapper.tsx | 46 ++++++++++ apps/cs/src/shared/lib/cookie-provider.tsx | 30 ------ apps/cs/src/shared/lib/index.ts | 2 +- apps/cs/src/shared/lib/use-user-store.ts | 14 +++ apps/cs/src/shared/providers/index.ts | 2 + .../providers/query-provider.tsx | 0 .../src/shared/providers/store-provider.tsx | 27 ++++++ apps/cs/src/shared/store/index.ts | 1 + apps/cs/src/shared/store/user-store.ts | 56 +++++++++++ pnpm-lock.yaml | 25 +++++ 14 files changed, 184 insertions(+), 135 deletions(-) delete mode 100644 apps/cs/src/entities/auth/lib/auth-provider.tsx create mode 100644 apps/cs/src/entities/auth/ui/auth-wrapper.tsx delete mode 100644 apps/cs/src/shared/lib/cookie-provider.tsx create mode 100644 apps/cs/src/shared/lib/use-user-store.ts create mode 100644 apps/cs/src/shared/providers/index.ts rename apps/cs/src/{app => shared}/providers/query-provider.tsx (100%) create mode 100644 apps/cs/src/shared/providers/store-provider.tsx create mode 100644 apps/cs/src/shared/store/index.ts create mode 100644 apps/cs/src/shared/store/user-store.ts diff --git a/apps/cs/package.json b/apps/cs/package.json index 402f0d414..4b400a285 100644 --- a/apps/cs/package.json +++ b/apps/cs/package.json @@ -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", diff --git a/apps/cs/src/app/providers/app-provider.tsx b/apps/cs/src/app/providers/app-provider.tsx index 7bd2ec683..cf477dcc0 100644 --- a/apps/cs/src/app/providers/app-provider.tsx +++ b/apps/cs/src/app/providers/app-provider.tsx @@ -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 ( - - - - {children} - - - + + + + {children} + + + ); }; diff --git a/apps/cs/src/entities/auth/index.ts b/apps/cs/src/entities/auth/index.ts index 97579d0a5..dc3fb96c9 100644 --- a/apps/cs/src/entities/auth/index.ts +++ b/apps/cs/src/entities/auth/index.ts @@ -1,2 +1,2 @@ export { LoginApi } from "./api/login-service"; -export { AuthProvider } from "./lib/auth-provider"; +export { AuthWrapper } from "./ui/auth-wrapper"; diff --git a/apps/cs/src/entities/auth/lib/auth-provider.tsx b/apps/cs/src/entities/auth/lib/auth-provider.tsx deleted file mode 100644 index 551cfa79d..000000000 --- a/apps/cs/src/entities/auth/lib/auth-provider.tsx +++ /dev/null @@ -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(null); - -export const AuthProvider = ({ children }: { children: React.ReactNode }) => { - const router = useRouter(); - const [userInfo, setUserInfo] = useState(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( - `${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 ( - - {children} - - ); -}; diff --git a/apps/cs/src/entities/auth/ui/auth-wrapper.tsx b/apps/cs/src/entities/auth/ui/auth-wrapper.tsx new file mode 100644 index 000000000..ba175e697 --- /dev/null +++ b/apps/cs/src/entities/auth/ui/auth-wrapper.tsx @@ -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( + `${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 }; diff --git a/apps/cs/src/shared/lib/cookie-provider.tsx b/apps/cs/src/shared/lib/cookie-provider.tsx deleted file mode 100644 index f844d4f8a..000000000 --- a/apps/cs/src/shared/lib/cookie-provider.tsx +++ /dev/null @@ -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(null); - -export function CookieProvider({ children }: { children: React.ReactNode }) { - const cookieContextValue: CookieContextType = { - getCookie, - setCookie, - removeCookie, - }; - - return ( - - {children} - - ); -} diff --git a/apps/cs/src/shared/lib/index.ts b/apps/cs/src/shared/lib/index.ts index 0f4be9c9b..259525de6 100644 --- a/apps/cs/src/shared/lib/index.ts +++ b/apps/cs/src/shared/lib/index.ts @@ -1,2 +1,2 @@ -export { CookieProvider } from "./cookie-provider"; export { getCookie, setCookie, removeCookie } from "./cookie"; +export { useUserStore } from "./use-user-store"; diff --git a/apps/cs/src/shared/lib/use-user-store.ts b/apps/cs/src/shared/lib/use-user-store.ts new file mode 100644 index 000000000..f4bf9763a --- /dev/null +++ b/apps/cs/src/shared/lib/use-user-store.ts @@ -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 = (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); +}; diff --git a/apps/cs/src/shared/providers/index.ts b/apps/cs/src/shared/providers/index.ts new file mode 100644 index 000000000..5d9f096a3 --- /dev/null +++ b/apps/cs/src/shared/providers/index.ts @@ -0,0 +1,2 @@ +export { QueryProvider } from "./query-provider"; +export { StoreProvider } from "./store-provider"; diff --git a/apps/cs/src/app/providers/query-provider.tsx b/apps/cs/src/shared/providers/query-provider.tsx similarity index 100% rename from apps/cs/src/app/providers/query-provider.tsx rename to apps/cs/src/shared/providers/query-provider.tsx diff --git a/apps/cs/src/shared/providers/store-provider.tsx b/apps/cs/src/shared/providers/store-provider.tsx new file mode 100644 index 000000000..ec913f235 --- /dev/null +++ b/apps/cs/src/shared/providers/store-provider.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { createContext, useRef } from "react"; +import { createUserStore } from "@/shared/store/user-store"; + +type StoreContextType = { + userStore: ReturnType; +}; + +export const StoreContext = createContext( + undefined, +); + +export const StoreProvider = ({ children }: { children: React.ReactNode }) => { + const storeRef = useRef(null); + if (storeRef.current === null) { + storeRef.current = { + userStore: createUserStore(), + }; + } + + return ( + + {children} + + ); +}; diff --git a/apps/cs/src/shared/store/index.ts b/apps/cs/src/shared/store/index.ts new file mode 100644 index 000000000..4dae19ced --- /dev/null +++ b/apps/cs/src/shared/store/index.ts @@ -0,0 +1 @@ +export { createUserStore, type IUserInfo } from "./user-store"; diff --git a/apps/cs/src/shared/store/user-store.ts b/apps/cs/src/shared/store/user-store.ts new file mode 100644 index 000000000..00379fb1f --- /dev/null +++ b/apps/cs/src/shared/store/user-store.ts @@ -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()((set) => ({ + ...initState, + setUserInfo: (userInfo: IUserInfo | null) => set(() => ({ userInfo })), + })); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58b9fe203..484efdd1d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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