"use client" import { useEffect, useMemo, useState } from "react" import { Loader2 } from "lucide-react" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { PasswordFieldBlock } from "@/components/auth/password-field-block" import { checkMailAddressAvailability } from "@/lib/auth/check-mail-address" import { buildSignupEmail, normalizeSignupLocalPart, SIGNUP_MAIL_DOMAIN, } from "@/lib/auth/signup-platform" import { useDebouncedValue } from "@/lib/hooks/use-debounced-value" import { cn } from "@/lib/utils" type PromptField = { field_key: string label?: string type?: string required?: boolean placeholder?: string } type SignupCredentialsFieldsProps = { fields: PromptField[] values: Record fieldErrors: Record onChange: (key: string, value: string) => void onCanSubmitChange?: (canSubmit: boolean) => void } export function isSignupCredentialsStep(fields: PromptField[]): boolean { const keys = new Set(fields.map((field) => field.field_key)) return keys.has("username") && keys.has("password") && keys.has("password_repeat") } export function SignupCredentialsFields({ fields, values, fieldErrors, onChange, onCanSubmitChange, }: SignupCredentialsFieldsProps) { const username = values.username ?? "" const normalizedLocal = useMemo(() => normalizeSignupLocalPart(username), [username]) const previewEmail = buildSignupEmail(username) const debouncedLocal = useDebouncedValue(normalizedLocal, 1000) const [availability, setAvailability] = useState< "idle" | "checking" | "available" | "taken" | "error" >("idle") useEffect(() => { if (debouncedLocal.length < 2) { setAvailability("idle") return } let cancelled = false setAvailability("checking") void checkMailAddressAvailability(debouncedLocal, SIGNUP_MAIL_DOMAIN) .then((result) => { if (cancelled) return setAvailability(result.available ? "available" : "taken") }) .catch(() => { if (cancelled) return setAvailability("error") }) return () => { cancelled = true } }, [debouncedLocal]) const canSubmit = normalizedLocal.length >= 2 && availability !== "taken" && availability !== "checking" useEffect(() => { onCanSubmitChange?.(canSubmit) }, [canSubmit, onCanSubmitChange]) const otherFields = fields.filter( (field) => field.type !== "hidden" && field.type !== "static" && !["username", "password", "password_repeat", "domain_hint"].includes( field.field_key ) ) return (
field.field_key === "username")?.placeholder ?? "prenom.nom" } value={username} required aria-invalid={ fieldErrors.username || availability === "taken" ? true : undefined } onChange={(event) => onChange("username", normalizeSignupLocalPart(event.target.value)) } onKeyDown={(event) => { if (event.key === "@") event.preventDefault() }} onPaste={(event) => { const text = event.clipboardData.getData("text") if (!text.includes("@")) return event.preventDefault() onChange("username", normalizeSignupLocalPart(text)) }} className="h-10 rounded-lg pr-[9.5rem]" /> @{SIGNUP_MAIL_DOMAIN}

Votre adresse sera{" "} {previewEmail || `identifiant@${SIGNUP_MAIL_DOMAIN}`}

{fieldErrors.username ? (

{fieldErrors.username}

) : null}
onChange("password", value)} /> onChange("password_repeat", value)} /> {otherFields.map((field) => ( onChange(field.field_key, value)} /> ))}
) } function AvailabilityHint({ local, debouncedLocal, status, }: { local: string debouncedLocal: string status: "idle" | "checking" | "available" | "taken" | "error" }) { if (local.length === 0) return null if (local.length < 2) { return (

Au moins 2 caractères avant le @.

) } if (local !== debouncedLocal || status === "checking") { return (

Vérification de la disponibilité…

) } if (status === "available") { return (

Cette adresse est disponible.

) } if (status === "taken") { return (

Cette adresse est déjà prise.

) } if (status === "error") { return (

Impossible de vérifier la disponibilité pour le moment.

) } return null } function GenericSignupField({ field, value, error, onChange, }: { field: PromptField value: string error?: string onChange: (value: string) => void }) { if (field.type === "file") { return (
{ const file = event.target.files?.[0] if (!file) { onChange("") return } const reader = new FileReader() reader.onload = () => onChange(String(reader.result ?? "")) reader.readAsDataURL(file) }} />
) } return (
onChange(event.target.value)} className="h-10 rounded-lg" /> {error ? (

{error}

) : null}
) } function autoCompleteForField(fieldKey: string): string | undefined { if (fieldKey === "name") return "name" if (fieldKey.includes("phone")) return "tel" return undefined }