[feat]: 보호된 대시보드 및 API 라우트 추가

- 인증된 사용자용 대시보드 페이지 구현 ((protected) 라우트 그룹)
- API 엔드포인트 추가 (인증, 사용자 관리)
- 커스텀 훅 추가 (useAuth)
- 미들웨어 인증 로직 강화
- 환경변수 예제 업데이트
- 기존 dashboard 페이지 제거 후 보호된 라우트로 이동

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-11-10 09:38:59 +09:00
parent 56386e6d88
commit bf39fd22bd
14 changed files with 804 additions and 40 deletions

View File

@@ -27,7 +27,7 @@ export function LoginPage() {
const [rememberMe, setRememberMe] = useState(false);
const [error, setError] = useState("");
const handleLogin = () => {
const handleLogin = async () => {
setError("");
// Validation
@@ -36,35 +36,45 @@ export function LoginPage() {
return;
}
// Demo accounts
const demoAccounts = [
{ userId: "ceo", password: "demo1234", role: "CEO", name: "김대표", email: "ceo@demo.com" },
{ userId: "manager", password: "demo1234", role: "ProductionManager", name: "이생산", email: "manager@demo.com" },
{ userId: "worker", password: "demo1234", role: "Worker", name: "박작업", email: "worker@demo.com" },
{ userId: "admin", password: "demo1234", role: "SystemAdmin", name: "최시스템", email: "admin@demo.com" },
{ userId: "sales", password: "demo1234", role: "Sales", name: "박영업", email: "sales@demo.com" },
];
try {
// ✅ HttpOnly Cookie 방식: Next.js API Route로 프록시
// 토큰은 JavaScript에서 접근 불가능한 HttpOnly 쿠키로 저장됨
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: userId,
user_pwd: password,
}),
});
const account = demoAccounts.find(acc => acc.userId === userId && acc.password === password);
const data = await response.json();
if (account) {
// Save user data to localStorage (client-side only)
const userData = {
userId: account.userId,
email: account.email,
role: account.role,
name: account.name,
companyName: "데모 기업",
};
if (typeof window !== "undefined") {
localStorage.setItem("user", JSON.stringify(userData));
if (!response.ok) {
throw {
status: response.status,
message: data.error || 'Login failed',
};
}
// Navigate to dashboard
console.log('✅ 로그인 성공:', data.message);
console.log('📦 사용자 정보:', data.user);
console.log('🔐 토큰은 안전한 HttpOnly 쿠키에 저장됨 (JavaScript 접근 불가)');
// 대시보드로 이동
router.push("/dashboard");
} else {
setError(t('invalidCredentials'));
} catch (err: any) {
console.error('❌ 로그인 실패:', err);
if (err.status === 422) {
setError(t('invalidCredentials'));
} else if (err.status === 429) {
setError('너무 많은 로그인 시도가 있었습니다. 잠시 후 다시 시도해주세요.');
} else {
setError(err.message || t('invalidCredentials'));
}
}
};