82 lines
2.5 KiB
TypeScript
82 lines
2.5 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
|
import { useAuth, OAUTH_POST_LOGIN_REDIRECT_KEY } from '../contexts/AuthContext';
|
|
import { useLanguage } from '../contexts/LanguageContext';
|
|
|
|
function toInternalPath(urlOrPath: string): string {
|
|
try {
|
|
const parsed = new URL(urlOrPath, window.location.origin);
|
|
if (parsed.origin !== window.location.origin) {
|
|
return '/';
|
|
}
|
|
|
|
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
} catch {
|
|
return '/';
|
|
}
|
|
}
|
|
|
|
export default function AuthCallbackPage() {
|
|
const navigate = useNavigate();
|
|
const [searchParams] = useSearchParams();
|
|
const { completeGoogleOAuth } = useAuth();
|
|
const { t } = useLanguage();
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const code = useMemo(() => searchParams.get('code') ?? '', [searchParams]);
|
|
const state = useMemo(() => searchParams.get('state') ?? '', [searchParams]);
|
|
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
|
|
const run = async () => {
|
|
if (!code || !state) {
|
|
if (mounted) {
|
|
setError(t.auth.oauthFailed);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const redirectUri = `${window.location.origin}/auth/google/callback`;
|
|
const result = await completeGoogleOAuth({ code, state, redirect_uri: redirectUri });
|
|
|
|
if (result.error) {
|
|
if (mounted) {
|
|
setError(result.error.message || t.auth.oauthFailed);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const redirectTarget = sessionStorage.getItem(OAUTH_POST_LOGIN_REDIRECT_KEY) || '/';
|
|
navigate(toInternalPath(redirectTarget), { replace: true });
|
|
};
|
|
|
|
run();
|
|
|
|
return () => {
|
|
mounted = false;
|
|
};
|
|
}, [code, completeGoogleOAuth, navigate, state, t.auth.oauthFailed]);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
|
|
<div className="bg-white rounded-xl shadow-xl w-full max-w-md p-6 text-center">
|
|
<h1 className="text-xl font-bold text-gray-900 mb-3">Google OAuth</h1>
|
|
{!error && <p className="text-gray-600">{t.auth.oauthExchanging}</p>}
|
|
{error && (
|
|
<>
|
|
<p className="text-red-600 text-sm mb-4">{error}</p>
|
|
<button
|
|
type="button"
|
|
onClick={() => navigate('/', { replace: true })}
|
|
className="px-4 py-2 bg-gray-900 text-white rounded-lg hover:bg-gray-800 transition-colors"
|
|
>
|
|
Back Home
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|