import {create} from 'zustand'; import {clearTokens, getAccessToken, setTokens, UserRole} from '@/api'; import type {ResMe} from '@/api'; export interface AuthUser { userId: string; id: string; name?: string; email?: string; role: number; companyId?: string; companyName?: string; } /** * `GET /v1/auth/me` 응답 → 화면이 쓰는 사용자. * * ★ 생성 모델의 필드가 전부 optional 인 것이 이상해 보이지만 맞다 — 백엔드가 기본값을 준 * 필드(`user_id: str = ""`)는 OpenAPI 에서 required 가 아니고, 응답은 `RemoveNoneResponse` * 를 지나며 None 필드가 키째 사라진다. 그래서 없을 수 있다고 보고 여기 한 곳에서만 메운다. */ export function toAuthUser(res: ResMe): AuthUser { return { userId: res.user_id ?? '', id: res.id ?? '', name: res.name ?? undefined, email: res.email ?? undefined, role: res.role ?? UserRole.USER, companyId: res.company?.company_id, companyName: res.company?.name, }; } /** * 화면에 띄울 이름. 구글 계정의 로그인 아이디는 `google_` 라 그대로 보이면 안 된다 — * 이름 → 이메일 순으로 떨어뜨리고 아이디는 마지막이다. */ export function userLabel(user: AuthUser): string { return user.name || user.email || user.id; } interface AuthState { user: AuthUser | null; /** 부팅 시 저장된 토큰을 확인하기 전까지 true. 가드가 이 동안 리다이렉트를 미룬다. */ isRestoring: boolean; isAuthenticated: () => boolean; signIn: (tokens: {accessToken: string; refreshToken?: string}, user: AuthUser) => void; signOut: () => void; setUser: (user: AuthUser | null) => void; finishRestore: () => void; } export const useAuthStore = create((set, get) => ({ user: null, isRestoring: true, // 토큰 보관소는 custom-fetch 가 유일한 소스다. 스토어가 사본을 들지 않는다 — // 두 곳에 두면 refresh 로 갱신됐을 때 한쪽만 낡는다. isAuthenticated: () => Boolean(getAccessToken()) && Boolean(get().user), signIn: (tokens, user) => { setTokens(tokens.accessToken, tokens.refreshToken ?? null); set({user, isRestoring: false}); }, signOut: () => { clearTokens(); set({user: null, isRestoring: false}); }, setUser: (user) => set({user}), finishRestore: () => set({isRestoring: false}), }));