Fix auth login flow, Iran Yekan/Doran fonts, and account tiles.
Prevent stale /auth/me from wiping a fresh session, unblock login submit, allow localhost HMR, and drop the tile "مشاهده" CTA. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
27d5cd0186
commit
68365457a1
@@ -1,12 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
FormEvent,
|
||||
InputHTMLAttributes,
|
||||
ReactNode,
|
||||
Suspense,
|
||||
useState,
|
||||
} from "react";
|
||||
import ManaLogo from "@/components/ui/ManaLogo";
|
||||
@@ -18,7 +17,6 @@ type LoginMethod = "password" | "otp";
|
||||
|
||||
function AuthFormInner() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const {
|
||||
login,
|
||||
sendLoginOtp,
|
||||
@@ -35,24 +33,28 @@ function AuthFormInner() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const isLogin = mode === "login";
|
||||
const customerHint = searchParams.get("as") === "customer";
|
||||
|
||||
function afterAuth(role: string) {
|
||||
if (role === "CUSTOMER") {
|
||||
setError(
|
||||
"این پنل مخصوص فروشندگان است. مشتریان از وبسایت مانا کالکشن وارد شوند.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
router.replace("/");
|
||||
function afterAuth(_role: string) {
|
||||
router.replace("/account");
|
||||
}
|
||||
|
||||
async function handleLoginPassword(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
async function handleLoginPassword(event?: FormEvent<HTMLFormElement>) {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
setError(null);
|
||||
setBusy(true);
|
||||
const form = new FormData(event.currentTarget);
|
||||
const cellNumber = String(form.get("cellNumber") ?? "").trim();
|
||||
const formEl =
|
||||
event?.currentTarget ??
|
||||
(document.getElementById("auth-login-form") as HTMLFormElement | null);
|
||||
if (!formEl) {
|
||||
setError("فرم ورود پیدا نشد");
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
const form = new FormData(formEl);
|
||||
const cellNumber = String(form.get("cellNumber") ?? "")
|
||||
.trim()
|
||||
.replace(/[۰-۹]/g, (d) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(d)));
|
||||
const password = String(form.get("password") ?? "");
|
||||
try {
|
||||
const user = await login(cellNumber, password);
|
||||
@@ -64,12 +66,23 @@ function AuthFormInner() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLoginOtp(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
async function handleLoginOtp(event?: FormEvent<HTMLFormElement>) {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
setError(null);
|
||||
setBusy(true);
|
||||
const form = new FormData(event.currentTarget);
|
||||
const cellNumber = String(form.get("cellNumber") ?? "").trim();
|
||||
const formEl =
|
||||
event?.currentTarget ??
|
||||
(document.getElementById("auth-login-form") as HTMLFormElement | null);
|
||||
if (!formEl) {
|
||||
setError("فرم ورود پیدا نشد");
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
const form = new FormData(formEl);
|
||||
const cellNumber = String(form.get("cellNumber") ?? "")
|
||||
.trim()
|
||||
.replace(/[۰-۹]/g, (d) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(d)));
|
||||
const code = String(form.get("code") ?? "").trim();
|
||||
try {
|
||||
if (!otpSent) {
|
||||
@@ -87,14 +100,25 @@ function AuthFormInner() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRegister(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
async function handleRegister(event?: FormEvent<HTMLFormElement>) {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
setError(null);
|
||||
setBusy(true);
|
||||
const form = new FormData(event.currentTarget);
|
||||
const formEl =
|
||||
event?.currentTarget ??
|
||||
(document.getElementById("auth-register-form") as HTMLFormElement | null);
|
||||
if (!formEl) {
|
||||
setError("فرم ثبتنام پیدا نشد");
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
const form = new FormData(formEl);
|
||||
const firstName = String(form.get("firstName") ?? "").trim();
|
||||
const lastName = String(form.get("lastName") ?? "").trim();
|
||||
const cellNumber = String(form.get("cellNumber") ?? "").trim();
|
||||
const cellNumber = String(form.get("cellNumber") ?? "")
|
||||
.trim()
|
||||
.replace(/[۰-۹]/g, (d) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(d)));
|
||||
const password = String(form.get("password") ?? "");
|
||||
const confirm = String(form.get("confirm") ?? "");
|
||||
const code = String(form.get("code") ?? "").trim();
|
||||
@@ -176,29 +200,16 @@ function AuthFormInner() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{customerHint ? (
|
||||
<p className="mb-4 text-xs leading-6 text-muted">
|
||||
حساب شما مشتری است. برای مدیریت فروشگاه باید با حساب فروشنده وارد
|
||||
شوید.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<p className="mb-4 text-xs leading-6 text-red-600">{error}</p>
|
||||
) : null}
|
||||
|
||||
<div className="grid min-h-0 flex-1">
|
||||
<div
|
||||
className={`col-start-1 row-start-1 flex items-center transition-opacity duration-300 ${
|
||||
isLogin
|
||||
? "relative z-10 opacity-100"
|
||||
: "pointer-events-none invisible z-0 opacity-0"
|
||||
}`}
|
||||
aria-hidden={!isLogin}
|
||||
>
|
||||
<div className="flex min-h-0 flex-1 items-center">
|
||||
{isLogin ? (
|
||||
<AuthPanel
|
||||
title="ورود به وبسایت"
|
||||
subtitle="با شماره موبایل و رمز عبور یا کد یکبارمصرف وارد پنل فروشگاه شوید."
|
||||
formId="auth-login-form"
|
||||
title="ورود به حساب"
|
||||
subtitle="با شماره موبایل و رمز عبور یا کد یکبارمصرف وارد شوید."
|
||||
onSubmit={
|
||||
loginMethod === "password"
|
||||
? handleLoginPassword
|
||||
@@ -215,7 +226,7 @@ function AuthFormInner() {
|
||||
placeholder="09123456789"
|
||||
dir="ltr"
|
||||
pattern="09[0-9]{9}"
|
||||
required={isLogin}
|
||||
required
|
||||
defaultValue={pendingCell || undefined}
|
||||
/>
|
||||
{loginMethod === "password" ? (
|
||||
@@ -227,7 +238,7 @@ function AuthFormInner() {
|
||||
autoComplete="off"
|
||||
placeholder="••••••••"
|
||||
dir="ltr"
|
||||
required={isLogin}
|
||||
required
|
||||
minLength={4}
|
||||
/>
|
||||
) : otpSent ? (
|
||||
@@ -246,10 +257,14 @@ function AuthFormInner() {
|
||||
/>
|
||||
) : null}
|
||||
<button
|
||||
type="submit"
|
||||
type="button"
|
||||
className="auth-button"
|
||||
disabled={busy}
|
||||
tabIndex={isLogin ? 0 : -1}
|
||||
onClick={() => {
|
||||
void (loginMethod === "password"
|
||||
? handleLoginPassword()
|
||||
: handleLoginOtp());
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{busy
|
||||
@@ -298,17 +313,9 @@ function AuthFormInner() {
|
||||
</button>
|
||||
</div>
|
||||
</AuthPanel>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`col-start-1 row-start-1 flex items-center transition-opacity duration-300 ${
|
||||
!isLogin
|
||||
? "relative z-10 opacity-100"
|
||||
: "pointer-events-none invisible z-0 opacity-0"
|
||||
}`}
|
||||
aria-hidden={isLogin}
|
||||
>
|
||||
) : (
|
||||
<AuthPanel
|
||||
formId="auth-register-form"
|
||||
title="عضویت در مانا کالکشن"
|
||||
subtitle="با تکمیل اطلاعات زیر، ما یک حساب کاربری برای شما ایجاد میکنیم."
|
||||
onSubmit={handleRegister}
|
||||
@@ -322,7 +329,7 @@ function AuthFormInner() {
|
||||
name="firstName"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
required={!isLogin}
|
||||
required
|
||||
/>
|
||||
<Field
|
||||
id="register-last"
|
||||
@@ -330,7 +337,7 @@ function AuthFormInner() {
|
||||
name="lastName"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
required={!isLogin}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<Field
|
||||
@@ -343,7 +350,7 @@ function AuthFormInner() {
|
||||
placeholder="09123456789"
|
||||
dir="ltr"
|
||||
pattern="09[0-9]{9}"
|
||||
required={!isLogin}
|
||||
required
|
||||
/>
|
||||
<Field
|
||||
id="register-password"
|
||||
@@ -353,7 +360,7 @@ function AuthFormInner() {
|
||||
autoComplete="new-password"
|
||||
placeholder="••••••••"
|
||||
dir="ltr"
|
||||
required={!isLogin}
|
||||
required
|
||||
minLength={4}
|
||||
/>
|
||||
<Field
|
||||
@@ -364,7 +371,7 @@ function AuthFormInner() {
|
||||
autoComplete="new-password"
|
||||
placeholder="••••••••"
|
||||
dir="ltr"
|
||||
required={!isLogin}
|
||||
required
|
||||
minLength={4}
|
||||
/>
|
||||
</>
|
||||
@@ -395,10 +402,12 @@ function AuthFormInner() {
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
type="button"
|
||||
className="auth-button"
|
||||
disabled={busy}
|
||||
tabIndex={!isLogin ? 0 : -1}
|
||||
onClick={() => {
|
||||
void handleRegister();
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{busy
|
||||
@@ -409,7 +418,7 @@ function AuthFormInner() {
|
||||
</span>
|
||||
</button>
|
||||
</AuthPanel>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="mt-6 shrink-0 text-xs leading-6 text-muted">
|
||||
@@ -450,36 +459,36 @@ function AuthFormInner() {
|
||||
}
|
||||
|
||||
export default function AuthForm() {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex min-h-dvh items-center justify-center text-sm text-muted">
|
||||
در حال بارگذاری…
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<AuthFormInner />
|
||||
</Suspense>
|
||||
);
|
||||
return <AuthFormInner />;
|
||||
}
|
||||
|
||||
function AuthPanel({
|
||||
formId,
|
||||
title,
|
||||
subtitle,
|
||||
onSubmit,
|
||||
children,
|
||||
}: {
|
||||
formId: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
||||
onSubmit: (event?: FormEvent<HTMLFormElement>) => void;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<h1 className="mb-2 text-xl font-medium text-ink sm:text-2xl">{title}</h1>
|
||||
<h1 className="font-doran mb-2 text-xl font-medium text-ink sm:text-2xl">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="mb-8 max-w-sm text-sm leading-7 text-muted">{subtitle}</p>
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
id={formId}
|
||||
noValidate
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onSubmit(event);
|
||||
}}
|
||||
className="flex w-full flex-col gap-4"
|
||||
autoComplete="off"
|
||||
>
|
||||
@@ -493,14 +502,11 @@ function Field({
|
||||
id,
|
||||
label,
|
||||
autoComplete = "off",
|
||||
onFocus,
|
||||
...props
|
||||
}: {
|
||||
id: string;
|
||||
label: string;
|
||||
} & InputHTMLAttributes<HTMLInputElement>) {
|
||||
const [unlockAutofill, setUnlockAutofill] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<label htmlFor={id} className="auth-label">
|
||||
@@ -511,11 +517,6 @@ function Field({
|
||||
className="auth-input"
|
||||
{...props}
|
||||
autoComplete={autoComplete}
|
||||
readOnly={!unlockAutofill}
|
||||
onFocus={(event) => {
|
||||
if (!unlockAutofill) setUnlockAutofill(true);
|
||||
onFocus?.(event);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user