feat: Enhance user forms and user management UI (#437)

This commit is contained in:
samanhappy
2025-11-23 13:50:55 +08:00
committed by GitHub
parent ac0b60ed4b
commit 6de3221974
7 changed files with 270 additions and 89 deletions

View File

@@ -62,93 +62,132 @@ const EditUserForm = ({ user, onEdit, onCancel }: EditUserFormProps) => {
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
setFormData((prev) => ({
...prev,
[name]: type === 'checkbox' ? checked : value
[name]: type === 'checkbox' ? checked : value,
}));
};
return (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center z-50">
<div className="bg-white p-8 rounded-lg shadow-xl max-w-md w-full mx-4">
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
<div className="bg-white p-8 rounded-xl shadow-2xl max-w-md w-full mx-4 border border-gray-100">
<form onSubmit={handleSubmit}>
<h2 className="text-xl font-semibold text-gray-800 mb-4">
{t('users.edit')} - {user.username}
<h2 className="text-xl font-bold text-gray-900 mb-6">
{t('users.edit')} - <span className="text-blue-600">{user.username}</span>
</h2>
{error && (
<div className="bg-red-100 border-l-4 border-red-500 text-red-700 p-3 mb-4">
<p className="text-sm">{error}</p>
<div className="bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-md">
<p className="text-sm font-medium">{error}</p>
</div>
)}
<div className="space-y-4">
<div className="flex items-center">
<div className="space-y-5">
<div className="flex items-center pt-2">
<input
type="checkbox"
id="isAdmin"
name="isAdmin"
checked={formData.isAdmin}
onChange={handleInputChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
className="h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded transition-colors duration-200"
disabled={isSubmitting}
/>
<label htmlFor="isAdmin" className="ml-2 block text-sm text-gray-700">
<label
htmlFor="isAdmin"
className="ml-3 block text-sm font-medium text-gray-700 cursor-pointer select-none"
>
{t('users.adminRole')}
</label>
</div>
<div>
<label htmlFor="newPassword" className="block text-sm font-medium text-gray-700 mb-1">
{t('users.newPassword')}
</label>
<input
type="password"
id="newPassword"
name="newPassword"
value={formData.newPassword}
onChange={handleInputChange}
placeholder={t('users.newPasswordPlaceholder')}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isSubmitting}
minLength={6}
/>
</div>
<div className="border-t border-gray-100 pt-4 mt-2">
<p className="text-xs text-gray-500 uppercase font-semibold tracking-wider mb-3">
{t('users.changePassword')}
</p>
{formData.newPassword && (
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
{t('users.confirmPassword')}
</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleInputChange}
placeholder={t('users.confirmPasswordPlaceholder')}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isSubmitting}
minLength={6}
/>
<div className="space-y-4">
<div>
<label
htmlFor="newPassword"
className="block text-sm font-medium text-gray-700 mb-1"
>
{t('users.newPassword')}
</label>
<input
type="password"
id="newPassword"
name="newPassword"
value={formData.newPassword}
onChange={handleInputChange}
placeholder={t('users.newPasswordPlaceholder')}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200"
disabled={isSubmitting}
minLength={6}
/>
</div>
{formData.newPassword && (
<div className="animate-fadeIn">
<label
htmlFor="confirmPassword"
className="block text-sm font-medium text-gray-700 mb-1"
>
{t('users.confirmPassword')}
</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleInputChange}
placeholder={t('users.confirmPasswordPlaceholder')}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200"
disabled={isSubmitting}
minLength={6}
/>
</div>
)}
</div>
)}
</div>
</div>
<div className="flex justify-end space-x-3 mt-6">
<div className="flex justify-end space-x-3 mt-8">
<button
type="button"
onClick={onCancel}
className="px-4 py-2 text-gray-700 bg-gray-200 rounded hover:bg-gray-300 transition-colors duration-200"
className="px-5 py-2.5 text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-all duration-200 font-medium btn-secondary shadow-sm"
disabled={isSubmitting}
>
{t('common.cancel')}
</button>
<button
type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
className="px-5 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all duration-200 font-medium btn-primary shadow-md disabled:opacity-70 disabled:cursor-not-allowed flex items-center"
disabled={isSubmitting}
>
{isSubmitting && (
<svg
className="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
)}
{isSubmitting ? t('common.updating') : t('users.update')}
</button>
</div>