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')}

+
+
+ ); +}