- Add package.json with development and production dependencies - Create postcss.config.js for Tailwind CSS integration - Implement AdminController for handling admin routes - Add admin index template with React root element - Create base template for consistent layout - Configure TypeScript with tsconfig.json - Set up Webpack configuration for asset management
95 lines
3.3 KiB
TypeScript
95 lines
3.3 KiB
TypeScript
import React from 'react';
|
|
import { useForm } from 'react-hook-form';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { z } from 'zod';
|
|
import { toast } from 'sonner';
|
|
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 { 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.message || 'خطا در ورود');
|
|
return;
|
|
}
|
|
|
|
const json = await res.json();
|
|
login(json.access_token, json.refresh_token ?? '');
|
|
toast.success('ورود موفق');
|
|
} catch {
|
|
toast.error('خطا در اتصال به سرور');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-[#f1f5f9]">
|
|
<div className="bg-white rounded-2xl shadow-lg p-8 w-full max-w-sm">
|
|
<div className="text-center mb-8">
|
|
<h1 className="text-2xl font-bold text-gray-900">ClinicPro</h1>
|
|
<p className="text-gray-500 text-sm mt-1">پنل مدیریت</p>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
شماره موبایل
|
|
</label>
|
|
<input
|
|
{...register('mobile')}
|
|
type="tel"
|
|
dir="ltr"
|
|
placeholder="09xxxxxxxxx"
|
|
className="w-full h-11 border border-gray-300 rounded-[10px] px-3 text-right focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
/>
|
|
{errors.mobile && (
|
|
<p className="text-red-500 text-xs mt-1">{errors.mobile.message}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
رمز عبور
|
|
</label>
|
|
<input
|
|
{...register('password')}
|
|
type="password"
|
|
placeholder="••••••••"
|
|
className="w-full h-11 border border-gray-300 rounded-[10px] px-3 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
/>
|
|
{errors.password && (
|
|
<p className="text-red-500 text-xs mt-1">{errors.password.message}</p>
|
|
)}
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={isSubmitting}
|
|
className="w-full h-11 bg-primary-600 hover:bg-primary-700 disabled:opacity-60 text-white font-medium rounded-[10px] transition-colors"
|
|
>
|
|
{isSubmitting ? 'در حال ورود...' : 'ورود'}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|