'use client'; import { Suspense, useEffect, useState, useRef } from 'react'; import { useSearchParams } from 'next/navigation'; import { useRouter } from '@/core/lib/navigation'; import { useAuth } from '@/core/providers/store-provider'; import { LoadingSpinner } from '@/ui/shared/loading-spinner'; import { useTranslations } from '@/core/lib/translations'; /** * `oauth_error` is a stable snake_case code, not a message — the backend sends * the RFC 6749 §4.1.2.1 pair (`oauth_error` + an English `error_description` * meant for developers). Map codes to translated copy here; anything unknown, * including provider-specific codes, falls through to the generic message. */ const OAUTH_ERROR_MESSAGE_KEYS: Record = { access_denied: 'oauthErrorCancelled', invalid_request: 'oauthErrorExpired', invalid_state: 'oauthErrorExpired', state_already_used: 'oauthErrorExpired', state_expired: 'oauthErrorExpired', link_blocked_unverified_password_account: 'oauthErrorVerifyEmailFirst', }; function OAuthCallbackContent() { const router = useRouter(); const searchParams = useSearchParams(); const auth = useAuth(); const [error, setError] = useState(null); const processedRef = useRef(false); const t = useTranslations('auth'); const oauthSuccess = searchParams.get('oauth_success'); const oauthError = searchParams.get('oauth_error'); // Token is no longer in URL — it was set as httpOnly cookie by /api/auth/oauth-callback useEffect(() => { // Prevent double-processing in React StrictMode if (processedRef.current) return; processedRef.current = true; if (oauthError) { setError(t(OAUTH_ERROR_MESSAGE_KEYS[oauthError] ?? 'oauthErrorGeneric')); return; } if (oauthSuccess === 'true') { // Cookie was already set by the API route; refresh auth state auth.login().then(() => { router.push('/'); }); } else { setError(t('authFailedDesc')); } }, [oauthSuccess, oauthError, auth, router, t]); if (error) { return (

{t('authFailed')}

{error}

); } return (

{t('completingSignIn')}

); } export default function OAuthCallbackPage() { return ( } > ); }