150 lines
5.1 KiB
JavaScript
150 lines
5.1 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog';
|
|
import { Button } from './ui/button';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
|
import { Label } from './ui/label';
|
|
import { AlertCircle, Shield } from 'lucide-react';
|
|
import api from '../utils/api';
|
|
import { toast } from 'sonner';
|
|
|
|
export default function ChangeRoleDialog({ open, onClose, user, onSuccess }) {
|
|
const [roles, setRoles] = useState([]);
|
|
const [selectedRole, setSelectedRole] = useState('');
|
|
const [selectedRoleId, setSelectedRoleId] = useState(null);
|
|
const [loadingRoles, setLoadingRoles] = useState(false);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
fetchRoles();
|
|
// Pre-select current role
|
|
setSelectedRole(user.role);
|
|
setSelectedRoleId(user.role_id);
|
|
}
|
|
}, [open, user]);
|
|
|
|
const fetchRoles = async () => {
|
|
setLoadingRoles(true);
|
|
try {
|
|
// Reuse existing endpoint that returns assignable roles based on privilege
|
|
const response = await api.get('/admin/roles/assignable');
|
|
// Map API response to format expected by Select component
|
|
const mappedRoles = response.data.map(role => ({
|
|
value: role.code,
|
|
label: role.name,
|
|
id: role.id,
|
|
description: role.description
|
|
}));
|
|
setRoles(mappedRoles);
|
|
} catch (error) {
|
|
console.error('Failed to fetch assignable roles:', error);
|
|
toast.error('Failed to load roles. Please try again.');
|
|
} finally {
|
|
setLoadingRoles(false);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (!selectedRole) {
|
|
toast.error('Please select a role');
|
|
return;
|
|
}
|
|
|
|
// Don't submit if role hasn't changed
|
|
if (selectedRole === user.role && selectedRoleId === user.role_id) {
|
|
toast.info('The selected role is the same as current role');
|
|
return;
|
|
}
|
|
|
|
setSubmitting(true);
|
|
try {
|
|
await api.put(`/admin/users/${user.id}/role`, {
|
|
role: selectedRole,
|
|
role_id: selectedRoleId
|
|
});
|
|
|
|
toast.success(`Role changed to ${selectedRole}`);
|
|
|
|
onSuccess();
|
|
onClose();
|
|
} catch (error) {
|
|
const message = error.response?.data?.detail || 'Failed to change role';
|
|
toast.error(message);
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onClose}>
|
|
<DialogContent className="sm:max-w-[500px] overflow-y-auto max-h-[90vh]">
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-2">
|
|
<Shield className="h-5 w-5 text-[#664fa3]" />
|
|
Change User Role
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
Change role for {user.first_name} {user.last_name} ({user.email})
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4 py-4">
|
|
{/* Current Role Display */}
|
|
<div className="p-3 bg-[#f1eef9] rounded-lg border border-[#DDD8EB]">
|
|
<p className="text-sm text-gray-600">Current Role</p>
|
|
<p className="font-semibold text-[#664fa3] capitalize">{user.role}</p>
|
|
</div>
|
|
|
|
{/* Role Selection */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="role">New Role</Label>
|
|
<Select value={selectedRole} onValueChange={setSelectedRole} disabled={loadingRoles}>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder={loadingRoles ? "Loading roles..." : "Select role"} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{roles.map((role) => (
|
|
<SelectItem key={role.value} value={role.value}>
|
|
<span className="capitalize">{role.label}</span>
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Warning for privileged roles */}
|
|
{(selectedRole === 'admin' || selectedRole === 'superadmin') && (
|
|
<div className="flex items-start gap-2 p-3 bg-amber-50 border border-amber-200 rounded-lg">
|
|
<AlertCircle className="h-5 w-5 text-amber-600 flex-shrink-0 mt-0.5" />
|
|
<div className="text-sm">
|
|
<p className="font-semibold text-amber-900">Admin Access Warning</p>
|
|
<p className="text-amber-700">
|
|
This user will gain full administrative access to the system.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-3">
|
|
<Button
|
|
variant="outline"
|
|
onClick={onClose}
|
|
disabled={submitting}
|
|
className="border-2 border-gray-300 rounded-full"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={handleSubmit}
|
|
disabled={submitting || loadingRoles}
|
|
className="bg-[#664fa3] hover:bg-[#7d5ec2] text-white rounded-full"
|
|
>
|
|
{submitting ? 'Changing Role...' : 'Change Role'}
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|