feat: initialize project with Symfony, React, and Tailwind CSS setup
- 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
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user