- shared/auth 모듈: AuthProvider, ProtectedRoute, useAuth, authApi - 페이지: LoginPage(Google OAuth), PendingPage, DeniedPage - WING_PERMIT 역할 기반 접근 제어 - Topbar에 사용자 이름 + 로그아웃 버튼 추가 - App.tsx에 react-router 라우팅 + AuthProvider 래핑 - DEV 모드 Mock 로그인 지원 (김개발) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
35 lines
1009 B
TypeScript
35 lines
1009 B
TypeScript
const API_BASE = (import.meta.env.VITE_AUTH_API_URL || '').replace(/\/$/, '') + '/api';
|
|
|
|
async function request<T>(path: string, options?: RequestInit): Promise<T> {
|
|
const token = localStorage.getItem('token');
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
};
|
|
|
|
const res = await fetch(`${API_BASE}${path}`, { ...options, headers });
|
|
|
|
if (res.status === 401) {
|
|
localStorage.removeItem('token');
|
|
window.location.href = '/login';
|
|
throw new Error('Unauthorized');
|
|
}
|
|
|
|
if (!res.ok) {
|
|
const body = await res.text();
|
|
throw new Error(body || `HTTP ${res.status}`);
|
|
}
|
|
|
|
if (res.status === 204 || res.headers.get('content-length') === '0') {
|
|
return undefined as T;
|
|
}
|
|
|
|
return res.json();
|
|
}
|
|
|
|
export const authApi = {
|
|
get: <T>(path: string) => request<T>(path),
|
|
post: <T>(path: string, body?: unknown) =>
|
|
request<T>(path, { method: 'POST', body: JSON.stringify(body) }),
|
|
};
|