/** * OAuth callback page -- completes auth after API redirect. * URL: /auth/callback?success=true (redirected from API after PDS OAuth) * On success: refreshes session via cookie, stores token in memory, redirects to returnTo or /. * On failure: shows error with retry link. * @see specs/prd-web.md Section M3 (Auth Flow) */ 'use client' import { Suspense, useEffect, useMemo, useState, useRef } from 'react' import { useSearchParams } from 'next/navigation' import Link from 'next/link' import { refreshSession } from '@/lib/api/client' import { useAuth } from '@/hooks/use-auth' function CallbackContent() { const searchParams = useSearchParams() const { setSessionFromCallback } = useAuth() const processedRef = useRef(false) const success = searchParams.get('success') const errorParam = searchParams.get('error') // Derive missing-params error synchronously (not in effect) const missingParamsError = useMemo(() => { if (errorParam) return errorParam if (success) return null return 'Missing authorization parameters' }, [errorParam, success]) const [error, setError] = useState(missingParamsError) useEffect(() => { if (missingParamsError || processedRef.current) return processedRef.current = true async function processCallback() { try { // API already set the HTTP-only refresh cookie during redirect. // Call refresh to get the access token from the cookie. const session = await refreshSession() setSessionFromCallback(session) const returnTo = sessionStorage.getItem('auth_returnTo') ?? '/' sessionStorage.removeItem('auth_returnTo') window.location.href = returnTo } catch (err) { setError(err instanceof Error ? err.message : 'Authentication failed') } } void processCallback() }, [missingParamsError, setSessionFromCallback]) if (error) { return (

Login failed

{error}

Try again
) } return (

Completing login...

) } export default function AuthCallbackPage() { return (

Completing login...

} >
) }