feat: Zustand 전역 상태 관리 스토어 추가
- auth-store.ts: 사용자 인증 상태 관리 (localStorage 지속성) - ui-store.ts: UI 상태 관리 (테마, 사이드바, 모달, 토스트)
This commit is contained in:
79
stores/auth-store.ts
Normal file
79
stores/auth-store.ts
Normal file
@@ -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<User>) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* [인증 스토어]
|
||||
*
|
||||
* 전역 사용자 인증 상태를 관리합니다.
|
||||
* - localStorage에 자동 저장 (persist 미들웨어)
|
||||
* - 페이지 새로고침 시에도 상태 유지
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { useAuthStore } from '@/stores/auth-store';
|
||||
*
|
||||
* function Profile() {
|
||||
* const { user, isAuthenticated, setUser } = useAuthStore();
|
||||
*
|
||||
* if (!isAuthenticated) return <Login />;
|
||||
* return <div>Welcome, {user?.email}</div>;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
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 키 이름
|
||||
},
|
||||
),
|
||||
);
|
||||
111
stores/ui-store.ts
Normal file
111
stores/ui-store.ts
Normal file
@@ -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<Toast, "id">) => 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 (
|
||||
* <header>
|
||||
* <button onClick={toggleSidebar}>Menu</button>
|
||||
* <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
|
||||
* Toggle Theme
|
||||
* </button>
|
||||
* </header>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const useUIStore = create<UIState>()(
|
||||
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,
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
Reference in New Issue
Block a user