From c0ecec6586e314fef91931e498261778eabf549b Mon Sep 17 00:00:00 2001 From: "jihoon87.lee" Date: Wed, 4 Feb 2026 09:34:49 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Zustand=20=EC=A0=84=EC=97=AD=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EA=B4=80=EB=A6=AC=20=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EC=96=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - auth-store.ts: 사용자 인증 상태 관리 (localStorage 지속성) - ui-store.ts: UI 상태 관리 (테마, 사이드바, 모달, 토스트) --- stores/auth-store.ts | 79 ++++++++++++++++++++++++++++++ stores/ui-store.ts | 111 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 stores/auth-store.ts create mode 100644 stores/ui-store.ts diff --git a/stores/auth-store.ts b/stores/auth-store.ts new file mode 100644 index 0000000..bd27f60 --- /dev/null +++ b/stores/auth-store.ts @@ -0,0 +1,79 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +/** + * [사용자 정보 타입] + */ +export interface User { + id: string; + email: string; + name?: string; + avatar?: string; + createdAt?: string; +} + +/** + * [인증 상태 인터페이스] + */ +interface AuthState { + // ========== 상태 ========== + user: User | null; + isAuthenticated: boolean; + + // ========== 액션 ========== + setUser: (user: User | null) => void; + updateUser: (updates: Partial) => void; + logout: () => void; +} + +/** + * [인증 스토어] + * + * 전역 사용자 인증 상태를 관리합니다. + * - localStorage에 자동 저장 (persist 미들웨어) + * - 페이지 새로고침 시에도 상태 유지 + * + * @example + * ```tsx + * import { useAuthStore } from '@/stores/auth-store'; + * + * function Profile() { + * const { user, isAuthenticated, setUser } = useAuthStore(); + * + * if (!isAuthenticated) return ; + * return
Welcome, {user?.email}
; + * } + * ``` + */ +export const useAuthStore = create()( + persist( + (set) => ({ + // ========== 초기 상태 ========== + user: null, + isAuthenticated: false, + + // ========== 사용자 설정 ========== + setUser: (user) => + set({ + user, + isAuthenticated: !!user, + }), + + // ========== 사용자 정보 업데이트 ========== + updateUser: (updates) => + set((state) => ({ + user: state.user ? { ...state.user, ...updates } : null, + })), + + // ========== 로그아웃 ========== + logout: () => + set({ + user: null, + isAuthenticated: false, + }), + }), + { + name: "auth-storage", // localStorage 키 이름 + }, + ), +); diff --git a/stores/ui-store.ts b/stores/ui-store.ts new file mode 100644 index 0000000..8943a4e --- /dev/null +++ b/stores/ui-store.ts @@ -0,0 +1,111 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +/** + * [UI 상태 인터페이스] + */ +interface UIState { + // ========== 테마 ========== + theme: "light" | "dark" | "system"; + setTheme: (theme: "light" | "dark" | "system") => void; + + // ========== 사이드바 ========== + isSidebarOpen: boolean; + toggleSidebar: () => void; + setSidebarOpen: (isOpen: boolean) => void; + + // ========== 모달 ========== + isModalOpen: boolean; + modalContent: React.ReactNode | null; + openModal: (content: React.ReactNode) => void; + closeModal: () => void; + + // ========== 토스트/알림 ========== + toasts: Toast[]; + addToast: (toast: Omit) => void; + removeToast: (id: string) => void; +} + +/** + * [토스트 메시지 타입] + */ +export interface Toast { + id: string; + type: "success" | "error" | "warning" | "info"; + message: string; + duration?: number; +} + +/** + * [UI 스토어] + * + * 전역 UI 상태를 관리합니다. + * - 테마 설정 (다크/라이트 모드) + * - 사이드바 열림/닫힘 + * - 모달 상태 + * - 토스트 알림 + * + * @example + * ```tsx + * import { useUIStore } from '@/stores/ui-store'; + * + * function Header() { + * const { theme, setTheme, toggleSidebar } = useUIStore(); + * + * return ( + *
+ * + * + *
+ * ); + * } + * ``` + */ +export const useUIStore = create()( + persist( + (set) => ({ + // ========== 테마 ========== + theme: "system", + setTheme: (theme) => set({ theme }), + + // ========== 사이드바 ========== + isSidebarOpen: true, + toggleSidebar: () => + set((state) => ({ isSidebarOpen: !state.isSidebarOpen })), + setSidebarOpen: (isOpen) => set({ isSidebarOpen: isOpen }), + + // ========== 모달 ========== + isModalOpen: false, + modalContent: null, + openModal: (content) => set({ isModalOpen: true, modalContent: content }), + closeModal: () => set({ isModalOpen: false, modalContent: null }), + + // ========== 토스트 ========== + toasts: [], + addToast: (toast) => + set((state) => ({ + toasts: [ + ...state.toasts, + { + ...toast, + id: `toast-${Date.now()}-${Math.random()}`, + }, + ], + })), + removeToast: (id) => + set((state) => ({ + toasts: state.toasts.filter((toast) => toast.id !== id), + })), + }), + { + name: "ui-storage", // localStorage 키 이름 + // 일부 상태는 지속하지 않음 (모달, 토스트) + partialize: (state) => ({ + theme: state.theme, + isSidebarOpen: state.isSidebarOpen, + }), + }, + ), +);