Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
"dependencies": {
"@headlessui/react": "^2.2.1",
"@heroicons/react": "^2.2.0",
"@types/js-cookie": "^3.0.6",
"chart.js": "^4.4.8",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.6.3",
"js-cookie": "^3.0.5",
"lucide-react": "^0.487.0",
"next": "15.2.4",
"react": "^19.0.0",
Expand Down
48 changes: 44 additions & 4 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import { useState, useEffect } from 'react';
import { useTheme } from '@/contexts/ThemeContext';
import { UserCircleIcon, KeyIcon, ArrowLeftIcon } from '@heroicons/react/24/outline';
import { useRouter } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import { useAuthStore, LoginRequest } from '@/lib/authStore';
import Link from 'next/link';

export default function LoginPage() {
const { currentTheme } = useTheme();
const router = useRouter();
const searchParams = useSearchParams();

// 로그인 폼 데이터
const [loginData, setLoginData] = useState<LoginRequest>({
Expand All @@ -22,15 +23,42 @@ export default function LoginPage() {
isLoading,
error,
isAuthenticated,
login
login,
checkAuth
} = useAuthStore();

// silent 모드 확인 (내부 처리용)
const isSilentMode = searchParams.get('silent') === 'true';
const callbackUrl = searchParams.get('callbackUrl');

// 페이지 로드 시 인증 상태 확인
useEffect(() => {
// silent 모드일 경우 자동으로 인증 상태 확인
if (isSilentMode) {
const isAuthed = checkAuth();

if (isAuthed) {
// 이미 인증된 경우 callbackUrl로 리다이렉트
if (callbackUrl) {
router.push(decodeURIComponent(callbackUrl));
} else {
router.push('/');
}
}
}
}, [isSilentMode, callbackUrl, router, checkAuth]);

// 로그인 후 리다이렉션
useEffect(() => {
if (isAuthenticated) {
router.push('/');
// callbackUrl 파라미터 확인
if (callbackUrl) {
router.push(decodeURIComponent(callbackUrl));
} else {
router.push('/');
}
}
}, [isAuthenticated, router]);
}, [isAuthenticated, router, callbackUrl]);

// 입력 필드 변경 처리
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -52,6 +80,18 @@ export default function LoginPage() {
await login(loginData);
};

// silent 모드일 경우 로딩 화면 표시
if (isSilentMode) {
return (
<div className={`min-h-screen ${currentTheme.background} flex items-center justify-center`}>
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className={`text-lg ${currentTheme.text}`}>인증 확인 중...</p>
</div>
</div>
);
}

return (
<div className={`min-h-screen ${currentTheme.background} p-8`}>
<div className="max-w-md mx-auto">
Expand Down
19 changes: 11 additions & 8 deletions src/app/monitoring/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { useEffect, useState, useRef } from 'react';
import { useRouter } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import { useAuthStore } from '@/lib/authStore';
import { useTheme } from '@/contexts/ThemeContext';
import {
Expand Down Expand Up @@ -192,6 +192,7 @@ function VehicleSidebar({

export default function MonitoringPage() {
const router = useRouter();
const searchParams = useSearchParams();
const { isAuthenticated } = useAuthStore();
const { currentTheme } = useTheme();
const [wsConnected, setWsConnected] = useState(false);
Expand Down Expand Up @@ -481,14 +482,16 @@ export default function MonitoringPage() {
connectWebSocket();
};

useEffect(() => {
if (!isAuthenticated) {
router.push('/login');
}
}, [isAuthenticated, router]);

// 인증되지 않은 경우 로딩 화면 표시
if (!isAuthenticated) {
return null;
return (
<div className={`${currentTheme.background} min-h-screen flex items-center justify-center`}>
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className={`text-lg ${currentTheme.text}`}>인증 확인 중...</p>
</div>
</div>
);
}

return (
Expand Down
13 changes: 12 additions & 1 deletion src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,20 @@ export function middleware(request: NextRequest) {
const needsAuth = PROTECTED_PATHS.some(path => pathname === path || pathname.startsWith(`${path}/`));
if (needsAuth && !isAuthenticated) {
console.log(`[Middleware] 인증되지 않은 사용자가 보호된 경로 접근: ${pathname} -> /login으로 리다이렉트`);

// 로그인 페이지로 리다이렉트할 때 특별한 쿼리 파라미터 추가
const url = new URL('/login', request.url);
url.searchParams.set('callbackUrl', encodeURI(pathname));
return NextResponse.redirect(url);
url.searchParams.set('silent', 'true'); // 내부 처리용 플래그

// 리다이렉트 응답 생성
const response = NextResponse.redirect(url);

// 응답 헤더에 인증 상태 정보 추가
response.headers.set('x-authenticated', 'false');
response.headers.set('x-redirect', 'true');

return response;
}

// 인증된 사용자가 로그인/회원가입 페이지에 접근하면 대시보드로 리다이렉트
Expand Down