2035f73e7d
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.5 KiB
TypeScript
96 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { Loader2 } from "lucide-react";
|
|
import { useRouter } from "next/navigation";
|
|
import { useState } from "react";
|
|
import { Controller, useForm } from "react-hook-form";
|
|
import { PasswordInput } from "@/components/password-input";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Field, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
|
|
import { Input } from "@/components/ui/input";
|
|
import { tr } from "@/i18n/tr";
|
|
import { authClient } from "@/lib/auth-client";
|
|
import { looksLikeEmail, normalizeUsername } from "@/shared/schemas/common";
|
|
import { loginSchema, type LoginInput } from "@/shared/schemas/auth";
|
|
|
|
export function LoginForm() {
|
|
const router = useRouter();
|
|
const [formError, setFormError] = useState<string | null>(null);
|
|
const form = useForm<LoginInput>({
|
|
resolver: zodResolver(loginSchema),
|
|
defaultValues: { identifier: "", password: "" },
|
|
});
|
|
|
|
async function onSubmit({ identifier, password }: LoginInput) {
|
|
setFormError(null);
|
|
const value = identifier.trim();
|
|
const { error } = looksLikeEmail(value)
|
|
? await authClient.signIn.email({ email: value.toLowerCase(), password })
|
|
: await authClient.signIn.username({ username: normalizeUsername(value), password });
|
|
|
|
if (error) {
|
|
// Hangi bilginin yanlış olduğu söylenmez (hesap tahmini zorlaşır).
|
|
if (error.status === 429) setFormError(tr.auth.tooManyAttempts);
|
|
else if (error.code === "BANNED_USER") setFormError(tr.auth.banned);
|
|
else setFormError(tr.auth.invalidCredentials);
|
|
form.setValue("password", "");
|
|
return;
|
|
}
|
|
router.replace("/dashboard");
|
|
router.refresh();
|
|
}
|
|
|
|
const submitting = form.formState.isSubmitting;
|
|
|
|
return (
|
|
<form onSubmit={form.handleSubmit(onSubmit)} noValidate aria-busy={submitting}>
|
|
<FieldGroup>
|
|
<Controller
|
|
name="identifier"
|
|
control={form.control}
|
|
render={({ field, fieldState }) => (
|
|
<Field data-invalid={fieldState.invalid}>
|
|
<FieldLabel htmlFor="identifier">{tr.auth.identifier}</FieldLabel>
|
|
<Input
|
|
{...field}
|
|
id="identifier"
|
|
autoComplete="username"
|
|
autoCapitalize="none"
|
|
autoCorrect="off"
|
|
spellCheck={false}
|
|
inputMode="email"
|
|
placeholder={tr.auth.identifierPlaceholder}
|
|
aria-invalid={fieldState.invalid}
|
|
autoFocus
|
|
/>
|
|
{fieldState.invalid ? <FieldError errors={[fieldState.error]} /> : null}
|
|
</Field>
|
|
)}
|
|
/>
|
|
<Controller
|
|
name="password"
|
|
control={form.control}
|
|
render={({ field, fieldState }) => (
|
|
<Field data-invalid={fieldState.invalid}>
|
|
<FieldLabel htmlFor="password">{tr.auth.password}</FieldLabel>
|
|
<PasswordInput
|
|
{...field}
|
|
id="password"
|
|
autoComplete="current-password"
|
|
aria-invalid={fieldState.invalid}
|
|
/>
|
|
{fieldState.invalid ? <FieldError errors={[fieldState.error]} /> : null}
|
|
</Field>
|
|
)}
|
|
/>
|
|
{formError ? <FieldError>{formError}</FieldError> : null}
|
|
<Button type="submit" size="lg" disabled={submitting} className="w-full">
|
|
{submitting ? <Loader2 className="animate-spin" /> : null}
|
|
{submitting ? tr.auth.submitting : tr.auth.submit}
|
|
</Button>
|
|
</FieldGroup>
|
|
</form>
|
|
);
|
|
}
|