aaa
This commit is contained in:
185
src/app/(auth)/auth-form.tsx
Normal file
185
src/app/(auth)/auth-form.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Icons } from "@/components/icons";
|
||||
import type { FormState } from "./auth-actions";
|
||||
|
||||
type Action = (prev: FormState, data: FormData) => Promise<FormState>;
|
||||
|
||||
export function AuthForm({
|
||||
mode,
|
||||
action,
|
||||
next = "/",
|
||||
}: {
|
||||
mode: "login" | "register";
|
||||
action: Action;
|
||||
next?: string;
|
||||
}) {
|
||||
const [state, formAction, pending] = useActionState<FormState, FormData>(
|
||||
action,
|
||||
{},
|
||||
);
|
||||
|
||||
const isLogin = mode === "login";
|
||||
const title = isLogin ? "Đăng nhập" : "Tạo tài khoản";
|
||||
const subtitle = isLogin
|
||||
? "Tiếp tục với địa điểm đã lưu của bạn"
|
||||
: "Bắt đầu lưu địa điểm cùng nhóm nhỏ";
|
||||
const cta = isLogin ? "Đăng nhập" : "Đăng ký";
|
||||
const switchPrompt = isLogin ? "Chưa có tài khoản?" : "Đã có tài khoản?";
|
||||
const switchBase = isLogin ? "/register" : "/login";
|
||||
const switchHref =
|
||||
next && next !== "/"
|
||||
? `${switchBase}?next=${encodeURIComponent(next)}`
|
||||
: switchBase;
|
||||
const switchLabel = isLogin ? "Đăng ký" : "Đăng nhập";
|
||||
|
||||
return (
|
||||
<form
|
||||
action={formAction}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 14,
|
||||
padding: 24,
|
||||
background: "var(--card)",
|
||||
border: "0.5px solid var(--border)",
|
||||
borderRadius: "var(--radius-xl)",
|
||||
boxShadow: "var(--shadow-md)",
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="next" value={next} />
|
||||
<div style={{ marginBottom: 4 }}>
|
||||
<h1
|
||||
className="display"
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: 28,
|
||||
fontWeight: 700,
|
||||
letterSpacing: "-0.02em",
|
||||
lineHeight: 1.1,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
<p
|
||||
style={{
|
||||
margin: "4px 0 0",
|
||||
fontSize: 14,
|
||||
color: "var(--muted-foreground)",
|
||||
}}
|
||||
>
|
||||
{subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!isLogin && (
|
||||
<Field label="Tên hiển thị">
|
||||
<Icons.User size={18} stroke={1.75} style={{ color: "var(--muted-foreground)" }} />
|
||||
<input
|
||||
name="name"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="name"
|
||||
placeholder="Tên của bạn"
|
||||
disabled={pending}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
<Field label="Email">
|
||||
<Icons.Mail size={18} stroke={1.75} style={{ color: "var(--muted-foreground)" }} />
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
autoComplete="email"
|
||||
inputMode="email"
|
||||
placeholder="ten@email.com"
|
||||
disabled={pending}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Mật khẩu">
|
||||
<Icons.Lock size={18} stroke={1.75} style={{ color: "var(--muted-foreground)" }} />
|
||||
<input
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
minLength={8}
|
||||
autoComplete={isLogin ? "current-password" : "new-password"}
|
||||
placeholder={isLogin ? "••••••••" : "Tối thiểu 8 ký tự"}
|
||||
disabled={pending}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{state.error && (
|
||||
<div
|
||||
role="alert"
|
||||
style={{
|
||||
padding: "10px 12px",
|
||||
background: "var(--danger-soft)",
|
||||
color: "var(--danger)",
|
||||
borderRadius: "var(--radius-md)",
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{state.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn--block btn--lg"
|
||||
disabled={pending}
|
||||
style={{ marginTop: 4 }}
|
||||
>
|
||||
{pending ? "Đang xử lý..." : cta}
|
||||
</button>
|
||||
|
||||
<div
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: "var(--muted-foreground)",
|
||||
textAlign: "center",
|
||||
paddingTop: 4,
|
||||
}}
|
||||
>
|
||||
{switchPrompt}{" "}
|
||||
<Link
|
||||
href={switchHref}
|
||||
style={{ color: "var(--primary)", fontWeight: 600, textDecoration: "none" }}
|
||||
>
|
||||
{switchLabel}
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function Field({
|
||||
label,
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<label style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
color: "var(--muted-foreground)",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.04em",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<span className="input">{children}</span>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user