From 70134b49672506a824336571b897e69480163a17 Mon Sep 17 00:00:00 2001
From: Randall Stillwell
Date: Fri, 17 Apr 2026 09:56:06 -0500
Subject: [PATCH] Polish login and signup UX: fix stale copy, add password
toggles, strength meter, invite context, terms footer
Made-with: Cursor
---
src/app/(auth)/login/page.tsx | 16 ++++--
src/app/(auth)/signup/page.tsx | 101 +++++++++++++++++++++++++++++----
2 files changed, 100 insertions(+), 17 deletions(-)
diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx
index c532932..7bc104d 100644
--- a/src/app/(auth)/login/page.tsx
+++ b/src/app/(auth)/login/page.tsx
@@ -2,7 +2,7 @@
import { Suspense, useState } from "react";
import { signIn } from "next-auth/react";
-import { useRouter, useSearchParams } from "next/navigation";
+import { useSearchParams } from "next/navigation";
import Link from "next/link";
import { ScanLine, Mail, Lock, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
@@ -19,7 +19,6 @@ export default function LoginPage() {
}
function LoginForm() {
- const router = useRouter();
const searchParams = useSearchParams();
const callbackUrl = searchParams.get("callbackUrl") || "/";
const error = searchParams.get("error");
@@ -47,8 +46,7 @@ function LoginForm() {
setFormError("Invalid email or password");
setLoading(false);
} else {
- router.push(callbackUrl);
- router.refresh();
+ window.location.href = callbackUrl;
}
}
@@ -60,7 +58,7 @@ function LoginForm() {
Welcome back
- Sign in to your Echo account
+ Digitize your connect cards
@@ -139,9 +137,15 @@ function LoginForm() {
Don't have an account?{" "}
- Sign up with an invite
+ Sign up
+
+ By signing in, you agree to our{" "}
+ Terms
+ {" "}and{" "}
+ Privacy Policy.
+
);
}
diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx
index 3dc1445..93f8f14 100644
--- a/src/app/(auth)/signup/page.tsx
+++ b/src/app/(auth)/signup/page.tsx
@@ -1,10 +1,10 @@
"use client";
-import { Suspense, useState } from "react";
+import { Suspense, useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import { signIn } from "next-auth/react";
import Link from "next/link";
-import { ScanLine, Mail, Lock, User, Loader2 } from "lucide-react";
+import { ScanLine, Mail, Lock, User, Loader2, Eye, EyeOff } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -17,9 +17,23 @@ export default function SignupPage() {
);
}
+function getPasswordStrength(pw: string): { level: number; label: string } {
+ if (!pw || pw.length < 8) return { level: 0, label: "Weak" };
+ const hasUpper = /[A-Z]/.test(pw);
+ const hasLower = /[a-z]/.test(pw);
+ const hasNumber = /\d/.test(pw);
+ if (pw.length >= 12 && hasUpper && hasLower && hasNumber) {
+ return { level: 2, label: "Strong" };
+ }
+ return { level: 1, label: "Fair" };
+}
+
+const strengthColors = ["bg-red-500", "bg-amber-500", "bg-emerald-500"];
+
function SignupForm() {
const searchParams = useSearchParams();
const token = searchParams.get("token") || "";
+ const callbackUrl = searchParams.get("callbackUrl") || "/";
const [formData, setFormData] = useState({
displayName: "",
@@ -29,6 +43,26 @@ function SignupForm() {
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirm, setShowConfirm] = useState(false);
+ const [inviteInfo, setInviteInfo] = useState<{
+ orgName: string;
+ email: string;
+ role: string;
+ } | null>(null);
+
+ useEffect(() => {
+ if (!token) return;
+ fetch(`/api/invitations/verify?token=${token}`)
+ .then((r) => r.json())
+ .then((data) => {
+ if (data.valid) {
+ setInviteInfo({ orgName: data.orgName, email: data.email, role: data.role });
+ setFormData((prev) => ({ ...prev, email: data.email }));
+ }
+ })
+ .catch(() => {});
+ }, [token]);
function update(field: string, value: string) {
setFormData((prev) => ({ ...prev, [field]: value }));
@@ -72,7 +106,7 @@ function SignupForm() {
await signIn("credentials", {
email: formData.email,
password: formData.password,
- callbackUrl: "/",
+ callbackUrl,
});
} catch {
setError("Something went wrong. Please try again.");
@@ -80,6 +114,9 @@ function SignupForm() {
}
}
+ const strength = getPasswordStrength(formData.password);
+ const showStrength = formData.password.length > 0;
+
return (
@@ -88,9 +125,11 @@ function SignupForm() {
Create account
- {token
- ? "Complete your account setup"
- : "Get started with Echo"}
+ {inviteInfo
+ ? <>Join {inviteInfo.orgName}>
+ : token
+ ? "Complete your account setup"
+ : "Get started with Echo"}
@@ -129,7 +168,8 @@ function SignupForm() {
onChange={(e) => update("email", e.target.value)}
required
autoComplete="email"
- className="pl-10"
+ readOnly={!!inviteInfo}
+ className={`pl-10 ${inviteInfo ? "bg-muted/50 cursor-not-allowed" : ""}`}
/>
@@ -140,16 +180,40 @@ function SignupForm() {
update("password", e.target.value)}
required
minLength={8}
autoComplete="new-password"
- className="pl-10"
+ className="pl-10 pr-10"
/>
+
+ {showStrength && (
+
+
+ {[0, 1, 2].map((i) => (
+
+ ))}
+
+
{strength.label}
+
+ )}
@@ -158,15 +222,24 @@ function SignupForm() {
update("confirmPassword", e.target.value)}
required
minLength={8}
autoComplete="new-password"
- className="pl-10"
+ className="pl-10 pr-10"
/>
+
@@ -182,6 +255,12 @@ function SignupForm() {
Sign in
+
+ By creating an account, you agree to our{" "}
+ Terms
+ {" "}and{" "}
+ Privacy Policy.
+
);
}