This commit is contained in:
2026-01-23 20:35:40 +03:00
parent cf3b0e48ec
commit 66c151653e
137 changed files with 41495 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
import { useState, useEffect } from 'react';
import { Form } from '@remix-run/react';
import { Input, Button, Select, Text } from '~/components/ui';
import { validateUser } from '~/lib/validation';
import { AUTH_LEVELS, USER_STATUS } from '~/types/auth';
import type { UserWithoutPassword } from '~/types/database';
interface UserFormProps {
user?: UserWithoutPassword;
onSubmit: (data: FormData) => void;
onCancel: () => void;
loading?: boolean;
currentUserAuthLevel: number;
}
export function UserForm({
user,
onSubmit,
onCancel,
loading = false,
currentUserAuthLevel,
}: UserFormProps) {
const [formData, setFormData] = useState({
name: user?.name || '',
username: user?.username || '',
email: user?.email || '',
password: '',
confirmPassword: '',
authLevel: user?.authLevel || AUTH_LEVELS.USER,
status: user?.status || USER_STATUS.ACTIVE,
});
const [errors, setErrors] = useState<Record<string, string>>({});
const [touched, setTouched] = useState<Record<string, boolean>>({});
const isEditing = !!user;
// Validate form data
useEffect(() => {
const validationData = {
name: formData.name,
username: formData.username,
email: formData.email,
authLevel: formData.authLevel,
status: formData.status,
};
// Only validate password for new users or when password is provided
if (!isEditing || formData.password) {
validationData.password = formData.password;
}
const validation = validateUser(validationData);
// Add confirm password validation
const newErrors = { ...validation.errors };
if (!isEditing || formData.password) {
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = 'كلمات المرور غير متطابقة';
}
}
setErrors(newErrors);
}, [formData, isEditing]);
const handleInputChange = (field: string, value: string | number) => {
setFormData(prev => ({ ...prev, [field]: value }));
setTouched(prev => ({ ...prev, [field]: true }));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Mark all fields as touched
const allFields = Object.keys(formData);
setTouched(allFields.reduce((acc, field) => ({ ...acc, [field]: true }), {}));
// Check if form is valid
if (Object.keys(errors).length > 0) {
return;
}
// Create FormData
const submitData = new FormData();
submitData.append('name', formData.name);
submitData.append('username', formData.username);
submitData.append('email', formData.email);
submitData.append('authLevel', formData.authLevel.toString());
submitData.append('status', formData.status);
if (!isEditing || formData.password) {
submitData.append('password', formData.password);
}
if (isEditing) {
submitData.append('userId', user.id.toString());
submitData.append('_action', 'update');
} else {
submitData.append('_action', 'create');
}
onSubmit(submitData);
};
// Get available auth levels based on current user's level
const getAuthLevelOptions = () => {
const options = [];
if (currentUserAuthLevel === AUTH_LEVELS.SUPERADMIN) {
options.push({ value: AUTH_LEVELS.SUPERADMIN, label: 'مدير عام' });
}
if (currentUserAuthLevel <= AUTH_LEVELS.ADMIN) {
options.push({ value: AUTH_LEVELS.ADMIN, label: 'مدير' });
}
options.push({ value: AUTH_LEVELS.USER, label: 'مستخدم' });
return options;
};
const statusOptions = [
{ value: USER_STATUS.ACTIVE, label: 'نشط' },
{ value: USER_STATUS.INACTIVE, label: 'غير نشط' },
];
return (
<Form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input
label="الاسم"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
error={touched.name ? errors.name : undefined}
required
/>
<Input
label="اسم المستخدم"
value={formData.username}
onChange={(e) => handleInputChange('username', e.target.value)}
error={touched.username ? errors.username : undefined}
required
/>
</div>
<Input
label="البريد الإلكتروني"
type="email"
value={formData.email}
onChange={(e) => handleInputChange('email', e.target.value)}
error={touched.email ? errors.email : undefined}
required
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input
label={isEditing ? "كلمة المرور الجديدة (اختياري)" : "كلمة المرور"}
type="password"
value={formData.password}
onChange={(e) => handleInputChange('password', e.target.value)}
error={touched.password ? errors.password : undefined}
required={!isEditing}
/>
<Input
label="تأكيد كلمة المرور"
type="password"
value={formData.confirmPassword}
onChange={(e) => handleInputChange('confirmPassword', e.target.value)}
error={touched.confirmPassword ? errors.confirmPassword : undefined}
required={!isEditing || !!formData.password}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Select
label="مستوى الصلاحية"
value={formData.authLevel}
onChange={(e) => handleInputChange('authLevel', parseInt(e.target.value))}
options={getAuthLevelOptions()}
error={touched.authLevel ? errors.authLevel : undefined}
required
/>
<Select
label="الحالة"
value={formData.status}
onChange={(e) => handleInputChange('status', e.target.value)}
options={statusOptions}
error={touched.status ? errors.status : undefined}
required
/>
</div>
<div className="flex justify-start space-x-3 space-x-reverse pt-4">
<Button
type="submit"
loading={loading}
disabled={Object.keys(errors).length > 0}
>
{isEditing ? 'تحديث المستخدم' : 'إنشاء المستخدم'}
</Button>
<Button
type="button"
variant="outline"
onClick={onCancel}
disabled={loading}
>
إلغاء
</Button>
</div>
</Form>
);
}