mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
chore(release): v1.4.27
v1.4.27 — Mobile capability and maintainer-finding cleanup. ResponsiveSheet primitive (bottom-sheet on narrow viewports, centred dialog on desktop) applied to BD, Weight, Glucose, Mood, Activity forms. Maintainer-finding cleanups across Coach, Insights, Reports, Privacy. GeoLite2 offline geocoding with resilience layer (graceful fallback when EULA not yet propagated). E2E gap-fill coverage. 450-line release notes attached to the GitHub Release.
This commit is contained in:
1 parent
d98e85cd03
commit
4f09c3f862
314 files changed
+26801
-4419
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 () => {
|
||||
@@ -118,7 +124,11 @@ export default function LoginPage() {
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-4">
|
||||
<div className="border-border bg-card rounded-xl border p-8 shadow-lg shadow-black/20">
|
||||
{/* v1.4.27 MB7 / CF-61 — drop `p-8` to `p-6 sm:p-8` so the
|
||||
auth card breathes on Galaxy Fold / Pixel 5 without stealing
|
||||
half the visible viewport with padding. Tablet+ keeps the
|
||||
generous 32 px padding. */}
|
||||
<div className="border-border bg-card rounded-xl border p-6 shadow-lg shadow-black/20 sm:p-8">
|
||||
<div className="flex flex-col items-center gap-3 text-center">
|
||||
<div className="bg-primary/10 flex h-12 w-12 items-center justify-center rounded-lg">
|
||||
<Logo className="text-primary" size={28} />
|
||||
@@ -140,7 +150,7 @@ export default function LoginPage() {
|
||||
disabled={loading}
|
||||
>
|
||||
{loading && mode === "passkey" ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
|
||||
) : (
|
||||
<KeyRound className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
@@ -176,6 +186,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 +205,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>
|
||||
@@ -198,7 +219,7 @@ export default function LoginPage() {
|
||||
disabled={loading}
|
||||
>
|
||||
{loading && mode === "password" ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
|
||||
) : (
|
||||
<Lock className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
@@ -207,7 +228,7 @@ export default function LoginPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMode("passkey")}
|
||||
className="text-muted-foreground hover:text-foreground w-full text-center text-xs"
|
||||
className="text-muted-foreground hover:text-foreground inline-flex min-h-11 w-full items-center justify-center text-center text-xs"
|
||||
>
|
||||
{t("auth.backToPasskey")}
|
||||
</button>
|
||||
@@ -216,7 +237,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();
|
||||
@@ -59,7 +65,11 @@ export default function RegisterPage() {
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="border-border bg-card rounded-xl border p-8 shadow-lg shadow-black/20">
|
||||
{/* v1.4.27 MB7 / CF-61 — drop `p-8` to `p-6 sm:p-8` so the
|
||||
auth card breathes on Galaxy Fold / Pixel 5 without stealing
|
||||
half the visible viewport with padding. Tablet+ keeps the
|
||||
generous 32 px padding. */}
|
||||
<div className="border-border bg-card rounded-xl border p-6 shadow-lg shadow-black/20 sm:p-8">
|
||||
<div className="flex flex-col items-center gap-3 text-center">
|
||||
<div className="bg-primary/10 flex h-12 w-12 items-center justify-center rounded-lg">
|
||||
<Logo className="text-primary" size={28} />
|
||||
@@ -85,6 +95,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 +114,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 +135,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,16 +148,27 @@ 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}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{/* v1.4.27 MB6 — lift the submit to the 44 px tap-target
|
||||
floor and the `lg` size so the primary action stays
|
||||
reachable on a narrow viewport without falling under the
|
||||
iOS keyboard's accessory bar. */}
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
className="min-h-11 w-full"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin motion-reduce:animate-none" />
|
||||
) : (
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user