"use client" import { useEffect, useMemo, useRef, useState } from "react" import { Loader2 } from "lucide-react" import { AuthConnectButton } from "@/components/auth/auth-connect-button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import type { FlowChallenge } from "@/lib/auth/flow-api" type PromptField = { field_key: string label?: string type?: string required?: boolean placeholder?: string initial_value?: string choices?: Array<{ label?: string; value?: string }> } type FlowChallengeFormProps = { challenge: FlowChallenge | null component: string submitting: boolean fieldErrors?: Record onSubmit: (payload: Record) => void | Promise } export function FlowChallengeForm({ challenge, component, submitting, fieldErrors = {}, onSubmit, }: FlowChallengeFormProps) { const [values, setValues] = useState>({}) const initializedComponentRef = useRef(null) const promptFields = useMemo(() => readPromptFields(challenge), [challenge]) const primaryAction = readPrimaryAction(challenge) useEffect(() => { if (initializedComponentRef.current === component) return initializedComponentRef.current = component const next: Record = {} if (component === "ak-stage-prompt") { for (const field of promptFields) { if (field.type === "hidden" || field.type === "static") continue next[field.field_key] = field.initial_value ?? "" } } if (component === "ak-stage-identification") { next.uid_field = "" if (readPasswordFields(challenge)) { next.password = "" } } if (component === "ak-stage-email") { next.email = "" } setValues(next) }, [challenge, component, promptFields]) async function handleSubmit(event: React.FormEvent) { event.preventDefault() const payload: Record = { component } if (component === "ak-stage-prompt") { for (const field of promptFields) { const key = field.field_key if (field.type === "static") continue if (field.type === "hidden") { payload[key] = field.initial_value ?? "" continue } payload[key] = values[key] ?? "" } } else if (component === "ak-stage-identification") { payload.uid_field = values.uid_field ?? "" if (readPasswordFields(challenge)) { payload.password = values.password ?? "" } } else if (component === "ak-stage-email") { payload.email = values.email ?? "" } else if (component === "ak-stage-authenticator-validate") { payload.code = values.code ?? "" } else { Object.assign(payload, values) } await onSubmit(payload) } if (!component || component === "ak-stage-access-denied") { return null } return (
{component === "ak-stage-prompt" ? ( setValues((prev) => ({ ...prev, [key]: value })) } /> ) : null} {component === "ak-stage-identification" ? ( setValues((prev) => ({ ...prev, [key]: value })) } /> ) : null} {component === "ak-stage-email" ? ( setValues((prev) => ({ ...prev, email: value }))} required /> ) : null} {component === "ak-stage-authenticator-validate" ? ( setValues((prev) => ({ ...prev, code: value }))} required /> ) : null} {!isKnownComponent(component) ? (

Étape non prise en charge ({component}). Utilisez le portail Authentik.

) : null} {isKnownComponent(component) ? ( {submitting ? ( <> Patientez… ) : ( primaryAction )} ) : null} ) } function PromptFields({ fields, values, fieldErrors, onChange, }: { fields: PromptField[] values: Record fieldErrors: Record onChange: (key: string, value: string) => void }) { return (
{fields.map((field) => { if (field.type === "hidden") return null if (field.type === "static") { return (

{field.label} {field.initial_value ? ( {" "} {field.initial_value} ) : null}

) } const inputType = field.type === "password" ? "password" : field.type === "email" ? "email" : "text" if (field.type === "file") { return (
{ const file = event.target.files?.[0] if (!file) { onChange(field.field_key, "") return } const reader = new FileReader() reader.onload = () => { onChange(field.field_key, String(reader.result ?? "")) } reader.readAsDataURL(file) }} />
) } return ( onChange(field.field_key, value)} required={field.required} autoComplete={autoCompleteFor(field.field_key, field.type)} /> ) })}
) } function IdentificationFields({ challenge, values, fieldErrors, onChange, }: { challenge: FlowChallenge | null values: Record fieldErrors: Record onChange: (key: string, value: string) => void }) { const withPassword = readPasswordFields(challenge) const userFields = readUserFields(challenge) const label = userFields.includes("email") && !userFields.includes("username") ? "Adresse e-mail" : "Identifiant" return (
onChange("uid_field", value)} required /> {withPassword ? ( onChange("password", value)} required /> ) : null}
) } function FieldBlock({ id, label, type = "text", placeholder, value, error, onChange, required, autoComplete, inputMode, }: { id: string label: string type?: string placeholder?: string value: string error?: string onChange: (value: string) => void required?: boolean autoComplete?: string inputMode?: React.HTMLAttributes["inputMode"] }) { return (
onChange(event.target.value)} className="h-10 rounded-lg" /> {error ? (

{error}

) : null}
) } function readPromptFields(challenge: FlowChallenge | null): PromptField[] { const raw = challenge?.fields if (!Array.isArray(raw)) return [] return raw.filter(isPromptField) } function isPromptField(value: unknown): value is PromptField { return ( typeof value === "object" && value !== null && typeof (value as PromptField).field_key === "string" ) } function readPrimaryAction(challenge: FlowChallenge | null): string { const action = challenge?.primary_action return typeof action === "string" && action.trim() ? action : "Continuer" } function readPasswordFields(challenge: FlowChallenge | null): boolean { return challenge?.password_fields === true } function readUserFields(challenge: FlowChallenge | null): string[] { const raw = challenge?.user_fields if (!Array.isArray(raw)) return ["username"] return raw.filter((v): v is string => typeof v === "string") } function autoCompleteFor(fieldKey: string, type?: string): string | undefined { if (type === "password") { return fieldKey.includes("repeat") ? "new-password" : "new-password" } if (fieldKey === "username") return "username" if (fieldKey === "email" || fieldKey.includes("email")) return "email" if (fieldKey === "name") return "name" if (fieldKey.includes("phone")) return "tel" return undefined } function isKnownComponent(component: string): boolean { return [ "ak-stage-prompt", "ak-stage-identification", "ak-stage-email", "ak-stage-authenticator-validate", ].includes(component) }