diff --git a/web/frontend/app/features/auth/components/AuthErrorAlert.tsx b/web/frontend/app/features/auth/components/AuthErrorAlert.tsx
index 4892d919..98f2b77d 100644
--- a/web/frontend/app/features/auth/components/AuthErrorAlert.tsx
+++ b/web/frontend/app/features/auth/components/AuthErrorAlert.tsx
@@ -1,10 +1,20 @@
interface AuthErrorAlertProps {
message: string;
+ /** Per-field messages to list beneath the summary. */
+ details?: readonly string[];
}
-const AuthErrorAlert = ({ message }: AuthErrorAlertProps) => (
-
+const AuthErrorAlert = ({ message, details }: AuthErrorAlertProps) => (
+
{message}
+
+ {details?.length ? (
+
+ {details.map((detail) => (
+ - {detail}
+ ))}
+
+ ) : null}
);
diff --git a/web/frontend/app/features/auth/routes/register.tsx b/web/frontend/app/features/auth/routes/register.tsx
index fd6492b7..73cec5bd 100644
--- a/web/frontend/app/features/auth/routes/register.tsx
+++ b/web/frontend/app/features/auth/routes/register.tsx
@@ -51,6 +51,12 @@ const Register = ({ actionData }: Route.ComponentProps) => {
submit(data, { method: 'post' });
});
+ // A request that reached the action without passing client validation, so the
+ // rejected fields may well sit on a step other than the one on screen.
+ const rejectedFields = Object.values(actionData?.fieldErrors ?? {}).flatMap(
+ (messages) => messages ?? [],
+ );
+
useActionErrorToast(actionData?.error);
return (
@@ -68,6 +74,13 @@ const Register = ({ actionData }: Route.ComponentProps) => {
{actionData?.error &&
}
+ {rejectedFields.length > 0 && (
+
+ )}
+