126 lines
4.5 KiB
TypeScript
126 lines
4.5 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
||
import { useForm } from 'react-hook-form';
|
||
import { zodResolver } from '@hookform/resolvers/zod';
|
||
import { z } from 'zod';
|
||
import { toast } from 'sonner';
|
||
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
|
||
import { useAuthStore } from '../stores/authStore';
|
||
|
||
const schema = z.object({
|
||
mobile: z.string().min(10, 'شماره موبایل معتبر نیست'),
|
||
password: z.string().min(6, 'رمز عبور باید حداقل ۶ کاراکتر باشد'),
|
||
});
|
||
|
||
type FormData = z.infer<typeof schema>;
|
||
|
||
export default function LoginPage() {
|
||
const login = useAuthStore((s) => s.login);
|
||
const [showPass, setShowPass] = useState(false);
|
||
|
||
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>({
|
||
resolver: zodResolver(schema),
|
||
});
|
||
|
||
const onSubmit = async (data: FormData) => {
|
||
try {
|
||
const res = await fetch('/api/v1/user/login', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ mobile_number: data.mobile, password: data.password }),
|
||
});
|
||
|
||
if (!res.ok) {
|
||
const err = await res.json();
|
||
toast.error(err?.errors?.[0]?.message ?? 'خطا در ورود');
|
||
return;
|
||
}
|
||
|
||
const json = await res.json();
|
||
login(json.access_token, json.refresh_token ?? '');
|
||
toast.success('خوش آمدید');
|
||
} catch {
|
||
toast.error('خطا در اتصال به سرور');
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div style={{
|
||
minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||
background: 'var(--bg)', padding: 20,
|
||
}}>
|
||
<div className="card card-pad" style={{ width: '100%', maxWidth: 420 }}>
|
||
{/* Logo */}
|
||
<div style={{ textAlign: 'center', marginBottom: 28 }}>
|
||
<div className="brand-logo" style={{
|
||
margin: '0 auto 16px', width: 54, height: 54, borderRadius: 16,
|
||
fontSize: 26,
|
||
}}>
|
||
♥
|
||
</div>
|
||
<h1 style={{ fontSize: 22, fontWeight: 800, marginBottom: 6 }}>ورود به پنل ادمین</h1>
|
||
<p className="muted" style={{ fontSize: 13 }}>اطلاعات حساب مدیریتی خود را وارد کنید</p>
|
||
</div>
|
||
|
||
<form onSubmit={handleSubmit(onSubmit)} noValidate>
|
||
{/* Mobile */}
|
||
<div className="form-row">
|
||
<label>شماره موبایل</label>
|
||
<input
|
||
{...register('mobile')}
|
||
className={`input${errors.mobile ? ' err' : ''}`}
|
||
type="tel"
|
||
dir="ltr"
|
||
placeholder="09xxxxxxxxx"
|
||
autoComplete="username"
|
||
style={{ textAlign: 'right' }}
|
||
/>
|
||
{errors.mobile && <div className="err-text">{errors.mobile.message}</div>}
|
||
</div>
|
||
|
||
{/* Password */}
|
||
<div className="form-row">
|
||
<label>رمز عبور</label>
|
||
<div style={{ position: 'relative' }}>
|
||
<input
|
||
{...register('password')}
|
||
className={`input${errors.password ? ' err' : ''}`}
|
||
type={showPass ? 'text' : 'password'}
|
||
placeholder="••••••••"
|
||
autoComplete="current-password"
|
||
style={{ paddingLeft: 44 }}
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowPass((v) => !v)}
|
||
style={{
|
||
position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)',
|
||
color: 'var(--text-3)', background: 'none', border: 'none', cursor: 'pointer',
|
||
display: 'flex', alignItems: 'center',
|
||
}}
|
||
>
|
||
{showPass
|
||
? <EyeSlashIcon style={{ width: 18, height: 18 }} />
|
||
: <EyeIcon style={{ width: 18, height: 18 }} />}
|
||
</button>
|
||
</div>
|
||
{errors.password && <div className="err-text">{errors.password.message}</div>}
|
||
</div>
|
||
|
||
<button
|
||
type="submit"
|
||
className="btn primary block"
|
||
disabled={isSubmitting}
|
||
style={{ marginTop: 8, height: 46, fontSize: 15 }}
|
||
>
|
||
{isSubmitting ? 'در حال ورود...' : 'ورود به سیستم'}
|
||
</button>
|
||
</form>
|
||
|
||
<p className="muted" style={{ textAlign: 'center', fontSize: 12, marginTop: 20 }}>
|
||
ClinicPro — نسخه ۱.۰.۰
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|