From 8adaa0072eb8702f63b38acdd3ff7013e6dd1de9 Mon Sep 17 00:00:00 2001 From: Seono-Na Date: Mon, 28 Jul 2025 05:07:14 +0900 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20zustand=20=EB=B0=8F=20react=20rout?= =?UTF-8?q?er=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zustand와 react-router-dom 의존성 설치 개발 시 필요한 타입 정의를 위해 @types/react-router-dom 개발 의존성 설치 --- package.json | 5 ++- pnpm-lock.yaml | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ab9ad3c..9c54ddf 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,11 @@ "react": "^19.0.0", "react-day-picker": "^9.7.0", "react-dom": "^19.0.0", + "react-router-dom": "^7.7.1", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.3", - "tw-animate-css": "^1.2.5" + "tw-animate-css": "^1.2.5", + "zustand": "^5.0.6" }, "devDependencies": { "@commitlint/cli": "^19.8.0", @@ -38,6 +40,7 @@ "@eslint/js": "^9.21.0", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", + "@types/react-router-dom": "^5.3.3", "@vitejs/plugin-react-swc": "^3.8.0", "eslint": "^9.24.0", "eslint-config-prettier": "^10.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c19063..0df0fcf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: react-dom: specifier: ^19.0.0 version: 19.1.0(react@19.1.0) + react-router-dom: + specifier: ^7.7.1 + version: 7.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) tailwind-merge: specifier: ^3.2.0 version: 3.3.1 @@ -59,6 +62,9 @@ importers: tw-animate-css: specifier: ^1.2.5 version: 1.3.4 + zustand: + specifier: ^5.0.6 + version: 5.0.6(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) devDependencies: '@commitlint/cli': specifier: ^19.8.0 @@ -75,6 +81,9 @@ importers: '@types/react-dom': specifier: ^19.0.4 version: 19.1.6(@types/react@19.1.8) + '@types/react-router-dom': + specifier: ^5.3.3 + version: 5.3.3 '@vitejs/plugin-react-swc': specifier: ^3.8.0 version: 3.10.2(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)) @@ -1466,6 +1475,9 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/history@4.7.11': + resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1498,6 +1510,12 @@ packages: peerDependencies: '@types/react': ^19.0.0 + '@types/react-router-dom@5.3.3': + resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} + + '@types/react-router@5.1.20': + resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} + '@types/react@19.1.8': resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} @@ -1920,6 +1938,10 @@ packages: engines: {node: '>=16'} hasBin: true + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + cosmiconfig-typescript-loader@6.1.0: resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==} engines: {node: '>=v18'} @@ -3535,6 +3557,23 @@ packages: '@types/react': optional: true + react-router-dom@7.7.1: + resolution: {integrity: sha512-bavdk2BA5r3MYalGKZ01u8PGuDBloQmzpBZVhDLrOOv1N943Wq6dcM9GhB3x8b7AbqPMEezauv4PeGkAJfy7FQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.7.1: + resolution: {integrity: sha512-jVKHXoWRIsD/qS6lvGveckwb862EekvapdHJN/cGmzw40KnJH5gg53ujOJ4qX6EKIK9LSBfFed/xiQ5yeXNrUA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -3665,6 +3704,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -4196,6 +4238,24 @@ packages: zod@3.25.67: resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} + zustand@5.0.6: + resolution: {integrity: sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==} + 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 + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -5573,6 +5633,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/history@4.7.11': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -5602,6 +5664,17 @@ snapshots: dependencies: '@types/react': 19.1.8 + '@types/react-router-dom@5.3.3': + dependencies: + '@types/history': 4.7.11 + '@types/react': 19.1.8 + '@types/react-router': 5.1.20 + + '@types/react-router@5.1.20': + dependencies: + '@types/history': 4.7.11 + '@types/react': 19.1.8 + '@types/react@19.1.8': dependencies: csstype: 3.1.3 @@ -6053,6 +6126,8 @@ snapshots: meow: 12.1.1 split2: 4.2.0 + cookie@1.0.2: {} + cosmiconfig-typescript-loader@6.1.0(@types/node@24.0.3)(cosmiconfig@9.0.0(typescript@5.7.3))(typescript@5.7.3): dependencies: '@types/node': 24.0.3 @@ -7937,6 +8012,20 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + react-router-dom@7.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-router: 7.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + + react-router@7.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + cookie: 1.0.2 + react: 19.1.0 + set-cookie-parser: 2.7.1 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.0): dependencies: get-nonce: 1.0.1 @@ -8127,6 +8216,8 @@ snapshots: semver@7.7.2: {} + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -8741,4 +8832,10 @@ snapshots: zod@3.25.67: {} + zustand@5.0.6(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)): + optionalDependencies: + '@types/react': 19.1.8 + react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) + zwitch@2.0.4: {} From fda119a6744161430a75eb10d74565a023b9dec4 Mon Sep 17 00:00:00 2001 From: Seono-Na Date: Mon, 28 Jul 2025 16:53:06 +0900 Subject: [PATCH 2/5] =?UTF-8?q?style:=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=20=ED=91=9C=EC=A4=80=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .env.local 파일의 환경변수 설정을 표준 형식으로 정리 - VITE_PROJECT_URL과 VITE_PUBLIC_ANON_KEY 변수의 공백 제거 - 문자열 값 주변의 따옴표 제거하여 일관성 개선 --- .env.local | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.local b/.env.local index 5bd7eea..ecdd6ae 100644 --- a/.env.local +++ b/.env.local @@ -1,2 +1,2 @@ -VITE_PROJECT_URL = 'https://rkrrreuxefnsnovornvcc.supabase.co' -VITE_PUBLIC_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJrcnJldXhlZm5zbm92b3JudmNjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI5MTM4NjYsImV4cCI6MjA2ODQ4OTg2Nn0.Y2-2gcusLoQ8STuUkV3V-1yvxpCzrY2Ur0vHwDZpDXc' \ No newline at end of file +VITE_PROJECT_URL=https://rkrreuxefnsnovornvcc.supabase.co +VITE_PUBLIC_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJrcnJldXhlZm5zbm92b3JudmNjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTI5MTM4NjYsImV4cCI6MjA2ODQ4OTg2Nn0.Y2-2gcusLoQ8STuUkV3V-1yvxpCzrY2Ur0vHwDZpDXc \ No newline at end of file From c492c14f5507953d6756f1a7979abe455345bf9e Mon Sep 17 00:00:00 2001 From: Seono-Na Date: Mon, 28 Jul 2025 16:59:53 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zustand 기반 인증 상태 관리 및 GitHub OAuth 연동 - 인증 관련 타입 정의 (AuthStatus, User, AuthState, AuthActions) - Zustand를 활용한 인증 스토어 구현 (게스트/GitHub 로그인, 로그아웃) - Supabase 세션 변경 감지를 위한 useAuth 훅 구현 - 브라우저 스토리지 persist 지원으로 인증 상태 유지 --- src/features/kanban/hooks/useAuth.ts | 41 +++++++++ src/features/kanban/types/auth.ts | 33 +++++++ src/stores/authStore.ts | 129 +++++++++++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 src/features/kanban/hooks/useAuth.ts create mode 100644 src/features/kanban/types/auth.ts create mode 100644 src/stores/authStore.ts diff --git a/src/features/kanban/hooks/useAuth.ts b/src/features/kanban/hooks/useAuth.ts new file mode 100644 index 0000000..2f20e8b --- /dev/null +++ b/src/features/kanban/hooks/useAuth.ts @@ -0,0 +1,41 @@ +// src/hooks/useAuth.ts +import { useEffect } from 'react'; + +import { supabase } from '@/shared/lib/supa-client'; +import { useAuthStore } from '@/stores/authStore'; + +export const useAuth = () => { + const authStore = useAuthStore(); + + useEffect(() => { + // 초기 인증 상태 확인 + authStore.initializeAuth(); + + // Supabase 세션 변경 감지 + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange(async (event, session) => { + if (event === 'SIGNED_IN' && session) { + authStore.setStatus('authenticated'); + authStore.setUser({ + id: session.user.id, + email: session.user.email!, + name: session.user.user_metadata?.full_name || session.user.email!, + avatar_url: session.user.user_metadata?.avatar_url, + github_username: session.user.user_metadata?.user_name, + }); + authStore.setSession(session); + } else if (event === 'SIGNED_OUT') { + if (authStore.status !== 'guest') { + authStore.setStatus('unauthenticated'); + authStore.setUser(null); + authStore.setSession(null); + } + } + }); + + return () => subscription.unsubscribe(); + }, []); + + return authStore; +}; diff --git a/src/features/kanban/types/auth.ts b/src/features/kanban/types/auth.ts new file mode 100644 index 0000000..065745f --- /dev/null +++ b/src/features/kanban/types/auth.ts @@ -0,0 +1,33 @@ +export type AuthStatus = + | 'loading' + | 'guest' + | 'authenticated' + | 'unauthenticated'; + +export interface User { + id: string; + email: string; + name: string; + avatar_url?: string; + github_username?: string; +} + +export interface AuthState { + status: AuthStatus; + user: User | null; + session: any; // Supabase Session 타입 + isLoading: boolean; +} + +export interface AuthActions { + setStatus: (status: AuthStatus) => void; + setUser: (user: User | null) => void; + setSession: (session: any) => void; + loginAsGuest: () => void; + loginWithGitHub: () => Promise; + logout: () => Promise; + checkAuthStatus: () => Promise; + initializeAuth: () => Promise; +} + +export type AuthStore = AuthState & AuthActions; diff --git a/src/stores/authStore.ts b/src/stores/authStore.ts new file mode 100644 index 0000000..cd3a95d --- /dev/null +++ b/src/stores/authStore.ts @@ -0,0 +1,129 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +import type { AuthActions, AuthState } from '@/features/kanban/types/auth'; +import { supabase } from '@/shared/lib/supa-client'; + +export const useAuthStore = create()( + persist( + (set, get) => ({ + // State + status: 'loading', + user: null, + session: null, + isLoading: false, + + // Actions + setStatus: (status) => set({ status }), + + setUser: (user) => set({ user }), + + setSession: (session) => set({ session }), + + loginAsGuest: () => { + set({ + status: 'guest', + user: { + id: 'guest', + email: 'guest@example.com', + name: 'Guest User', + }, + session: null, + }); + }, + + loginWithGitHub: async () => { + try { + set({ isLoading: true }); + + const { data, error } = await supabase.auth.signInWithOAuth({ + provider: 'github', + options: { + redirectTo: `${window.location.origin}/auth/callback`, + scopes: 'read:user user:email read:org', + }, + }); + + if (error) throw error; + + // OAuth 리디렉션이 발생하므로 여기서는 상태 변경하지 않음 + } catch (error) { + console.error('GitHub login failed:', error); + set({ isLoading: false, status: 'unauthenticated' }); + } + }, + + logout: async () => { + try { + set({ isLoading: true }); + + if (get().status === 'guest') { + // 게스트 로그아웃 + set({ + status: 'unauthenticated', + user: null, + session: null, + isLoading: false, + }); + } else { + // GitHub 로그아웃 + await supabase.auth.signOut(); + set({ + status: 'unauthenticated', + user: null, + session: null, + isLoading: false, + }); + } + } catch (error) { + console.error('Logout failed:', error); + set({ isLoading: false }); + } + }, + + checkAuthStatus: async () => { + try { + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (session) { + set({ + status: 'authenticated', + user: { + id: session.user.id, + email: session.user.email!, + name: + session.user.user_metadata?.full_name || session.user.email!, + avatar_url: session.user.user_metadata?.avatar_url, + github_username: session.user.user_metadata?.user_name, + }, + session, + }); + } else { + const currentStatus = get().status; + if (currentStatus !== 'guest') { + set({ status: 'unauthenticated' }); + } + } + } catch (error) { + console.error('Auth check failed:', error); + set({ status: 'unauthenticated' }); + } + }, + + initializeAuth: async () => { + set({ isLoading: true }); + await get().checkAuthStatus(); + set({ isLoading: false }); + }, + }), + { + name: 'auth-storage', + partialize: (state) => ({ + status: state.status, + user: state.user, + }), + } + ) +); From f5e0a3910c63279d665a637b9b15719b024d5b07 Mon Sep 17 00:00:00 2001 From: Seono-Na Date: Mon, 28 Jul 2025 17:05:31 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 인증 시스템용 사용자 인터페이스 추가 - GitHubLoginButton: GitHub OAuth 로그인 버튼 (로딩 상태 포함) - GuestLoginButton: 게스트 로그인 버튼 (라우팅 연동) - LoginPage: 전체 로그인 페이지 레이아웃 및 로딩 처리 --- .../components/auth/GitHubLoginButton.tsx | 28 +++++++++++ .../components/auth/GuestLoginButton.tsx | 29 ++++++++++++ .../kanban/components/auth/LoginPage.tsx | 47 +++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 src/features/kanban/components/auth/GitHubLoginButton.tsx create mode 100644 src/features/kanban/components/auth/GuestLoginButton.tsx create mode 100644 src/features/kanban/components/auth/LoginPage.tsx diff --git a/src/features/kanban/components/auth/GitHubLoginButton.tsx b/src/features/kanban/components/auth/GitHubLoginButton.tsx new file mode 100644 index 0000000..841b31f --- /dev/null +++ b/src/features/kanban/components/auth/GitHubLoginButton.tsx @@ -0,0 +1,28 @@ +import { useAuthStore } from '@/stores/authStore'; + +export const GitHubLoginButton = () => { + const { loginWithGitHub, isLoading } = useAuthStore(); + + return ( + + ); +}; diff --git a/src/features/kanban/components/auth/GuestLoginButton.tsx b/src/features/kanban/components/auth/GuestLoginButton.tsx new file mode 100644 index 0000000..6b2f953 --- /dev/null +++ b/src/features/kanban/components/auth/GuestLoginButton.tsx @@ -0,0 +1,29 @@ +import { useNavigate } from 'react-router-dom'; + +import { useAuthStore } from '@/stores/authStore'; + +export const GuestLoginButton = () => { + const navigate = useNavigate(); + const { loginAsGuest } = useAuthStore(); + + const handleGuestLogin = () => { + loginAsGuest(); + navigate('/'); + }; + + return ( + + ); +}; diff --git a/src/features/kanban/components/auth/LoginPage.tsx b/src/features/kanban/components/auth/LoginPage.tsx new file mode 100644 index 0000000..3816aad --- /dev/null +++ b/src/features/kanban/components/auth/LoginPage.tsx @@ -0,0 +1,47 @@ +import { useAuth } from '../../hooks/useAuth'; +import { GitHubLoginButton } from './GitHubLoginButton'; +import { GuestLoginButton } from './GuestLoginButton'; + +export const LoginPage = () => { + const { isLoading } = useAuth(); + + if (isLoading) { + return ( +
+
+
+ ); + } + + return ( +
+
+

+ 칸반 로그인 +

+

+ 게스트로 둘러보거나 GitHub 계정으로 로그인하세요 +

+
+ +
+
+
+ + +
+
+
+
+
+ 또는 +
+
+ + +
+
+
+
+ ); +}; From 5ecd5dfa720be55087f00b90169accc058ca7b81 Mon Sep 17 00:00:00 2001 From: Seono-Na Date: Tue, 29 Jul 2025 22:34:11 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20boolean=20helper=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/kanban/types/auth.ts | 7 +++++++ src/stores/authStore.ts | 23 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/features/kanban/types/auth.ts b/src/features/kanban/types/auth.ts index 065745f..4f75cd9 100644 --- a/src/features/kanban/types/auth.ts +++ b/src/features/kanban/types/auth.ts @@ -17,6 +17,13 @@ export interface AuthState { user: User | null; session: any; // Supabase Session 타입 isLoading: boolean; + + // Computed properties (getters) - boolean 헬퍼들 + readonly isGuest: boolean; + readonly isAuthenticated: boolean; + readonly isGitHubUser: boolean; + readonly isUnauthenticated: boolean; + readonly isLoadingAuth: boolean; } export interface AuthActions { diff --git a/src/stores/authStore.ts b/src/stores/authStore.ts index cd3a95d..2b8dd07 100644 --- a/src/stores/authStore.ts +++ b/src/stores/authStore.ts @@ -13,6 +13,27 @@ export const useAuthStore = create()( session: null, isLoading: false, + // Computed properties (getters) - boolean 헬퍼들 + get isGuest() { + return get().status === 'guest'; + }, + + get isAuthenticated() { + return get().status === 'authenticated'; + }, + + get isGitHubUser() { + return get().status === 'authenticated' && get().session !== null; + }, + + get isUnauthenticated() { + return get().status === 'unauthenticated'; + }, + + get isLoadingAuth() { + return get().status === 'loading' || get().isLoading; + }, + // Actions setStatus: (status) => set({ status }), @@ -57,7 +78,7 @@ export const useAuthStore = create()( try { set({ isLoading: true }); - if (get().status === 'guest') { + if (get().isGuest) { // 게스트 로그아웃 set({ status: 'unauthenticated',