diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx
index 009c22c..625b16b 100644
--- a/packages/web/src/App.tsx
+++ b/packages/web/src/App.tsx
@@ -5,6 +5,7 @@ import { ThemeProvider } from './lib/theme';
import { I18nProvider } from './lib/i18n';
import Login from './pages/Login';
import Register from './pages/Register';
+import LoginCallback from './pages/LoginCallback';
import Layout from './pages/Layout';
import Projects from './pages/Projects';
import ProjectDetail from './pages/ProjectDetail';
@@ -22,6 +23,7 @@ export default function App() {
} />
} />
} />
+ } />
}>
} />
} />
diff --git a/packages/web/src/pages/LoginCallback.tsx b/packages/web/src/pages/LoginCallback.tsx
new file mode 100644
index 0000000..348879c
--- /dev/null
+++ b/packages/web/src/pages/LoginCallback.tsx
@@ -0,0 +1,67 @@
+import { useEffect, useState } from 'react';
+import { useNavigate, useSearchParams, Link } from 'react-router-dom';
+import { useAuth } from '../lib/auth';
+import { useI18n } from '../lib/i18n';
+
+export default function LoginCallback() {
+ const [searchParams] = useSearchParams();
+ const [error, setError] = useState('');
+ const { loginWithTokens } = useAuth();
+ const navigate = useNavigate();
+ const { t } = useI18n();
+
+ useEffect(() => {
+ const accessToken = searchParams.get('accessToken');
+ const refreshToken = searchParams.get('refreshToken');
+ const errorParam = searchParams.get('error');
+
+ if (errorParam) {
+ setError(errorParam);
+ return;
+ }
+
+ if (!accessToken || !refreshToken) {
+ setError('Missing authentication tokens');
+ return;
+ }
+
+ // Clear tokens from URL immediately
+ window.history.replaceState({}, '', '/login/callback');
+
+ loginWithTokens(accessToken, refreshToken)
+ .then(() => navigate('/dashboard', { replace: true }))
+ .catch((err) => setError(err instanceof Error ? err.message : 'Authentication failed'));
+ }, []);
+
+ if (error) {
+ return (
+
+
+
+
{t('auth.callback.error')}
+
{error}
+
+ {t('auth.callback.retry')}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
{t('auth.callback.loading')}
+
+
+ );
+}