189 lines
6.9 KiB
TypeScript
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;
|