wing-ops/frontend/src/common/store/authStore.ts
Nan Kyung Lee c3d3b82b60 feat(scat): 해안평가 서브메뉴 3개 추가 (해안오염 조사 평가, 해양오염분포도, Pre-SCAT)
- useSubMenu에 scat 서브메뉴 설정 추가 (survey, distribution, pre-scat)
- ScatView wrapper 컴포넌트로 서브탭 분기 처리
- SurveyView, DistributionView placeholder 컴포넌트 생성
- hasPermission에 부모 리소스 fallback 로직 추가 (scat:survey → scat)
- App.tsx에서 PreScatView → ScatView 교체

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:27:53 +09:00

92 lines
2.8 KiB
TypeScript

import { create } from 'zustand'
import { loginApi, googleLoginApi, logoutApi, fetchMe, PendingApprovalError } from '../services/authApi'
import type { AuthUser } from '../services/authApi'
interface AuthState {
user: AuthUser | null
isAuthenticated: boolean
isLoading: boolean
error: string | null
pendingMessage: string | null
login: (account: string, password: string) => Promise<void>
googleLogin: (credential: string) => Promise<void>
logout: () => Promise<void>
checkSession: () => Promise<void>
hasPermission: (resource: string, operation?: string) => boolean
clearError: () => void
}
export const useAuthStore = create<AuthState>((set, get) => ({
user: null,
isAuthenticated: false,
isLoading: true,
error: null,
pendingMessage: null,
login: async (account: string, password: string) => {
set({ isLoading: true, error: null })
try {
const user = await loginApi(account, password)
set({ user, isAuthenticated: true, isLoading: false })
} catch (err) {
const message = (err as { message?: string })?.message || '로그인에 실패했습니다.'
set({ isLoading: false, error: message })
throw err
}
},
googleLogin: async (credential: string) => {
set({ isLoading: true, error: null, pendingMessage: null })
try {
const user = await googleLoginApi(credential)
set({ user, isAuthenticated: true, isLoading: false })
} catch (err) {
if (err instanceof PendingApprovalError) {
set({ isLoading: false, pendingMessage: err.message })
return
}
const message = (err as { message?: string })?.message || 'Google 로그인에 실패했습니다.'
set({ isLoading: false, error: message })
throw err
}
},
logout: async () => {
try {
await logoutApi()
} catch {
// 로그아웃 실패해도 클라이언트 상태는 초기화
}
set({ user: null, isAuthenticated: false, isLoading: false, error: null })
},
checkSession: async () => {
set({ isLoading: true })
try {
const user = await fetchMe()
set({ user, isAuthenticated: true, isLoading: false })
} catch {
set({ user: null, isAuthenticated: false, isLoading: false })
}
},
hasPermission: (resource: string, operation?: string) => {
const { user } = get()
if (!user) return false
const op = operation ?? 'READ'
// 정확한 리소스 권한 확인
const ops = user.permissions[resource]
if (ops) return ops.includes(op)
// 'scat:survey' → 부모 'scat' 권한으로 fallback
const colonIdx = resource.indexOf(':')
if (colonIdx > 0) {
const parent = resource.substring(0, colonIdx)
const parentOps = user.permissions[parent]
if (parentOps) return parentOps.includes(op)
}
return false
},
clearError: () => set({ error: null, pendingMessage: null }),
}))