@@ -4,6 +4,7 @@ import apiClient from '../../api/apiClient';
44import { useAuth } from '../../components/AuthProvider' ;
55import { Button } from '../../components/ui/button' ;
66import { Input } from '../../components/ui/input' ;
7+ import { ConfirmationModal } from '../../components/ConfirmationModal' ;
78
89interface CombinedUser {
910 username : string ;
@@ -67,6 +68,14 @@ export const UserManagement: React.FC = () => {
6768 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
6869 const [ activeTab , setActiveTab ] = useState < 'pending' | 'approved' > ( 'pending' ) ;
6970 const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
71+ const [ editingUser , setEditingUser ] = useState < CombinedUser | null > ( null ) ;
72+ const [ denyingUser , setDenyingUser ] = useState < CombinedUser | null > ( null ) ;
73+ const [ verifyingUser , setVerifyingUser ] = useState < CombinedUser | null > ( null ) ;
74+ const [ modalPosition , setModalPosition ] = useState <
75+ { top : number ; right : number } | undefined
76+ > ( undefined ) ;
77+ const [ isUpdatingRole , setIsUpdatingRole ] = useState ( false ) ;
78+ const [ isProcessing , setIsProcessing ] = useState ( false ) ;
7079
7180 const fetchUsers = async ( ) => {
7281 try {
@@ -84,25 +93,60 @@ export const UserManagement: React.FC = () => {
8493 fetchUsers ( ) ;
8594 } , [ ] ) ;
8695
87- const handleVerify = async ( email : string ) => {
96+ const handleVerify = async ( ) => {
97+ if ( ! verifyingUser ) return ;
98+ setIsProcessing ( true ) ;
8899 try {
89100 await ( apiClient as any ) . axiosInstance . post ( '/api/auth/admin-verify' , {
90- email,
101+ email : verifyingUser . email ,
91102 } ) ;
92- fetchUsers ( ) ;
103+ await fetchUsers ( ) ;
104+ setVerifyingUser ( null ) ;
105+ setModalPosition ( undefined ) ;
93106 } catch ( err : any ) {
94107 alert ( 'Error: ' + err . message ) ;
108+ } finally {
109+ setIsProcessing ( false ) ;
95110 }
96111 } ;
97112
98- const handleDeny = async ( email : string ) => {
113+ const handleDeny = async ( ) => {
114+ if ( ! denyingUser ) return ;
115+ setIsProcessing ( true ) ;
99116 try {
100117 await ( apiClient as any ) . axiosInstance . post ( '/api/auth/admin-deny' , {
101- email,
118+ email : denyingUser . email ,
102119 } ) ;
103- fetchUsers ( ) ;
120+ await fetchUsers ( ) ;
121+ setDenyingUser ( null ) ;
122+ setModalPosition ( undefined ) ;
104123 } catch ( err : any ) {
105124 alert ( 'Error: ' + err . message ) ;
125+ } finally {
126+ setIsProcessing ( false ) ;
127+ }
128+ } ;
129+
130+ const handleEditRole = async ( ) => {
131+ if ( ! editingUser ) return ;
132+
133+ // Shell function: we'll just log and close the modal for now
134+ // as per instructions: "build it for edit role as an example"
135+ setIsUpdatingRole ( true ) ;
136+ try {
137+ console . log ( `Updating role for ${ editingUser . email } ` ) ;
138+ // Replace with your API call:
139+ // await apiClient.axiosInstance.patch('/api/auth/user/role', { ... })
140+
141+ // Artificial delay to show loading state if you have one
142+ await new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) ) ;
143+
144+ await fetchUsers ( ) ;
145+ setEditingUser ( null ) ;
146+ } catch ( err : any ) {
147+ alert ( 'Error changing role: ' + err . message ) ;
148+ } finally {
149+ setIsUpdatingRole ( false ) ;
106150 }
107151 } ;
108152
@@ -249,15 +293,31 @@ export const UserManagement: React.FC = () => {
249293 < >
250294 < Button
251295 variant = "success"
252- onClick = { ( ) => handleVerify ( user . email ) }
296+ onClick = { ( e ) => {
297+ const rect =
298+ e . currentTarget . getBoundingClientRect ( ) ;
299+ setModalPosition ( {
300+ top : rect . bottom + 8 ,
301+ right : window . innerWidth - rect . right ,
302+ } ) ;
303+ setVerifyingUser ( user ) ;
304+ } }
253305 className = "rounded-[10px] px-3 py-1 h-auto text-sm leading-6"
254306 >
255307 Approve
256308 </ Button >
257309 < Button
258310 variant = "outline"
259- onClick = { ( ) => handleDeny ( user . email ) }
260- className = "rounded-[10px] px-3 py-1 h-auto text-sm leading-6 border-[#e5e5e5] text-black bg-white hover:bg-gray-50"
311+ onClick = { ( e ) => {
312+ const rect =
313+ e . currentTarget . getBoundingClientRect ( ) ;
314+ setModalPosition ( {
315+ top : rect . bottom + 8 ,
316+ right : window . innerWidth - rect . right ,
317+ } ) ;
318+ setDenyingUser ( user ) ;
319+ } }
320+ className = "rounded-[10px] px-3 py-1 h-auto text-sm leading-6 border-[#e5e5e5] text-black bg-white hover:bg-gray-50 shadow-sm"
261321 >
262322 Deny
263323 </ Button >
@@ -266,13 +326,30 @@ export const UserManagement: React.FC = () => {
266326 < >
267327 < Button
268328 variant = "outline"
269- className = "rounded-[10px] px-3 py-1 h-auto text-sm leading-6 font-['Source_Sans_Pro'] border-[#e5e5e5] text-black bg-white hover:bg-gray-50"
329+ onClick = { ( e ) => {
330+ const rect =
331+ e . currentTarget . getBoundingClientRect ( ) ;
332+ setModalPosition ( {
333+ top : rect . bottom + 8 ,
334+ right : window . innerWidth - rect . right ,
335+ } ) ;
336+ setEditingUser ( user ) ;
337+ } }
338+ className = "rounded-[10px] px-3 py-1 h-auto text-sm leading-6 font-['Source_Sans_Pro'] border-[#e5e5e5] text-black bg-white hover:bg-gray-50 shadow-sm"
270339 >
271340 Edit Role
272341 </ Button >
273342 < Button
274- onClick = { ( ) => handleDeny ( user . email ) }
275- className = "rounded-[10px] px-3 py-1 h-auto text-sm leading-6 bg-[#893C27] text-white hover:bg-[#6c2f1f] border-0"
343+ onClick = { ( e ) => {
344+ const rect =
345+ e . currentTarget . getBoundingClientRect ( ) ;
346+ setModalPosition ( {
347+ top : rect . bottom + 8 ,
348+ right : window . innerWidth - rect . right ,
349+ } ) ;
350+ setDenyingUser ( user ) ;
351+ } }
352+ className = "rounded-[10px] px-3 py-1 h-auto text-sm leading-6 bg-[#893C27] text-white hover:bg-[#6c2f1f] border-0 outline-none"
276353 >
277354 Delete User
278355 </ Button >
@@ -343,6 +420,100 @@ export const UserManagement: React.FC = () => {
343420 </ Button >
344421 </ div >
345422 ) }
423+
424+ { /* Modular Popups for User Actions */ }
425+ { editingUser && (
426+ < ConfirmationModal
427+ isOpen = { true }
428+ position = { modalPosition }
429+ onClose = { ( ) => {
430+ setEditingUser ( null ) ;
431+ setModalPosition ( undefined ) ;
432+ } }
433+ onConfirm = { handleEditRole }
434+ title = "Edit Role"
435+ heading = { < > Update { editingUser . name || editingUser . username } role?</ > }
436+ description = {
437+ < >
438+ By pressing Confirm, you will update{ ' ' }
439+ < span className = "text-[#171717]" >
440+ { editingUser . name || editingUser . username }
441+ </ span > { ' ' }
442+ role to{ ' ' }
443+ < span className = "text-[#171717]" >
444+ { editingUser . dbUser ?. status === 'ADMIN' ? 'STANDARD' : 'ADMIN' }
445+ </ span >
446+ .
447+ </ >
448+ }
449+ confirmText = "Confirm"
450+ cancelText = "Cancel"
451+ confirmVariant = "success"
452+ isConfirming = { isUpdatingRole }
453+ />
454+ ) }
455+
456+ { verifyingUser && (
457+ < ConfirmationModal
458+ isOpen = { true }
459+ position = { modalPosition }
460+ onClose = { ( ) => {
461+ setVerifyingUser ( null ) ;
462+ setModalPosition ( undefined ) ;
463+ } }
464+ onConfirm = { handleVerify }
465+ title = "Approve User"
466+ heading = { < > Approve { verifyingUser . name || verifyingUser . username } ?</ > }
467+ description = {
468+ < >
469+ By pressing Confirm, you will approve{ ' ' }
470+ < span className = "text-[#171717]" >
471+ { verifyingUser . name || verifyingUser . username }
472+ </ span > { ' ' }
473+ as a user. This will allow them to access the platform.
474+ </ >
475+ }
476+ confirmText = "Confirm"
477+ cancelText = "Cancel"
478+ confirmVariant = "success"
479+ isConfirming = { isProcessing }
480+ />
481+ ) }
482+
483+ { denyingUser && (
484+ < ConfirmationModal
485+ isOpen = { true }
486+ position = { modalPosition }
487+ onClose = { ( ) => {
488+ setDenyingUser ( null ) ;
489+ setModalPosition ( undefined ) ;
490+ } }
491+ onConfirm = { handleDeny }
492+ title = "Delete User"
493+ heading = {
494+ < >
495+ { activeTab === 'pending' ? 'Deny' : 'Delete' } { ' ' }
496+ { denyingUser . name || denyingUser . username } ?
497+ </ >
498+ }
499+ description = {
500+ < >
501+ By pressing Confirm, you will{ ' ' }
502+ < span className = "text-[#171717]" >
503+ { activeTab === 'pending' ? 'deny and remove' : 'delete' }
504+ </ span > { ' ' }
505+ < span className = "text-[#171717]" >
506+ { denyingUser . name || denyingUser . username }
507+ </ span > { ' ' }
508+ from the system. This action cannot be undone.
509+ </ >
510+ }
511+ confirmText = "Confirm"
512+ cancelText = "Cancel"
513+ confirmVariant = "destructive"
514+ isConfirming = { isProcessing }
515+ />
516+ ) }
346517 </ div >
347518 ) ;
348519} ;
0 commit comments