fastcheck/src/components/modals/UserModal.tsx
2026-04-08 13:58:46 -04:00

189 lines
6.9 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Dialog } from '@headlessui/react';
import { XMarkIcon } from '@heroicons/react/24/outline';
import toast from 'react-hot-toast';
interface User {
id: string;
name: string;
email: string;
role: 'superuser' | 'tenant_admin' | 'evaluator' | 'read_only' | 'write_only';
isActive: boolean;
}
interface UserModalProps {
isOpen: boolean;
onClose: () => void;
user?: User;
onSuccess: () => void;
}
const API_URL = globalThis.__APP_ENV__?.VITE_API_BASE_URL || 'https://duxiter.azurianlab.com/api';
const UserModal: React.FC<UserModalProps> = ({ isOpen, onClose, user, onSuccess }) => {
const [formData, setFormData] = useState({
name: '',
email: '',
role: 'evaluator',
password: ''
});
useEffect(() => {
if (user) {
setFormData({
name: user.name,
email: user.email,
role: user.role,
password: ''
});
} else {
setFormData({
name: '',
email: '',
role: 'evaluator',
password: ''
});
}
}, [user]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const token = localStorage.getItem('token');
const url = user
? `${API_URL}/users/${user.id}`
: `${API_URL}/users`;
const response = await fetch(url, {
method: user ? 'PUT' : 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(formData)
});
if (!response.ok) {
throw new Error(user ? 'Failed to update user' : 'Failed to create user');
}
toast.success(user ? 'User updated successfully' : 'User created successfully');
onSuccess();
onClose();
} catch (error) {
logger.error('Error:', error);
toast.error(user ? 'Failed to update user' : 'Failed to create user');
}
};
return (
<Dialog
open={isOpen}
onClose={onClose}
className="relative z-50"
>
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
<div className="fixed inset-0 flex items-center justify-center p-4">
<Dialog.Panel className="mx-auto max-w-sm rounded-lg bg-white dark:bg-gray-800">
<div className="flex items-center justify-between border-b border-gray-200 dark:border-gray-700 px-4 py-3">
<Dialog.Title className="text-lg font-semibold leading-6 text-gray-900 dark:text-white">
{user ? 'Edit User' : 'Add New User'}
</Dialog.Title>
<button
type="button"
className="rounded-md bg-white dark:bg-gray-800 text-gray-400 hover:text-gray-500 dark:text-gray-300 dark:hover:text-white"
onClick={onClose}
>
<XMarkIcon className="h-6 w-6" />
</button>
</div>
<form onSubmit={handleSubmit} className="p-4">
<div className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Name
</label>
<input
type="text"
id="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
required
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Email
</label>
<input
type="email"
id="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
required
/>
</div>
<div>
<label htmlFor="role" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Role
</label>
<select
id="role"
value={formData.role}
onChange={(e) => setFormData({ ...formData, role: e.target.value as 'tenant_admin' | 'evaluator' })}
className="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
>
<option value="tenant_admin">Admin</option>
<option value="evaluator">Evaluator</option>
</select>
</div>
{!user && (
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Password
</label>
<input
type="password"
id="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
required={!user}
minLength={6}
/>
</div>
)}
</div>
<div className="mt-6 flex justify-end space-x-3">
<button
type="button"
onClick={onClose}
className="rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
>
Cancel
</button>
<button
type="submit"
className="rounded-md border border-transparent bg-primary-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
>
{user ? 'Update' : 'Create'}
</button>
</div>
</form>
</Dialog.Panel>
</div>
</Dialog>
);
};
export default UserModal;