Add confirmation modal for Super Admin role assignment

Prevents accidental Super Admin privilege grants by requiring users to
type "make this user super admin" before the role change is applied.
Modal blocks paste/drag input and reverts the dropdown on cancel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael 2026-02-22 08:25:56 -06:00
parent 51c4909ee7
commit a5d5d51d2a

View file

@ -25,6 +25,11 @@ export const UserManagement: React.FC = () => {
const [savedUserId, setSavedUserId] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
// Super Admin confirmation modal
const [pendingSuperAdminUser, setPendingSuperAdminUser] = useState<{ userId: string; userName: string } | null>(null);
const [confirmationText, setConfirmationText] = useState('');
const [confirmationError, setConfirmationError] = useState(false);
// New agency form
const [newAgencyName, setNewAgencyName] = useState('');
const [isCreatingAgency, setIsCreatingAgency] = useState(false);
@ -57,6 +62,13 @@ export const UserManagement: React.FC = () => {
};
const handleRoleChange = async (userId: string, newRole: string) => {
const user = users.find(u => u.id === userId);
if (newRole === 'super_admin' && user?.role !== 'super_admin') {
setPendingSuperAdminUser({ userId, userName: user?.name || 'this user' });
setConfirmationText('');
setConfirmationError(false);
return;
}
try {
const updated = await apiService.updateUser(userId, { role: newRole });
setUsers(prev => prev.map(u => u.id === userId ? updated : u));
@ -67,6 +79,32 @@ export const UserManagement: React.FC = () => {
}
};
const handleConfirmSuperAdmin = async () => {
if (confirmationText !== 'make this user super admin') {
setConfirmationError(true);
return;
}
if (!pendingSuperAdminUser) return;
const { userId } = pendingSuperAdminUser;
setPendingSuperAdminUser(null);
setConfirmationText('');
setConfirmationError(false);
try {
const updated = await apiService.updateUser(userId, { role: 'super_admin' });
setUsers(prev => prev.map(u => u.id === userId ? updated : u));
showSavedIndicator(userId);
} catch (err) {
console.error('Failed to update user role:', err);
setError('Failed to update user role.');
}
};
const handleCancelSuperAdmin = () => {
setPendingSuperAdminUser(null);
setConfirmationText('');
setConfirmationError(false);
};
const handleAgencyChange = async (userId: string, agencyId: string | null) => {
try {
const updated = await apiService.updateUser(userId, { agency_id: agencyId });
@ -224,6 +262,70 @@ export const UserManagement: React.FC = () => {
</div>
</section>
{/* Super Admin Confirmation Modal */}
{pendingSuperAdminUser && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
onClick={handleCancelSuperAdmin}
>
<div
className="bg-white rounded-[10px] shadow-xl max-w-md w-full mx-4 p-6"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center gap-3 mb-4">
<div className="flex-shrink-0 h-10 w-10 rounded-full bg-amber-100 flex items-center justify-center">
<svg className="h-5 w-5 text-amber-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-black-title">Confirm Super Admin Assignment</h3>
</div>
<p className="text-sm text-grey-700 mb-4">
You are about to grant Super Admin privileges to <strong className="text-black-title">{pendingSuperAdminUser.userName}</strong>. This gives full system access including user management. This action should only be performed intentionally.
</p>
<p className="text-sm text-grey-700 mb-2">
To confirm, type <strong className="text-black-title">make this user super admin</strong> below:
</p>
<input
type="text"
value={confirmationText}
onChange={(e) => {
setConfirmationText(e.target.value);
setConfirmationError(false);
}}
onPaste={(e) => e.preventDefault()}
onDrop={(e) => e.preventDefault()}
onDragOver={(e) => e.preventDefault()}
placeholder="Type the phrase above..."
className={`w-full p-2 border-2 rounded-[10px] text-sm text-black-title focus:outline-none focus:ring-2 transition ${
confirmationError
? 'border-error focus:ring-error'
: 'border-grey-700 focus:ring-active-blue focus:border-active-blue'
}`}
autoFocus
/>
{confirmationError && (
<p className="text-error text-xs mt-1">Please type the exact phrase to confirm.</p>
)}
<div className="flex justify-end gap-3 mt-5">
<button
onClick={handleCancelSuperAdmin}
className="px-4 py-2 text-sm font-medium text-grey-700 hover:text-black-title border border-grey-300 rounded-full hover:bg-grey-100 transition-colors"
>
Cancel
</button>
<button
onClick={handleConfirmSuperAdmin}
disabled={confirmationText !== 'make this user super admin'}
className="px-4 py-2 text-sm font-medium text-white bg-active-blue rounded-full hover:bg-active-blue/90 transition-colors disabled:bg-grey-700 disabled:cursor-not-allowed"
>
Confirm
</button>
</div>
</div>
</div>
)}
{/* Agency Management */}
<section>
<h2 className="text-xl font-semibold text-primary-blue mb-4">Agencies ({agencies.length})</h2>