From 8d618213ada383ab1318ab200efb6fb7fbf638b7 Mon Sep 17 00:00:00 2001 From: Chad Nuttall Date: Sun, 4 Oct 2026 23:18:38 -0400 Subject: [PATCH 1/2] Public-site interaction polish (#280) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebased onto current main: the #282 primitives (loading buttons, Skeleton, motion tokens) are already on main, so this is only the public-site delta — navigation-free surfaces: - Login: per-path pending state so the pressed sign-in button shows its own progress while both stay mutually disabled; forgot-password gets a real pending state; mount placeholder uses Skeleton. - Registration + sighting forms: submit buttons use loading/ loadingText instead of ad-hoc text swapping. - FAQ: Spinner primitive, chevron rotates rather than icon-swapping, accordion buttons get hover/active/focus-visible feedback. - Contact/homepage: press feedback + focus rings on real links; static contact card drops its misleading hover shadow. - Adoptions/vet-services/consent: consistent fast transitions. No static content made pseudo-interactive; no business logic touched. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/app/login/page.tsx | 36 +++++++++++++------ .../animal-adoptions-page.tsx | 2 +- .../animal-registration-page.tsx | 9 +++-- .../consent/consent-settings-button.tsx | 2 +- src/components/contact/contact-page.tsx | 14 ++++---- src/components/faq/faq-page.tsx | 16 ++++----- src/components/homepage/animals-section.tsx | 2 +- src/components/homepage/services-section.tsx | 2 +- .../homepage/who-we-are-section.tsx | 2 +- src/components/lost-pets/sighting-form.tsx | 4 +-- .../veterinary-services-page.tsx | 2 +- 11 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 6bda595..16376a8 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -11,9 +11,16 @@ import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/ import { useRouter } from "next/navigation"; import { useToast } from "@/hooks/use-toast"; import { ThemeToggle } from "@/components/theme-toggle"; +import { Skeleton } from "@/components/ui/skeleton"; export default function LoginPage() { - const [loading, setLoading] = useState(false); + // Which sign-in path is in flight — the pressed button is the one that + // shows progress; the other just disables. (#280) + const [loadingKind, setLoadingKind] = useState<"email" | "google" | null>( + null, + ); + const loading = loadingKind !== null; + const [resetting, setResetting] = useState(false); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [mounted, setMounted] = useState(false); @@ -46,14 +53,14 @@ export default function LoginPage() { return (
-
+
); } const handleGoogleSignIn = async () => { - setLoading(true); + setLoadingKind("google"); try { const provider = new GoogleAuthProvider(); const result = await signInWithPopup(auth, provider); @@ -96,13 +103,13 @@ export default function LoginPage() { variant: "destructive", }); } finally { - setLoading(false); + setLoadingKind(null); } }; const handleEmailSignIn = async (e: React.FormEvent) => { e.preventDefault(); - setLoading(true); + setLoadingKind("email"); try { const result = await signInWithEmailAndPassword(auth, email, password); @@ -145,13 +152,13 @@ export default function LoginPage() { variant: "destructive", }); } finally { - setLoading(false); + setLoadingKind(null); } }; const handleEmailSignUp = async (e: React.FormEvent) => { e.preventDefault(); - setLoading(true); + setLoadingKind("email"); try { const result = await createUserWithEmailAndPassword(auth, email, password); @@ -178,7 +185,7 @@ export default function LoginPage() { variant: "destructive", }); } finally { - setLoading(false); + setLoadingKind(null); } }; @@ -200,6 +207,7 @@ export default function LoginPage() { return; } + setResetting(true); try { await sendPasswordResetEmail(auth, email); toast({ @@ -215,6 +223,8 @@ export default function LoginPage() { : "Failed to send password reset email. Please try again.", variant: "destructive", }); + } finally { + setResetting(false); } }; @@ -282,6 +292,8 @@ export default function LoginPage() { variant="link" size="sm" className="h-auto p-0 text-xs" + loading={resetting} + loadingText="Sending reset email…" onClick={handleForgotPassword} > Forgot your password? @@ -291,11 +303,13 @@ export default function LoginPage() { @@ -312,12 +326,14 @@ export default function LoginPage() {

diff --git a/src/components/animal-adoptions/animal-adoptions-page.tsx b/src/components/animal-adoptions/animal-adoptions-page.tsx index 683e7f4..1187757 100644 --- a/src/components/animal-adoptions/animal-adoptions-page.tsx +++ b/src/components/animal-adoptions/animal-adoptions-page.tsx @@ -155,7 +155,7 @@ export function AnimalAdoptions({ transition={reduceMotion ? instantTransition : { duration: 0.5, delay: index * 0.1 }} viewport={{ once: true }} > - +

{animal.name} diff --git a/src/components/animal-registration/animal-registration-page.tsx b/src/components/animal-registration/animal-registration-page.tsx index b31a5cb..8203ef8 100644 --- a/src/components/animal-registration/animal-registration-page.tsx +++ b/src/components/animal-registration/animal-registration-page.tsx @@ -536,8 +536,13 @@ export function AnimalRegistration({
- diff --git a/src/components/consent/consent-settings-button.tsx b/src/components/consent/consent-settings-button.tsx index b7bbdc9..52f3800 100644 --- a/src/components/consent/consent-settings-button.tsx +++ b/src/components/consent/consent-settings-button.tsx @@ -8,7 +8,7 @@ export function ConsentSettingsButton({ className }: { className?: string }) { return ( diff --git a/src/components/homepage/animals-section.tsx b/src/components/homepage/animals-section.tsx index e017053..c8dc7d2 100644 --- a/src/components/homepage/animals-section.tsx +++ b/src/components/homepage/animals-section.tsx @@ -69,7 +69,7 @@ export function AnimalsSection({ animals }: AnimalsSectionProps) { aria-label={`Learn more about ${animal.name}`} className="block h-full rounded-xl focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" > - + {animal.photos && animal.photos.length > 0 && ( - +
{iconMap[index] ||