You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
5.5 KiB
155 lines
5.5 KiB
import { useState } from 'react'
|
|
import { Navigate, useLocation } from 'react-router-dom'
|
|
import type { Location } from 'react-router-dom'
|
|
import { Ic, Pattern } from '@/icons'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { useAuth, useLoginMutation } from '@/features/auth/use-auth'
|
|
import { ApiError } from '@/services/http'
|
|
import { siteConfig } from '@/config/site'
|
|
import { AqilaLogo } from '@/components/AqilaLogo'
|
|
|
|
function Field({ label, children }: { label: string; children: React.ReactNode }) {
|
|
return (
|
|
<div className="space-y-1.5">
|
|
<p className="text-xs font-semibold text-grey-2">{label}</p>
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function LoginPage() {
|
|
const location = useLocation()
|
|
const { isAuthenticated } = useAuth()
|
|
const loginMutation = useLoginMutation()
|
|
|
|
const [email, setEmail] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [showPassword, setShowPassword] = useState(false)
|
|
|
|
const from = (location.state as { from?: Location } | null)?.from?.pathname ?? '/'
|
|
|
|
if (isAuthenticated) {
|
|
return <Navigate to={from} replace />
|
|
}
|
|
|
|
const valid = email.trim().length > 0 && password.length > 0
|
|
|
|
const submit = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
if (!valid || loginMutation.isPending) return
|
|
loginMutation.mutate({ email: email.trim(), password })
|
|
}
|
|
|
|
const fillDemo = () => {
|
|
setEmail('admin@gmail.com')
|
|
setPassword('admin')
|
|
}
|
|
|
|
const errorMessage =
|
|
loginMutation.error instanceof ApiError
|
|
? loginMutation.error.message
|
|
: loginMutation.error
|
|
? 'ورود ناموفق بود. دوباره تلاش کنید.'
|
|
: null
|
|
|
|
return (
|
|
<div className="relative flex min-h-svh items-center justify-center overflow-hidden bg-background px-4">
|
|
<div className="pointer-events-none absolute inset-0 opacity-[0.04]">
|
|
<Pattern />
|
|
</div>
|
|
|
|
<div className="relative w-full max-w-[420px]">
|
|
{/* لوگو و نام برند */}
|
|
<div className="mb-6 flex flex-col items-center gap-2.5 text-center">
|
|
<div className="flex size-14 items-center justify-center rounded-2xl bg-primary text-white shadow-lg">
|
|
<AqilaLogo className="size-8 text-white" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-xl font-black text-foreground">{siteConfig.name}</h1>
|
|
<p className="text-xs text-grey-3 mt-1">ورود به پنل مدیریت و داشبورد</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* کارت لاگین */}
|
|
<form
|
|
onSubmit={submit}
|
|
className="space-y-4 rounded-2xl border border-border-soft bg-card/60 p-6 shadow-2xl backdrop-blur"
|
|
>
|
|
{/* راهنمای لاگین دمو */}
|
|
<div className="flex items-center justify-between rounded-xl border border-primary/20 bg-primary/10 p-3 text-xs">
|
|
<div>
|
|
<span className="font-bold text-primary block">حساب آزمایشی آماده:</span>
|
|
<span dir="ltr" className="text-[11px] text-grey-2 font-mono">admin@gmail.com / admin</span>
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={fillDemo}
|
|
className="h-7 text-[11px]"
|
|
>
|
|
پر کردن خودکار
|
|
</Button>
|
|
</div>
|
|
|
|
<Field label="ایمیل حساب کاربری">
|
|
<Input
|
|
type="email"
|
|
dir="ltr"
|
|
className="text-left"
|
|
autoComplete="username"
|
|
placeholder="you@example.com"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
autoFocus
|
|
/>
|
|
</Field>
|
|
|
|
<Field label="رمز عبور">
|
|
<div className="relative">
|
|
<Input
|
|
type={showPassword ? 'text' : 'password'}
|
|
dir="ltr"
|
|
className="text-left pe-11"
|
|
autoComplete="current-password"
|
|
placeholder="••••••••"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPassword((v) => !v)}
|
|
className="absolute inset-y-0 start-0 flex w-11 items-center justify-center text-grey-3 transition-colors hover:text-grey-1 [&_svg]:size-4"
|
|
aria-label={showPassword ? 'مخفیکردن رمز' : 'نمایش رمز'}
|
|
tabIndex={-1}
|
|
>
|
|
<Ic name={showPassword ? 'eyeOff' : 'eye'} />
|
|
</button>
|
|
</div>
|
|
</Field>
|
|
|
|
{errorMessage && (
|
|
<div className="flex items-start gap-2 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-rose-400">
|
|
<Ic name="alert" className="size-4 shrink-0 mt-0.5" />
|
|
<span>{errorMessage}</span>
|
|
</div>
|
|
)}
|
|
|
|
<Button
|
|
type="submit"
|
|
size="lg"
|
|
className="w-full"
|
|
disabled={!valid || loginMutation.isPending}
|
|
>
|
|
{loginMutation.isPending ? 'در حال ورود…' : 'ورود به حساب کاربری'}
|
|
</Button>
|
|
</form>
|
|
|
|
<p className="mt-4 text-center text-xs text-grey-4">
|
|
پشتیبانی از قالبهای سفارشی و اتصال مستقیم به API
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|