"use client" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { Loader2 } from "lucide-react" import { AuthCard } from "@/components/auth/auth-card" import { AuthConnectButton } from "@/components/auth/auth-connect-button" import { FlowChallengeForm } from "@/components/auth/flow-challenge-form" import { AUTH_FLOW_SLUGS, isAuthenticationFlow, type AuthFlowSlug, } from "@/lib/auth/auth-flow-slugs" import { completeAuthFlow, flowComponent, flowRedirectUrl, flowTitle, flowValidationErrors, isRecoveryEmailSent, respondAuthFlow, startAuthFlow, type FlowChallenge, } from "@/lib/auth/flow-api" type AuthFlowPageProps = { slug: AuthFlowSlug flowQuery?: string defaultTitle: string defaultDescription: string successTitle: string successDescription: string successActionLabel: string successHref: string /** Full document navigation required (OIDC login routes). */ successExternal?: boolean /** After embedded auth flow, bridge to OIDC session via BFF /flows/complete. */ bridgeAuthentication?: boolean returnTo?: string initialError?: string | null footer: React.ReactNode onSuccess?: () => void } function decodeAuthError(value: string | null | undefined): string | null { if (!value) return null try { return decodeURIComponent(value) } catch { return value } } export function AuthFlowPage({ slug, flowQuery, defaultTitle, defaultDescription, successTitle, successDescription, successActionLabel, successHref, successExternal = false, bridgeAuthentication = false, returnTo = "/mail/inbox", initialError = null, footer, onSuccess, }: AuthFlowPageProps) { const [sessionId, setSessionId] = useState(null) const [challenge, setChallenge] = useState(null) const [loading, setLoading] = useState(true) const [submitting, setSubmitting] = useState(false) const [bridging, setBridging] = useState(false) const [flowError, setFlowError] = useState(null) const [done, setDone] = useState(false) const [denied, setDenied] = useState(false) const bridgedRef = useRef(false) const redirectError = decodeAuthError(initialError) const finishAuthentication = useCallback(async () => { if (!bridgeAuthentication || !isAuthenticationFlow(slug) || bridgedRef.current) { onSuccess?.() return } bridgedRef.current = true setBridging(true) setFlowError(null) try { const { redirectUrl } = await completeAuthFlow(returnTo) window.location.href = redirectUrl } catch (err) { bridgedRef.current = false setBridging(false) setFlowError( err instanceof Error ? err.message : "Impossible de finaliser la connexion" ) } }, [bridgeAuthentication, onSuccess, returnTo, slug]) const redirectToOidcLogin = useCallback(() => { const params = new URLSearchParams({ returnTo }) window.location.assign(`/api/auth/login?${params.toString()}`) }, [returnTo]) const bootstrap = useCallback(async () => { setLoading(true) try { const step = await startAuthFlow(slug, flowQuery) setSessionId(step.sessionId) setChallenge(step.challenge) setDone(step.done) setDenied(step.denied) if (step.done && !step.denied) { if (bridgeAuthentication) { // Start returned immediate redirect — no flow session cookie for /complete. redirectToOidcLogin() return } onSuccess?.() } } catch (err) { setFlowError( err instanceof Error ? err.message : "Impossible de démarrer le parcours" ) } finally { setLoading(false) } }, [bridgeAuthentication, flowQuery, onSuccess, redirectToOidcLogin, slug]) useEffect(() => { void bootstrap() }, [bootstrap]) const handleSubmit = useCallback( async (payload: Record) => { if (!sessionId) return setSubmitting(true) setFlowError(null) try { const step = await respondAuthFlow(slug, payload, flowQuery) setSessionId(step.sessionId) setChallenge(step.challenge) setDone(step.done) setDenied(step.denied) if (step.done && !step.denied) { await finishAuthentication() } } catch (err) { setFlowError(err instanceof Error ? err.message : "Échec de l'étape") } finally { setSubmitting(false) } }, [finishAuthentication, flowQuery, sessionId, slug] ) const validationErrors = useMemo(() => flowValidationErrors(challenge), [challenge]) const formError = flowError ?? redirectError ?? validationErrors._form ?? null const title = useMemo(() => { if ((done && !denied) || isRecoveryEmailSent(slug, challenge)) return successTitle if (denied) return "Accès refusé" return flowTitle(challenge) || defaultTitle }, [challenge, defaultTitle, denied, done, slug, successTitle]) const description = useMemo(() => { if ((done && !denied) || isRecoveryEmailSent(slug, challenge)) return successDescription if (denied) { return "Ce parcours a été refusé. Vérifiez vos informations ou contactez le support." } return defaultDescription }, [challenge, defaultDescription, denied, done, slug, successDescription]) const component = flowComponent(challenge) const redirectUrl = flowRedirectUrl(challenge) const recoveryEmailSent = isRecoveryEmailSent(slug, challenge) const showSuccess = (done && !denied && !bridgeAuthentication) || recoveryEmailSent return ( {loading || bridging ? (
{bridging ? "Finalisation…" : "Chargement…"}
) : showSuccess ? (
{redirectUrl ? (

Redirection en cours…

) : null} {successExternal ? ( {successActionLabel} ) : ( {successActionLabel} )}
) : ( )}
) } export { AUTH_FLOW_SLUGS }