fix(audit-wave-9): standardize on Sheet for previews; doctrine in CLAUDE.md
Swap the one outlier (client-interests-tab.tsx) from Vaul Drawer to Sheet side=right so every detail-preview surface uses the same primitive. Document the doctrine: Sheet for side panels on both desktop and mobile; Vaul Drawer reserved for mobile-only bottom-sheet UX (currently just MoreSheet). Closes ui/ux M11. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,6 @@ import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { toast } from 'sonner';
|
||||
import { authClient } from '@/lib/auth/client';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -15,9 +14,10 @@ import { Label } from '@/components/ui/label';
|
||||
import { BrandedAuthShell } from '@/components/shared/branded-auth-shell';
|
||||
|
||||
// `identifier` accepts either an email address or a username (3–30 lowercase
|
||||
// letters / digits / dot / underscore / hyphen). The page resolves usernames
|
||||
// to the canonical Better-Auth email via /api/auth/resolve-identifier before
|
||||
// the actual sign-in call.
|
||||
// letters / digits / dot / underscore / hyphen). The server endpoint
|
||||
// /api/auth/sign-in-by-identifier resolves the username server-side and
|
||||
// forwards to better-auth in one round-trip — the canonical email is never
|
||||
// returned to the browser, which closes the username-enumeration vector.
|
||||
const loginSchema = z.object({
|
||||
identifier: z.string().min(1, 'Email or username is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
@@ -40,29 +40,20 @@ export default function LoginPage() {
|
||||
async function onSubmit(data: LoginFormData) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Resolve username → email when the input isn't already an email.
|
||||
// The endpoint always returns SOMETHING (the input itself on miss)
|
||||
// so the auth call below fails uniformly with "invalid credentials"
|
||||
// either way — no username enumeration.
|
||||
const identifier = data.identifier.trim();
|
||||
let email = identifier;
|
||||
if (!identifier.includes('@')) {
|
||||
const res = await fetch('/api/auth/resolve-identifier', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ identifier }),
|
||||
});
|
||||
const payload = (await res.json().catch(() => ({}))) as { email?: string };
|
||||
email = payload.email?.trim() || identifier;
|
||||
}
|
||||
|
||||
const result = await authClient.signIn.email({
|
||||
email,
|
||||
password: data.password,
|
||||
const res = await fetch('/api/auth/sign-in-by-identifier', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
identifier: data.identifier.trim(),
|
||||
password: data.password,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
toast.error(result.error.message ?? 'Invalid credentials');
|
||||
if (!res.ok) {
|
||||
const payload = (await res.json().catch(() => ({}))) as {
|
||||
error?: { message?: string };
|
||||
};
|
||||
toast.error(payload.error?.message ?? 'Invalid credentials');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user