Skip to content

Commit 25afa31

Browse files
committed
feat: Enhance user management with role filtering, user deletion, and profile updates
1 parent 2f55bb1 commit 25afa31

File tree

7 files changed

+431
-67
lines changed

7 files changed

+431
-67
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
2+
import React from 'react';
3+
import { X, User, Mail, Calendar, Shield, Trash2, MapPin } from 'lucide-react';
4+
import { User as UserType } from '../../types';
5+
import { useAuth } from '../../context/AuthContext';
6+
import { CustomSelect } from '../ui/CustomSelect';
7+
8+
interface UserProfileModalProps {
9+
isOpen: boolean;
10+
onClose: () => void;
11+
user: UserType | null;
12+
onDeleteRequest: (user: UserType) => void;
13+
onRoleUpdate: (userId: number, newRole: string) => void;
14+
}
15+
16+
const ROLE_OPTIONS = [
17+
{ label: 'Super Admin', value: 'ROLE_SUPER_ADMIN' },
18+
{ label: 'Admin', value: 'ROLE_ADMIN' },
19+
{ label: 'Employee', value: 'ROLE_EMPLOYEE' },
20+
{ label: 'Client', value: 'ROLE_CLIENT' },
21+
];
22+
23+
export const UserProfileModal: React.FC<UserProfileModalProps> = ({ isOpen, onClose, user, onDeleteRequest, onRoleUpdate }) => {
24+
const { user: currentUser } = useAuth();
25+
26+
if (!isOpen || !user) return null;
27+
28+
const isSelf = currentUser?.id === user.id;
29+
30+
const getRoleBadge = (role: string) => {
31+
let styles = 'bg-gray-100 text-gray-600 border-gray-200';
32+
if (role === 'ROLE_SUPER_ADMIN') styles = 'bg-yellow-50 text-yellow-700 border-yellow-200';
33+
if (role === 'ROLE_ADMIN') styles = 'bg-purple-50 text-purple-700 border-purple-200';
34+
if (role === 'ROLE_EMPLOYEE') styles = 'bg-blue-50 text-blue-700 border-blue-200';
35+
36+
const label = role.replace('ROLE_', '').replace('_', ' ');
37+
return <span className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-bold uppercase tracking-wide border ${styles}`}>{label}</span>;
38+
};
39+
40+
return (
41+
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-in fade-in duration-200" onClick={onClose}>
42+
<div className="bg-white rounded-[2rem] shadow-2xl w-full max-w-md overflow-hidden flex flex-col transform transition-all scale-100 relative border border-gray-100" onClick={(e) => e.stopPropagation()}>
43+
44+
{/* Close Button - Z-Index 50 to stay above banner */}
45+
<button
46+
onClick={onClose}
47+
className="absolute top-4 right-4 z-50 p-2 bg-black/20 hover:bg-black/30 text-white rounded-full transition-colors backdrop-blur-md"
48+
>
49+
<X className="h-5 w-5" />
50+
</button>
51+
52+
{/* Cover Background */}
53+
<div className="h-48 relative bg-gradient-to-r from-brand-600 to-indigo-600 shrink-0">
54+
<img
55+
src="/banner.png"
56+
alt="Profile Banner"
57+
className="w-full h-full object-cover absolute inset-0 z-10"
58+
onError={(e) => e.currentTarget.style.display = 'none'}
59+
/>
60+
{/* Avatar - Absolute to banner but hanging off bottom */}
61+
<div className="absolute -bottom-12 left-8 z-30">
62+
<div className="h-24 w-24 rounded-3xl bg-white p-1.5 shadow-xl border border-gray-100">
63+
{user.avatarUrl ? (
64+
<img src={user.avatarUrl} alt={user.name} className="h-full w-full rounded-2xl object-cover bg-gray-100" />
65+
) : (
66+
<div className="h-full w-full rounded-2xl bg-gradient-to-br from-gray-100 to-gray-200 flex items-center justify-center text-3xl font-bold text-gray-400 uppercase tracking-tighter">
67+
{user.name.charAt(0)}
68+
</div>
69+
)}
70+
</div>
71+
</div>
72+
</div>
73+
74+
{/* User Info - Added top padding to account for avatar overlap */}
75+
<div className="pt-16 pb-8 px-8">
76+
<div className="mb-6">
77+
<div className="flex flex-col gap-4">
78+
<div>
79+
<h2 className="text-2xl font-bold text-gray-900">{user.name}</h2>
80+
<div className="mt-3">
81+
{isSelf ? (
82+
<div title="You cannot change your own role.">
83+
{getRoleBadge(user.role)}
84+
</div>
85+
) : (
86+
<div className="w-full">
87+
<CustomSelect
88+
value={user.role}
89+
onChange={(val) => onRoleUpdate(user.id, val)}
90+
options={ROLE_OPTIONS}
91+
placeholder="Select Role"
92+
className="w-full"
93+
/>
94+
</div>
95+
)}
96+
</div>
97+
</div>
98+
</div>
99+
100+
<div className="mt-6 space-y-3">
101+
<div className="flex items-center gap-3 text-sm text-gray-600 p-3.5 bg-gray-50 rounded-2xl border border-gray-100">
102+
<Mail className="h-5 w-5 text-gray-400" />
103+
<span className="font-medium">{user.email}</span>
104+
</div>
105+
106+
<div className="flex items-center gap-3 text-sm text-gray-600 p-3.5 bg-gray-50 rounded-2xl border border-gray-100">
107+
<Calendar className="h-5 w-5 text-gray-400" />
108+
<span>Joined {user.createdAt ? new Date(user.createdAt).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }) : 'Unknown'}</span>
109+
</div>
110+
111+
<div className="flex items-center gap-3 text-sm text-gray-600 p-3.5 bg-gray-50 rounded-2xl border border-gray-100">
112+
<Shield className="h-5 w-5 text-gray-400" />
113+
<span>System ID: <span className="font-mono text-xs font-bold bg-gray-200 px-2 py-0.5 rounded text-gray-600">{user.id}</span></span>
114+
</div>
115+
</div>
116+
</div>
117+
118+
{/* Actions */}
119+
<div className="pt-6 border-t border-gray-100 flex justify-end">
120+
{isSelf ? (
121+
<div className="text-xs text-gray-400 italic">You cannot delete your own account.</div>
122+
) : (
123+
<button
124+
onClick={() => onDeleteRequest(user)}
125+
className="flex items-center gap-2 px-5 py-2.5 bg-red-50 text-red-600 hover:bg-red-100 rounded-xl font-bold text-sm transition-colors border border-red-100"
126+
>
127+
<Trash2 className="h-4 w-4" /> Delete User
128+
</button>
129+
)}
130+
</div>
131+
</div>
132+
</div>
133+
</div>
134+
);
135+
};

client/components/layout/Sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const Sidebar: React.FC = () => {
6565
<div className={`h-[88px] flex items-center ${isSidebarCollapsed ? 'justify-center' : 'px-8'} transition-all duration-300`}>
6666
<div className="flex items-center gap-3.5 overflow-hidden whitespace-nowrap group cursor-pointer">
6767
<div className={`relative flex items-center justify-center rounded-xl bg-gradient-to-br from-brand-500 to-brand-700 shadow-lg shadow-brand-500/30 transition-all duration-500 ${isSidebarCollapsed ? 'h-10 w-10' : 'h-9 w-9'}`}>
68-
<Command className="h-5 w-5 text-white" />
68+
<img src="/logo.png" alt="Incial" className="h-9 w-9 rounded-xl bg-white shadow-lg object-contain p-1 flex-shrink-0" />
6969
</div>
7070

7171
{!isSidebarCollapsed && (

client/components/ui/DeleteConfirmationModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const DeleteConfirmationModal: React.FC<DeleteConfirmationModalProps> = (
2222
if (!isOpen) return null;
2323

2424
return (
25-
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-in fade-in duration-200" onClick={onClose}>
25+
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-in fade-in duration-200" onClick={onClose}>
2626
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden transform transition-all scale-100 p-6" onClick={(e) => e.stopPropagation()}>
2727

2828
<div className="flex items-start gap-4">

0 commit comments

Comments
 (0)