Redesign login, register, me pages with premium layout and i18n
Deploy / release (push) Skipped
Deploy / deploy (push) Successful in 2m7s

This commit is contained in:
openhands committed 2026-07-22 14:42:01 +02:00
1 parent 063ac9ec40
commit 1ffd46713a
7 files changed
+621 -279

No files matched your search

+122 -101
View File
@@ -10,7 +10,6 @@ import {
type CaptchaPublicConfig,
readCaptchaToken,
} from "@/components/auth/captcha-widget";
import { ContentCard } from "@/components/public/ui";
export function LoginForm({
captcha = { provider: "none" },
@@ -64,117 +63,139 @@ export function LoginForm({
);
return;
}
window.location.href = "/";
window.location.href = "/me";
} finally {
setPending(false);
}
}
return (
<main style={{ maxWidth: 420, margin: "2rem auto" }}>
<ContentCard
icon={needs2fa ? "🔒" : "🔑"}
title={t("title")}
subtitle={needs2fa ? t("subtitle2fa") : t("subtitle")}
>
<form onSubmit={onSubmit} style={{ display: "grid", gap: "0.7rem" }}>
<>
{needs2fa && (
<p className="text-sm font-semibold mb-4" style={{ color: "var(--color-text-muted)" }}>
{t("subtitle2fa")}
</p>
)}
<form onSubmit={onSubmit} className="flex flex-col gap-4">
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder={t("usernamePlaceholder")}
autoComplete="username"
disabled={needs2fa}
className="w-full rounded-xl border-2 px-4 py-3 text-sm font-medium transition-all focus:outline-none"
style={{
backgroundColor: "var(--color-background)",
color: "var(--color-text-readable, var(--color-text))",
borderColor: needs2fa
? "color-mix(in srgb, var(--color-text-muted) 15%, transparent)"
: "color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
}}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={t("passwordPlaceholder")}
autoComplete="current-password"
disabled={needs2fa}
className="w-full rounded-xl border-2 px-4 py-3 text-sm font-medium transition-all focus:outline-none"
style={{
backgroundColor: "var(--color-background)",
color: "var(--color-text-readable, var(--color-text))",
borderColor: needs2fa
? "color-mix(in srgb, var(--color-text-muted) 15%, transparent)"
: "color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
}}
/>
{needs2fa ? (
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder={t("usernamePlaceholder")}
autoComplete="username"
disabled={needs2fa}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={t("passwordPlaceholder")}
autoComplete="current-password"
disabled={needs2fa}
/>
{needs2fa ? (
<input
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder={t("codePlaceholder")}
inputMode="numeric"
autoComplete="one-time-code"
// biome-ignore lint/a11y/noAutofocus: focus the only relevant field in the 2FA step
autoFocus
/>
) : (
<CaptchaWidget captcha={captcha} nonce={nonce} />
)}
<button type="submit" className="btn btn-primary" disabled={pending}>
{pending ? t("pleaseWait") : needs2fa ? t("verify") : t("signIn")}
</button>
</form>
{!needs2fa ? (
<>
<div
style={{
display: "flex",
alignItems: "center",
gap: "0.5rem",
margin: "1rem 0",
}}
>
<span
style={{
flex: 1,
height: 1,
background: "var(--border-color)",
}}
/>
<span className="muted">{t("or")}</span>
<span
style={{
flex: 1,
height: 1,
background: "var(--border-color)",
}}
/>
</div>
<div style={{ display: "grid", gap: "0.5rem" }}>
<button
type="button"
className="btn btn-outline"
onClick={() => signIn("discord", { callbackUrl: "/" })}
>
{t("continueWithDiscord")}
</button>
<button
type="button"
className="btn btn-outline"
onClick={() => signIn("google", { callbackUrl: "/" })}
>
{t("continueWithGoogle")}
</button>
</div>
</>
) : null}
{error ? (
<p
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder={t("codePlaceholder")}
inputMode="numeric"
autoComplete="one-time-code"
autoFocus
className="w-full rounded-xl border-2 px-4 py-3 text-sm font-medium transition-all focus:outline-none"
style={{
color: "var(--color-danger)",
textAlign: "center",
marginBottom: 0,
backgroundColor: "var(--color-background)",
color: "var(--color-text-readable, var(--color-text))",
borderColor: "color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
}}
>
/>
) : (
<CaptchaWidget captcha={captcha} nonce={nonce} />
)}
{error && (
<p className="text-sm text-center font-semibold" style={{ color: "var(--color-danger)" }}>
{error}
</p>
) : null}
<p
className="muted"
style={{ textAlign: "center", marginBottom: 0, marginTop: "1rem" }}
)}
<button
type="submit"
disabled={pending}
className="w-full rounded-xl font-extrabold text-sm py-3.5 transition-all duration-200 hover:scale-[1.02] active:scale-95 shadow-lg disabled:opacity-60"
style={{
background: "var(--color-primary)",
color: "var(--color-primary-foreground)",
boxShadow: "0 4px 24px color-mix(in srgb, var(--color-primary) 35%, transparent)",
}}
>
{t("noAccount")} <Link href="/register">{t("createOne")}</Link> ·{" "}
<Link href="/forgot">{t("forgotPassword")}</Link>
</p>
</ContentCard>
</main>
{pending ? t("pleaseWait") : needs2fa ? t("verify") : t("signIn")}
</button>
</form>
{!needs2fa && (
<>
<div className="flex items-center gap-3 my-5">
<span className="flex-1 h-px" style={{ background: "color-mix(in srgb, var(--color-text-muted) 15%, transparent)" }} />
<span className="text-xs font-bold uppercase tracking-widest" style={{ color: "var(--color-text-muted)" }}>
{t("or")}
</span>
<span className="flex-1 h-px" style={{ background: "color-mix(in srgb, var(--color-text-muted) 15%, transparent)" }} />
</div>
<div className="flex flex-col gap-3">
<button
type="button"
className="w-full rounded-xl border-2 font-bold text-sm py-3 transition-all hover:scale-[1.02] active:scale-95"
onClick={() => signIn("discord", { callbackUrl: "/" })}
style={{
borderColor: "color-mix(in srgb, var(--color-text-muted) 20%, transparent)",
color: "var(--color-text-readable)",
}}
>
{t("continueWithDiscord")}
</button>
<button
type="button"
className="w-full rounded-xl border-2 font-bold text-sm py-3 transition-all hover:scale-[1.02] active:scale-95"
onClick={() => signIn("google", { callbackUrl: "/" })}
style={{
borderColor: "color-mix(in srgb, var(--color-text-muted) 20%, transparent)",
color: "var(--color-text-readable)",
}}
>
{t("continueWithGoogle")}
</button>
</div>
</>
)}
<p
className="text-xs text-center mt-6"
style={{ color: "var(--color-text-muted)" }}
>
{t("noAccount")}{" "}
<Link href="/register" className="font-extrabold hover:underline" style={{ color: "var(--color-primary)" }}>
{t("createOne")}
</Link>{" "}
·{" "}
<Link href="/forgot" className="font-extrabold hover:underline" style={{ color: "var(--color-primary)" }}>
{t("forgotPassword")}
</Link>
</p>
</>
);
}