Fix jwt-token

This commit is contained in:
2025-09-13 15:32:50 +02:00
parent 3a6e98d792
commit cd159239ed
7 changed files with 539 additions and 67 deletions

View File

@@ -1028,6 +1028,8 @@ const UsersSettings = ({ tenantConfig, onRefresh }) => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [showCreateUser, setShowCreateUser] = useState(false);
const [showEditUser, setShowEditUser] = useState(false);
const [editingUser, setEditingUser] = useState(null);
const authProvider = tenantConfig?.auth_provider;
const canManageUsers = authProvider === 'local'; // Only local auth allows user management
@@ -1246,13 +1248,30 @@ const UsersSettings = ({ tenantConfig, onRefresh }) => {
}}
/>
)}
{/* Edit User Modal for Local Auth */}
{showEditUser && canManageUsers && editingUser && (
<EditUserModal
isOpen={showEditUser}
onClose={() => {
setShowEditUser(false);
setEditingUser(null);
}}
user={editingUser}
onUserUpdated={() => {
fetchUsers();
setShowEditUser(false);
setEditingUser(null);
}}
/>
)}
</div>
);
// Helper functions for local user management
const handleEditUser = (user) => {
// TODO: Implement edit user modal
toast.info('Edit user functionality coming soon');
setEditingUser(user);
setShowEditUser(true);
};
const handleToggleUserStatus = async (user) => {
@@ -1418,4 +1437,220 @@ const CreateUserModal = ({ isOpen, onClose, onUserCreated }) => {
);
};
// Edit User Modal Component (for local auth only)
const EditUserModal = ({ isOpen, onClose, user, onUserUpdated }) => {
const [formData, setFormData] = useState({
email: '',
first_name: '',
last_name: '',
phone: '',
role: 'viewer',
password: '',
confirmPassword: ''
});
const [saving, setSaving] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
useEffect(() => {
if (user) {
setFormData({
email: user.email || '',
first_name: user.first_name || '',
last_name: user.last_name || '',
phone: user.phone || '',
role: user.role || 'viewer',
password: '',
confirmPassword: ''
});
}
}, [user]);
const handleSubmit = async (e) => {
e.preventDefault();
// Validate passwords if provided
if (formData.password && formData.password !== formData.confirmPassword) {
toast.error('Passwords do not match');
return;
}
setSaving(true);
try {
// Prepare update data (exclude password if empty)
const updateData = {
email: formData.email,
first_name: formData.first_name,
last_name: formData.last_name,
phone: formData.phone,
role: formData.role
};
// Only include password if it's provided
if (formData.password.trim()) {
updateData.password = formData.password;
}
await api.put(`/tenant/users/${user.id}`, updateData);
toast.success('User updated successfully');
onUserUpdated();
} catch (error) {
const message = error.response?.data?.message || 'Failed to update user';
toast.error(message);
} finally {
setSaving(false);
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" onClick={onClose}></div>
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<form onSubmit={handleSubmit}>
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="w-full">
<h3 className="text-lg leading-6 font-medium text-gray-900 mb-4">
Edit User: {user?.username}
</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">Email</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700">First Name</label>
<input
type="text"
value={formData.first_name}
onChange={(e) => setFormData(prev => ({ ...prev, first_name: e.target.value }))}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Last Name</label>
<input
type="text"
value={formData.last_name}
onChange={(e) => setFormData(prev => ({ ...prev, last_name: e.target.value }))}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Phone</label>
<input
type="tel"
value={formData.phone}
onChange={(e) => setFormData(prev => ({ ...prev, phone: e.target.value }))}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Role</label>
<select
value={formData.role}
onChange={(e) => setFormData(prev => ({ ...prev, role: e.target.value }))}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500"
>
<option value="viewer">Viewer</option>
<option value="operator">Operator</option>
<option value="admin">Admin</option>
</select>
</div>
<div className="border-t pt-4">
<h4 className="text-sm font-medium text-gray-700 mb-3">Change Password (Optional)</h4>
<div>
<label className="block text-sm font-medium text-gray-700">New Password</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
value={formData.password}
onChange={(e) => setFormData(prev => ({ ...prev, password: e.target.value }))}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500 pr-10"
placeholder="Leave empty to keep current password"
/>
<button
type="button"
className="absolute inset-y-0 right-0 pr-3 flex items-center"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeSlashIcon className="h-5 w-5 text-gray-400" />
) : (
<EyeIcon className="h-5 w-5 text-gray-400" />
)}
</button>
</div>
</div>
<div className="mt-3">
<label className="block text-sm font-medium text-gray-700">Confirm New Password</label>
<div className="relative">
<input
type={showConfirmPassword ? 'text' : 'password'}
value={formData.confirmPassword}
onChange={(e) => setFormData(prev => ({ ...prev, confirmPassword: e.target.value }))}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500 pr-10"
placeholder="Confirm new password"
/>
<button
type="button"
className="absolute inset-y-0 right-0 pr-3 flex items-center"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
>
{showConfirmPassword ? (
<EyeSlashIcon className="h-5 w-5 text-gray-400" />
) : (
<EyeIcon className="h-5 w-5 text-gray-400" />
)}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="submit"
disabled={saving}
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-primary-600 text-base font-medium text-white hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"
>
{saving ? 'Updating...' : 'Update User'}
</button>
<button
type="button"
onClick={onClose}
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
);
};
export default Settings;