feat: replace mock system with real backend, RBAC, and Teams management
All checks were successful
Build and Deploy / build-and-push (push) Successful in 2m3s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 2m3s
- Implemented real JWT authentication and persistent user sessions - Replaced all hardcoded mock data with dynamic MySQL-backed API calls - Created new 'Times' (Teams) dashboard with performance metrics - Renamed 'Equipe' to 'Membros' and centralized team management - Added Role-Based Access Control (RBAC) for Admin/Manager/Agent roles - Implemented secure invite-only member creation and password setup flow - Enhanced Login with password visibility and real-time validation - Added safe delete confirmation modal and custom Toast notifications
This commit is contained in:
@@ -1,285 +1,153 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Users, Plus, MoreHorizontal, Mail, Shield, Search, X, Edit, Trash2, Save } from 'lucide-react';
|
||||
import { USERS } from '../constants';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Users, Plus, Mail, Search, X, Edit, Trash2, Loader2, CheckCircle2, AlertCircle, AlertTriangle } from 'lucide-react';
|
||||
import { getUsers, getTeams, createMember, deleteUser, updateUser, getUserById } from '../services/dataService';
|
||||
import { User } from '../types';
|
||||
|
||||
export const TeamManagement: React.FC = () => {
|
||||
const [users, setUsers] = useState<User[]>(USERS.filter(u => u.role !== 'super_admin')); // Default hide super admin from list
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [teams, setTeams] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
// State for handling Add/Edit
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const [userToDelete, setUserToDelete] = useState<User | null>(null);
|
||||
const [deleteConfirmName, setDeleteConfirmName] = useState('');
|
||||
const [editingUser, setEditingUser] = useState<User | null>(null);
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
role: 'agent' as 'super_admin' | 'admin' | 'manager' | 'agent',
|
||||
team_id: 'sales_1',
|
||||
status: 'active' as 'active' | 'inactive'
|
||||
});
|
||||
const [formData, setFormData] = useState({ name: '', email: '', role: 'agent' as any, team_id: '', status: 'active' as any });
|
||||
|
||||
const filteredUsers = users.filter(u =>
|
||||
u.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
u.email.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
const loadData = async () => {
|
||||
const tid = localStorage.getItem('ctms_tenant_id');
|
||||
const uid = localStorage.getItem('ctms_user_id');
|
||||
if (!tid) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const [fu, ft, me] = await Promise.all([getUsers(tid), getTeams(tid), uid ? getUserById(uid) : null]);
|
||||
setUsers(fu.filter(u => u.role !== 'super_admin'));
|
||||
setTeams(ft);
|
||||
if (me) setCurrentUser(me);
|
||||
} catch (err) { console.error(err); } finally { setLoading(false); }
|
||||
};
|
||||
|
||||
useEffect(() => { loadData(); }, []);
|
||||
|
||||
const handleSave = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const tid = localStorage.getItem('ctms_tenant_id') || '';
|
||||
const success = editingUser ? await updateUser(editingUser.id, formData) : await createMember({ ...formData, tenant_id: tid });
|
||||
if (success) { setIsModalOpen(false); loadData(); }
|
||||
} catch (err) { console.error(err); } finally { setIsSaving(false); }
|
||||
};
|
||||
|
||||
const handleConfirmDelete = async () => {
|
||||
if (!userToDelete || deleteConfirmName !== userToDelete.name) return;
|
||||
setIsSaving(true);
|
||||
try {
|
||||
if (await deleteUser(userToDelete.id)) { setIsDeleteModalOpen(false); loadData(); }
|
||||
} catch (err) { console.error(err); } finally { setIsSaving(false); }
|
||||
};
|
||||
|
||||
if (loading && users.length === 0) return <div className="flex h-screen items-center justify-center text-slate-400">Carregando...</div>;
|
||||
|
||||
const canManage = currentUser?.role === 'admin' || currentUser?.role === 'super_admin';
|
||||
const filtered = users.filter(u => u.name.toLowerCase().includes(searchTerm.toLowerCase()) || u.email.toLowerCase().includes(searchTerm.toLowerCase()));
|
||||
|
||||
const getRoleBadge = (role: string) => {
|
||||
switch (role) {
|
||||
case 'super_admin': return 'bg-slate-900 text-white border-slate-700';
|
||||
case 'admin': return 'bg-purple-100 text-purple-700 border-purple-200';
|
||||
case 'manager': return 'bg-blue-100 text-blue-700 border-blue-200';
|
||||
default: return 'bg-slate-100 text-slate-700 border-slate-200';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
if (status === 'active') {
|
||||
return 'bg-green-100 text-green-700 border-green-200';
|
||||
}
|
||||
return 'bg-slate-100 text-slate-500 border-slate-200';
|
||||
};
|
||||
|
||||
// Actions
|
||||
const handleDelete = (userId: string) => {
|
||||
if (window.confirm('Tem certeza que deseja excluir este usuário? Esta ação não pode ser desfeita.')) {
|
||||
setUsers(prev => prev.filter(u => u.id !== userId));
|
||||
}
|
||||
};
|
||||
|
||||
const openAddModal = () => {
|
||||
setEditingUser(null);
|
||||
setFormData({ name: '', email: '', role: 'agent', team_id: 'sales_1', status: 'active' });
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const openEditModal = (user: User) => {
|
||||
setEditingUser(user);
|
||||
setFormData({
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
team_id: user.team_id,
|
||||
status: user.status
|
||||
});
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const handleSave = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (editingUser) {
|
||||
// Update existing user
|
||||
setUsers(prev => prev.map(u => u.id === editingUser.id ? { ...u, ...formData } : u));
|
||||
} else {
|
||||
// Create new user
|
||||
const newUser: User = {
|
||||
id: Date.now().toString(),
|
||||
tenant_id: 'tenant_123', // Mock default
|
||||
avatar_url: `https://ui-avatars.com/api/?name=${encodeURIComponent(formData.name)}&background=random`,
|
||||
...formData
|
||||
};
|
||||
setUsers(prev => [...prev, newUser]);
|
||||
}
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8 max-w-7xl mx-auto">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div className="space-y-8 max-w-7xl mx-auto pb-12">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 tracking-tight">Gerenciamento de Equipe</h1>
|
||||
<p className="text-slate-500 text-sm">Gerencie acesso, funções e times de vendas da sua organização.</p>
|
||||
<h1 className="text-2xl font-bold text-slate-900 tracking-tight">Membros</h1>
|
||||
<p className="text-slate-500 text-sm">Gerencie os acessos da sua organização.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={openAddModal}
|
||||
className="flex items-center gap-2 bg-slate-900 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-slate-800 transition-colors shadow-sm"
|
||||
>
|
||||
<Plus size={16} /> Adicionar Membro
|
||||
</button>
|
||||
{canManage && (
|
||||
<button onClick={() => { setEditingUser(null); setFormData({name:'', email:'', role:'agent', team_id:'', status:'active'}); setIsModalOpen(true); }} className="bg-slate-900 text-white px-4 py-2 rounded-lg flex items-center gap-2 text-sm font-bold shadow-sm">
|
||||
<Plus size={16} /> Adicionar Membro
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-slate-200 rounded-xl shadow-sm overflow-hidden">
|
||||
{/* Toolbar */}
|
||||
<div className="p-4 border-b border-slate-100 flex items-center justify-between gap-4">
|
||||
<div className="relative w-full max-w-sm">
|
||||
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Buscar por nome ou e-mail..."
|
||||
className="w-full pl-9 pr-4 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm outline-none focus:ring-2 focus:ring-blue-100 transition-all"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-slate-500 hidden sm:block">
|
||||
{filteredUsers.length} membros encontrados
|
||||
</div>
|
||||
<div className="p-4 border-b border-slate-100 bg-slate-50/30">
|
||||
<input type="text" placeholder="Buscar membros..." value={searchTerm} onChange={e => setSearchTerm(e.target.value)} className="w-full max-w-md border border-slate-200 p-2 rounded-lg text-sm outline-none" />
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-slate-50/50 text-slate-500 text-xs uppercase tracking-wider border-b border-slate-100">
|
||||
<th className="px-6 py-4 font-semibold">Usuário</th>
|
||||
<th className="px-6 py-4 font-semibold">Função</th>
|
||||
<th className="px-6 py-4 font-semibold">Time</th>
|
||||
<th className="px-6 py-4 font-semibold">Status</th>
|
||||
<th className="px-6 py-4 font-semibold text-right">Ações</th>
|
||||
<table className="w-full text-left">
|
||||
<thead className="bg-slate-50/50 text-slate-500 text-xs uppercase font-bold border-b">
|
||||
<tr>
|
||||
<th className="px-6 py-4">Usuário</th>
|
||||
<th className="px-6 py-4">Função</th>
|
||||
<th className="px-6 py-4">Time</th>
|
||||
<th className="px-6 py-4">Status</th>
|
||||
{canManage && <th className="px-6 py-4 text-right">Ações</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y text-sm">
|
||||
{filtered.map(user => (
|
||||
<tr key={user.id} className="hover:bg-slate-50 group">
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<img src={`https://ui-avatars.com/api/?name=${encodeURIComponent(user.name)}&background=random`} alt="" className="w-8 h-8 rounded-full" />
|
||||
<div><div className="font-bold text-slate-900">{user.name}</div><div className="text-xs text-slate-500">{user.email}</div></div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4"><span className={`px-2.5 py-0.5 rounded-full text-xs font-bold border capitalize ${getRoleBadge(user.role)}`}>{user.role}</span></td>
|
||||
<td className="px-6 py-4 text-slate-600">{teams.find(t => t.id === user.team_id)?.name || '-'}</td>
|
||||
<td className="px-6 py-4"><span className={`px-2 py-0.5 rounded-full text-xs font-bold ${user.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-500'}`}>{user.status}</span></td>
|
||||
{canManage && (
|
||||
<td className="px-6 py-4 text-right flex justify-end gap-2 opacity-0 group-hover:opacity-100">
|
||||
<button onClick={() => { setEditingUser(user); setFormData({name:user.name, email:user.email, role:user.role as any, team_id:user.team_id||'', status:user.status as any}); setIsModalOpen(true); }} className="p-1 hover:text-blue-600"><Edit size={16} /></button>
|
||||
<button onClick={() => { setUserToDelete(user); setDeleteConfirmName(''); setIsDeleteModalOpen(true); }} className="p-1 hover:text-red-600"><Trash2 size={16} /></button>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100 text-sm">
|
||||
{filteredUsers.map((user) => (
|
||||
<tr key={user.id} className="hover:bg-slate-50/50 transition-colors group">
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<img src={user.avatar_url} alt="" className="w-10 h-10 rounded-full border border-slate-200 object-cover" />
|
||||
<span className={`absolute bottom-0 right-0 w-3 h-3 rounded-full border-2 border-white ${user.status === 'active' ? 'bg-green-500' : 'bg-slate-300'}`}></span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-slate-900">{user.name}</div>
|
||||
<div className="flex items-center gap-1.5 text-xs text-slate-500">
|
||||
<Mail size={12} /> {user.email}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold border capitalize ${getRoleBadge(user.role)}`}>
|
||||
{user.role === 'manager' ? 'Gerente' : user.role === 'agent' ? 'Agente' : user.role === 'admin' ? 'Admin' : 'Super Admin'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-slate-600 font-medium">
|
||||
{user.team_id === 'sales_1' ? 'Vendas Alpha' : user.team_id === 'sales_2' ? 'Vendas Beta' : '-'}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold border capitalize ${getStatusBadge(user.status)}`}>
|
||||
{user.status === 'active' ? 'Ativo' : 'Inativo'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-right">
|
||||
<div className="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onClick={() => openEditModal(user)}
|
||||
className="p-2 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
title="Editar Usuário"
|
||||
>
|
||||
<Edit size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(user.id)}
|
||||
className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
title="Excluir Usuário"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{filteredUsers.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={5} className="px-6 py-8 text-center text-slate-400 italic">Nenhum usuário encontrado.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Add/Edit Modal */}
|
||||
{isModalOpen && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-900/50 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-xl shadow-xl w-full max-w-md overflow-hidden animate-in fade-in zoom-in duration-200">
|
||||
<div className="px-6 py-4 border-b border-slate-100 flex justify-between items-center bg-slate-50/50">
|
||||
<h3 className="text-lg font-bold text-slate-900">{editingUser ? 'Editar Usuário' : 'Convidar Novo Membro'}</h3>
|
||||
<button onClick={() => setIsModalOpen(false)} className="text-slate-400 hover:text-slate-600 transition-colors"><X size={20} /></button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSave} className="p-6 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-700">Nome Completo</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
className="w-full px-3 py-2 border border-slate-200 bg-slate-50 rounded-lg text-sm outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-400 transition-all"
|
||||
placeholder="João Silva"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({...formData, name: e.target.value})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-700">Endereço de E-mail</label>
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
className="w-full px-3 py-2 border border-slate-200 bg-slate-50 rounded-lg text-sm outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-400 transition-all"
|
||||
placeholder="joao@empresa.com"
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData({...formData, email: e.target.value})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-700">Função</label>
|
||||
<select
|
||||
className="w-full px-3 py-2 border border-slate-200 bg-slate-50 rounded-lg text-sm outline-none focus:ring-2 focus:ring-blue-100 transition-all cursor-pointer"
|
||||
value={formData.role}
|
||||
onChange={(e) => setFormData({...formData, role: e.target.value as any})}
|
||||
>
|
||||
<option value="agent">Agente</option>
|
||||
<option value="manager">Gerente</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-700">Time</label>
|
||||
<select
|
||||
className="w-full px-3 py-2 border border-slate-200 bg-slate-50 rounded-lg text-sm outline-none focus:ring-2 focus:ring-blue-100 transition-all cursor-pointer"
|
||||
value={formData.team_id}
|
||||
onChange={(e) => setFormData({...formData, team_id: e.target.value})}
|
||||
>
|
||||
<option value="sales_1">Vendas Alpha</option>
|
||||
<option value="sales_2">Vendas Beta</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-700">Status da Conta</label>
|
||||
<select
|
||||
className="w-full px-3 py-2 border border-slate-200 bg-slate-50 rounded-lg text-sm outline-none focus:ring-2 focus:ring-blue-100 transition-all cursor-pointer"
|
||||
value={formData.status}
|
||||
onChange={(e) => setFormData({...formData, status: e.target.value as any})}
|
||||
>
|
||||
<option value="active">Ativo</option>
|
||||
<option value="inactive">Inativo</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
className="px-4 py-2 text-slate-600 hover:bg-slate-100 rounded-lg text-sm font-medium transition-colors"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex items-center gap-2 px-4 py-2 bg-slate-900 text-white rounded-lg font-medium text-sm hover:bg-slate-800 transition-colors shadow-sm"
|
||||
>
|
||||
<Save size={16} /> {editingUser ? 'Salvar Alterações' : 'Adicionar Membro'}
|
||||
</button>
|
||||
</div>
|
||||
{isModalOpen && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-2xl p-6 w-full max-w-md shadow-xl">
|
||||
<h3 className="text-lg font-bold mb-4">{editingUser ? 'Editar' : 'Novo'} Membro</h3>
|
||||
<form onSubmit={handleSave} className="space-y-4">
|
||||
<div><label className="text-xs font-bold text-slate-500 block mb-1">NOME</label><input type="text" value={formData.name} onChange={e => setFormData({...formData, name:e.target.value})} className="w-full border p-2 rounded-lg" required /></div>
|
||||
<div><label className="text-xs font-bold text-slate-500 block mb-1">E-MAIL</label><input type="email" value={formData.email} onChange={e => setFormData({...formData, email:e.target.value})} className="w-full border p-2 rounded-lg" disabled={!!editingUser} required /></div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div><label className="text-xs font-bold text-slate-500 block mb-1">FUNÇÃO</label><select value={formData.role} onChange={e => setFormData({...formData, role: e.target.value as any})} className="w-full border p-2 rounded-lg"><option value="agent">Agente</option><option value="manager">Gerente</option><option value="admin">Admin</option></select></div>
|
||||
<div><label className="text-xs font-bold text-slate-500 block mb-1">TIME</label><select value={formData.team_id} onChange={e => setFormData({...formData, team_id: e.target.value})} className="w-full border p-2 rounded-lg"><option value="">Nenhum</option>{teams.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}</select></div>
|
||||
</div>
|
||||
<div><label className="text-xs font-bold text-slate-500 block mb-1">STATUS</label><select value={formData.status} onChange={e => setFormData({...formData, status: e.target.value as any})} className="w-full border p-2 rounded-lg"><option value="active">Ativo</option><option value="inactive">Inativo</option></select></div>
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<button type="button" onClick={() => setIsModalOpen(false)} className="px-4 py-2">Cancelar</button>
|
||||
<button type="submit" className="bg-slate-900 text-white px-6 py-2 rounded-lg font-bold">{isSaving ? '...' : 'Salvar'}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{isDeleteModalOpen && userToDelete && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50">
|
||||
<div className="bg-white rounded-2xl p-6 w-full max-w-md">
|
||||
<h3 className="text-lg font-bold mb-2">Excluir {userToDelete.name}?</h3>
|
||||
<input type="text" value={deleteConfirmName} onChange={e => setDeleteConfirmName(e.target.value)} className="w-full border-2 p-2 rounded-lg mb-4 text-center font-bold" placeholder="Digite o nome para confirmar" />
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => setIsDeleteModalOpen(false)} className="flex-1 py-2">Cancelar</button>
|
||||
<button onClick={handleConfirmDelete} disabled={deleteConfirmName !== userToDelete.name} className="flex-1 py-2 bg-red-600 text-white rounded-lg">Excluir</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user