mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
feat(auth): wire keyboard hints and aria error wiring on login and register
Bring the auth flows to parity with the rest of the v1.4.27 form sweep so screen-reader users hear the form-level error banner tied to the offending field and the mobile keyboard surfaces the right on-screen affordance per step. Login: email field flips to `inputMode="email"` + `enterKeyHint="next"` and disables capitalize / spell-check; password field carries `enterKeyHint="go"`. Both inputs reference the error banner via `aria-describedby` and the banner itself gains `aria-live="polite"`. Register: same pattern across email, username, and the new-password field. The submit key on password is `"go"` to fire the create- account submit on virtual-keyboard return.
This commit is contained in:
1 parent
7398761412
commit
95bc87f5ab
2 files changed
+45
-2
No files matched your search
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useId, useState } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { KeyRound, Lock, Loader2 } from "lucide-react";
|
||||
@@ -23,6 +23,12 @@ export default function LoginPage() {
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
// v1.4.27 MB3 — explicit error-region id so the email + password
|
||||
// inputs reference the banner via `aria-describedby`. Screen readers
|
||||
// pair the error with the field instead of announcing it as a
|
||||
// standalone alert detached from the form.
|
||||
const errorId = useId();
|
||||
const errorDescriptor = error ? errorId : undefined;
|
||||
const { data: registrationEnabled } = useQuery({
|
||||
queryKey: ["auth", "registration-status"],
|
||||
queryFn: async () => {
|
||||
@@ -176,6 +182,13 @@ export default function LoginPage() {
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
autoComplete="username"
|
||||
inputMode="email"
|
||||
enterKeyHint="next"
|
||||
autoCapitalize="none"
|
||||
spellCheck={false}
|
||||
aria-required="true"
|
||||
aria-invalid={!!error || undefined}
|
||||
aria-describedby={errorDescriptor}
|
||||
placeholder={t("auth.emailOrUsernamePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
@@ -188,6 +201,10 @@ export default function LoginPage() {
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="current-password"
|
||||
enterKeyHint="go"
|
||||
aria-required="true"
|
||||
aria-invalid={!!error || undefined}
|
||||
aria-describedby={errorDescriptor}
|
||||
placeholder="********"
|
||||
/>
|
||||
</div>
|
||||
@@ -216,7 +233,9 @@ export default function LoginPage() {
|
||||
|
||||
{error && (
|
||||
<div
|
||||
id={errorId}
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
className="bg-destructive/10 text-destructive rounded-lg p-3 text-sm"
|
||||
>
|
||||
{error}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useId, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Loader2, UserPlus } from "lucide-react";
|
||||
@@ -22,6 +22,12 @@ export default function RegisterPage() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { t } = useTranslations();
|
||||
// v1.4.27 MB3 — explicit error-region id wired to every required
|
||||
// input via `aria-describedby` so screen readers pair the validation
|
||||
// failure with the offending field instead of announcing it as a
|
||||
// standalone alert detached from the form.
|
||||
const errorId = useId();
|
||||
const errorDescriptor = error ? errorId : undefined;
|
||||
|
||||
async function handleRegister(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
@@ -85,6 +91,12 @@ export default function RegisterPage() {
|
||||
placeholder={t("auth.emailPlaceholder")}
|
||||
required
|
||||
autoComplete="email"
|
||||
enterKeyHint="next"
|
||||
autoCapitalize="none"
|
||||
spellCheck={false}
|
||||
aria-required="true"
|
||||
aria-invalid={!!error || undefined}
|
||||
aria-describedby={errorDescriptor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -98,6 +110,12 @@ export default function RegisterPage() {
|
||||
placeholder="user"
|
||||
required
|
||||
autoComplete="username"
|
||||
enterKeyHint="next"
|
||||
autoCapitalize="none"
|
||||
spellCheck={false}
|
||||
aria-required="true"
|
||||
aria-invalid={!!error || undefined}
|
||||
aria-describedby={errorDescriptor}
|
||||
minLength={3}
|
||||
maxLength={30}
|
||||
/>
|
||||
@@ -113,6 +131,10 @@ export default function RegisterPage() {
|
||||
placeholder={t("auth.passwordMinLength")}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
enterKeyHint="go"
|
||||
aria-required="true"
|
||||
aria-invalid={!!error || undefined}
|
||||
aria-describedby={errorDescriptor}
|
||||
/>
|
||||
<PasswordStrength password={password} />
|
||||
<p className="text-muted-foreground text-xs">
|
||||
@@ -122,7 +144,9 @@ export default function RegisterPage() {
|
||||
|
||||
{error && (
|
||||
<div
|
||||
id={errorId}
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
className="bg-destructive/10 text-destructive rounded-lg p-3 text-sm"
|
||||
>
|
||||
{error}
|
||||
|
||||
Reference in new issue
Block a user