'use client' import { useEffect, useState, Suspense, useRef, useCallback } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import { useAuth } from '@/lib/auth/context' import { AUTH_URL } from '@/lib/api/client' import { exchangeAuthCode, setSessionAction } from '@/app/actions/auth' import { authMessageFromErrorType, type AuthErrorType } from '@/lib/utils/authErrors' import { LoadingOverlay } from '@ciphera-net/ui' function AuthCallbackContent() { const router = useRouter() const searchParams = useSearchParams() const { login } = useAuth() const [error, setError] = useState(null) const [isRetrying, setIsRetrying] = useState(false) const processedRef = useRef(false) const runCodeExchange = useCallback(async () => { const code = searchParams.get('code') const codeVerifier = localStorage.getItem('oauth_code_verifier') const redirectUri = typeof window !== 'undefined' ? window.location.origin + '/auth/callback' : '' if (!code || !codeVerifier) return const result = await exchangeAuthCode(code, codeVerifier, redirectUri) if (result.success && result.user) { login(result.user) localStorage.removeItem('oauth_state') localStorage.removeItem('oauth_code_verifier') if (localStorage.getItem('pulse_pending_checkout')) { router.push('/welcome') } else { router.push('/') } } else { setError(authMessageFromErrorType(result.error as AuthErrorType)) } }, [searchParams, login, router]) useEffect(() => { // * Prevent double execution (React Strict Mode or fast re-renders) if (processedRef.current && !isRetrying) return // * Check for direct token passing (from auth-frontend direct login) // * This flow exposes tokens in URL, kept for legacy support. // * Recommended: Use Authorization Code flow (below) const token = searchParams.get('token') const refreshToken = searchParams.get('refresh_token') if (token && refreshToken) { processedRef.current = true const handleDirectTokens = async () => { const result = await setSessionAction(token, refreshToken) if (result.success && result.user) { login(result.user) const returnTo = searchParams.get('returnTo') || '/' router.push(returnTo) } else { setError(authMessageFromErrorType('invalid')) } } handleDirectTokens() return } const code = searchParams.get('code') const state = searchParams.get('state') if (!code || !state) return const storedState = localStorage.getItem('oauth_state') const codeVerifier = localStorage.getItem('oauth_code_verifier') if (!codeVerifier) { setError('Missing code verifier') return } if (state !== storedState) { console.error('State mismatch', { received: state, stored: storedState }) setError('Invalid state') return } processedRef.current = true if (isRetrying) setIsRetrying(false) runCodeExchange() }, [searchParams, login, router, isRetrying, runCodeExchange]) const handleRetry = () => { setError(null) setIsRetrying(true) } if (error) { const isNetworkError = error.includes('Network error') return (
{error}
{isNetworkError && ( )}
) } // * Use standard Pulse loading screen to make transition to Home seamless return } export default function AuthCallback() { return ( }> ) }