source: chapterx-frontend/src/components/admin/UserTable.tsx@ b62cefc

main
Last change on this file since b62cefc was b62cefc, checked in by kikisrbinoska <srbinoskakristina07@…>, 4 months ago

Added frontend and imporoved AI Suggestions service

  • Property mode set to 100644
File size: 6.7 KB
Line 
1import React, { useState } from 'react'
2import { Search, Shield, UserX, UserCheck } from 'lucide-react'
3import { useAuthStore } from '../../store/authStore'
4import { useNotificationStore } from '../../store/notificationStore'
5import { useUIStore } from '../../store/uiStore'
6import { User, UserRole } from '../../types'
7import { Avatar } from '../ui/Avatar'
8import { RoleBadge } from '../ui/Badge'
9import { Button } from '../ui/Button'
10import { Modal } from '../ui/Modal'
11
12export const UserTable: React.FC = () => {
13 const { allUsers, updateUserRole, currentUser } = useAuthStore()
14 const { addNotification } = useNotificationStore()
15 const { addToast } = useUIStore()
16 const [search, setSearch] = useState('')
17 const [confirmUser, setConfirmUser] = useState<User | null>(null)
18 const [confirmAction, setConfirmAction] = useState<'promote' | 'demote' | null>(null)
19
20 const filtered = allUsers.filter(
21 u =>
22 u.username.toLowerCase().includes(search.toLowerCase()) ||
23 u.email.toLowerCase().includes(search.toLowerCase()) ||
24 u.name.toLowerCase().includes(search.toLowerCase())
25 )
26
27 const handlePromote = (user: User) => {
28 const newRole: UserRole = user.role === 'regular' ? 'writer' : user.role === 'writer' ? 'admin' : 'admin'
29 updateUserRole(user.user_id, newRole)
30 addNotification({
31 user_id: user.user_id,
32 type: 'system',
33 title: 'Role Updated',
34 message: `Your account has been promoted to ${newRole}.`,
35 })
36 addToast(`${user.username} promoted to ${newRole}`)
37 setConfirmUser(null)
38 }
39
40 const handleDemote = (user: User) => {
41 const newRole: UserRole = user.role === 'admin' ? 'writer' : 'regular'
42 updateUserRole(user.user_id, newRole)
43 addToast(`${user.username} role changed to ${newRole}`, 'info')
44 setConfirmUser(null)
45 }
46
47 const formatDate = (str: string) =>
48 new Date(str).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
49
50 return (
51 <div>
52 {/* Search */}
53 <div className="relative mb-4">
54 <Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" />
55 <input
56 value={search}
57 onChange={e => setSearch(e.target.value)}
58 placeholder="Search users..."
59 className="w-full pl-9 pr-4 py-2.5 bg-slate-800 border border-slate-700 rounded-xl text-sm text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500"
60 />
61 </div>
62
63 {/* Table */}
64 <div className="overflow-x-auto rounded-xl border border-slate-700">
65 <table className="w-full text-sm">
66 <thead>
67 <tr className="border-b border-slate-700 bg-slate-800">
68 <th className="text-left px-4 py-3 text-slate-400 font-medium">User</th>
69 <th className="text-left px-4 py-3 text-slate-400 font-medium hidden md:table-cell">Email</th>
70 <th className="text-left px-4 py-3 text-slate-400 font-medium">Role</th>
71 <th className="text-left px-4 py-3 text-slate-400 font-medium hidden sm:table-cell">Joined</th>
72 <th className="text-right px-4 py-3 text-slate-400 font-medium">Actions</th>
73 </tr>
74 </thead>
75 <tbody className="divide-y divide-slate-800">
76 {filtered.map(user => (
77 <tr key={user.user_id} className="hover:bg-slate-800/50 transition-colors">
78 <td className="px-4 py-3">
79 <div className="flex items-center gap-3">
80 <Avatar name={user.name} size="sm" />
81 <div>
82 <p className="text-white font-medium">{user.name} {user.surname}</p>
83 <p className="text-slate-500 text-xs">@{user.username}</p>
84 </div>
85 </div>
86 </td>
87 <td className="px-4 py-3 text-slate-400 hidden md:table-cell">{user.email}</td>
88 <td className="px-4 py-3"><RoleBadge role={user.role} /></td>
89 <td className="px-4 py-3 text-slate-500 hidden sm:table-cell">{formatDate(user.created_at)}</td>
90 <td className="px-4 py-3">
91 <div className="flex items-center justify-end gap-2">
92 {user.user_id !== currentUser?.user_id && user.role !== 'admin' && (
93 <button
94 onClick={() => { setConfirmUser(user); setConfirmAction('promote') }}
95 className="flex items-center gap-1 text-xs text-indigo-400 hover:text-indigo-300 px-2 py-1 rounded bg-indigo-500/10 hover:bg-indigo-500/20 transition-colors"
96 >
97 <UserCheck size={12} />
98 Promote
99 </button>
100 )}
101 {user.user_id !== currentUser?.user_id && user.role !== 'regular' && (
102 <button
103 onClick={() => { setConfirmUser(user); setConfirmAction('demote') }}
104 className="flex items-center gap-1 text-xs text-rose-400 hover:text-rose-300 px-2 py-1 rounded bg-rose-500/10 hover:bg-rose-500/20 transition-colors"
105 >
106 <UserX size={12} />
107 Demote
108 </button>
109 )}
110 </div>
111 </td>
112 </tr>
113 ))}
114 </tbody>
115 </table>
116 {filtered.length === 0 && (
117 <div className="text-center py-10 text-slate-500">No users found.</div>
118 )}
119 </div>
120
121 {/* Confirm modal */}
122 <Modal
123 isOpen={!!confirmUser && !!confirmAction}
124 onClose={() => { setConfirmUser(null); setConfirmAction(null) }}
125 title={confirmAction === 'promote' ? 'Promote User' : 'Demote User'}
126 size="sm"
127 >
128 {confirmUser && (
129 <div className="space-y-4">
130 <p className="text-slate-300 text-sm">
131 {confirmAction === 'promote'
132 ? `Promote @${confirmUser.username} to the next role tier?`
133 : `Demote @${confirmUser.username} to a lower role?`}
134 </p>
135 <div className="flex gap-3">
136 <Button variant="secondary" className="flex-1" onClick={() => { setConfirmUser(null); setConfirmAction(null) }}>
137 Cancel
138 </Button>
139 <Button
140 variant={confirmAction === 'promote' ? 'primary' : 'danger'}
141 className="flex-1"
142 onClick={() => confirmAction === 'promote' ? handlePromote(confirmUser) : handleDemote(confirmUser)}
143 >
144 <Shield size={14} />
145 Confirm
146 </Button>
147 </div>
148 </div>
149 )}
150 </Modal>
151 </div>
152 )
153}
Note: See TracBrowser for help on using the repository browser.