@@ -9,6 +9,8 @@ import { Textarea } from '@/components/ui/textarea';
99import { Switch } from '@/components/ui/switch' ;
1010import { Badge } from '@/components/ui/badge' ;
1111import { CustomBadge } from '@/components/ui/custom-badge' ;
12+ import { Dialog , DialogContent , DialogDescription , DialogFooter , DialogHeader , DialogTitle } from '@/components/ui/dialog' ;
13+ import { AlertDialog , AlertDialogAction , AlertDialogCancel , AlertDialogContent , AlertDialogDescription , AlertDialogFooter , AlertDialogHeader , AlertDialogTitle } from '@/components/ui/alert-dialog' ;
1214import {
1315 Users ,
1416 Lock ,
@@ -21,8 +23,9 @@ import {
2123 Clock
2224} from 'lucide-react' ;
2325import { motion } from 'framer-motion' ;
24- import { db } from '../utils/firebase' ;
26+ import { db , adminUpdateUser , adminSuspendUser } from '../utils/firebase' ;
2527import { collection , getDocs , query , where , Timestamp } from 'firebase/firestore' ;
28+ import { toast } from 'sonner' ;
2629import {
2730 BarChart ,
2831 Bar ,
@@ -96,6 +99,12 @@ const Admin: React.FC = () => {
9699 const [ users , setUsers ] = useState < User [ ] > ( [ ] ) ;
97100 const [ isLoading , setIsLoading ] = useState ( true ) ;
98101 const [ checkInTimes , setCheckInTimes ] = useState < CheckInTime [ ] > ( [ ] ) ;
102+ const [ editUser , setEditUser ] = useState < User | null > ( null ) ;
103+ const [ editRole , setEditRole ] = useState < string > ( 'member' ) ;
104+ const [ editStreak , setEditStreak ] = useState < number > ( 0 ) ;
105+ const [ isSaving , setIsSaving ] = useState ( false ) ;
106+ const [ suspendUser , setSuspendUser ] = useState < User | null > ( null ) ;
107+ const [ isSuspending , setIsSuspending ] = useState ( false ) ;
99108 const isMobile = useIsMobile ( ) ;
100109
101110 useEffect ( ( ) => {
@@ -204,7 +213,48 @@ const Admin: React.FC = () => {
204213
205214 return colors [ day as keyof typeof colors ] || '#a1a1aa' ;
206215 } ;
207-
216+
217+ const handleEditOpen = ( user : User ) => {
218+ setEditUser ( user ) ;
219+ setEditRole ( user . role || 'member' ) ;
220+ setEditStreak ( user . streakDays || 0 ) ;
221+ } ;
222+
223+ const handleEditSave = async ( ) => {
224+ if ( ! editUser ) return ;
225+ setIsSaving ( true ) ;
226+ const success = await adminUpdateUser ( editUser . id , {
227+ role : editRole as 'admin' | 'member' ,
228+ streakDays : editStreak
229+ } ) ;
230+ if ( success ) {
231+ setUsers ( prev => prev . map ( u =>
232+ u . id === editUser . id ? { ...u , role : editRole , streakDays : editStreak } : u
233+ ) ) ;
234+ toast . success ( 'User updated successfully' ) ;
235+ } else {
236+ toast . error ( 'Failed to update user' ) ;
237+ }
238+ setIsSaving ( false ) ;
239+ setEditUser ( null ) ;
240+ } ;
241+
242+ const handleSuspendConfirm = async ( ) => {
243+ if ( ! suspendUser ) return ;
244+ setIsSuspending ( true ) ;
245+ const success = await adminSuspendUser ( suspendUser . id ) ;
246+ if ( success ) {
247+ setUsers ( prev => prev . map ( u =>
248+ u . id === suspendUser . id ? { ...u , streakDays : 0 , status : 'inactive' } : u
249+ ) ) ;
250+ toast . success ( 'User suspended successfully' ) ;
251+ } else {
252+ toast . error ( 'Failed to suspend user' ) ;
253+ }
254+ setIsSuspending ( false ) ;
255+ setSuspendUser ( null ) ;
256+ } ;
257+
208258 return (
209259 < motion . div
210260 className = "container max-w-6xl py-8 pb-16"
@@ -324,8 +374,8 @@ const Admin: React.FC = () => {
324374 < td className = "py-3 px-4" > { user . streakDays } days</ td >
325375 < td className = "py-3 px-4" >
326376 < div className = "flex gap-2" >
327- < Button variant = "ghost" size = "sm" > Edit</ Button >
328- < Button variant = "ghost" size = "sm" className = "text-destructive" > Suspend</ Button >
377+ < Button variant = "ghost" size = "sm" onClick = { ( ) => handleEditOpen ( user ) } > Edit</ Button >
378+ < Button variant = "ghost" size = "sm" className = "text-destructive" onClick = { ( ) => setSuspendUser ( user ) } > Suspend</ Button >
329379 </ div >
330380 </ td >
331381 </ tr >
@@ -783,6 +833,66 @@ const Admin: React.FC = () => {
783833 </ motion . div >
784834 </ TabsContent >
785835 </ Tabs >
836+
837+ { /* Edit User Dialog */ }
838+ < Dialog open = { ! ! editUser } onOpenChange = { ( open ) => { if ( ! open ) setEditUser ( null ) ; } } >
839+ < DialogContent >
840+ < DialogHeader >
841+ < DialogTitle > Edit User</ DialogTitle >
842+ < DialogDescription >
843+ Update the role or streak days for { editUser ?. name } .
844+ </ DialogDescription >
845+ </ DialogHeader >
846+ < div className = "space-y-4 py-2" >
847+ < div className = "space-y-2" >
848+ < Label htmlFor = "edit-role" > Role</ Label >
849+ < Select value = { editRole } onValueChange = { setEditRole } >
850+ < SelectTrigger id = "edit-role" >
851+ < SelectValue />
852+ </ SelectTrigger >
853+ < SelectContent >
854+ < SelectItem value = "member" > Member</ SelectItem >
855+ < SelectItem value = "admin" > Admin</ SelectItem >
856+ </ SelectContent >
857+ </ Select >
858+ </ div >
859+ < div className = "space-y-2" >
860+ < Label htmlFor = "edit-streak" > Streak Days</ Label >
861+ < Input
862+ id = "edit-streak"
863+ type = "number"
864+ min = { 0 }
865+ value = { editStreak }
866+ onChange = { e => setEditStreak ( Number ( e . target . value ) ) }
867+ />
868+ </ div >
869+ </ div >
870+ < DialogFooter >
871+ < Button variant = "outline" onClick = { ( ) => setEditUser ( null ) } disabled = { isSaving } > Cancel</ Button >
872+ < Button onClick = { handleEditSave } disabled = { isSaving } >
873+ { isSaving ? 'Saving...' : 'Save' }
874+ </ Button >
875+ </ DialogFooter >
876+ </ DialogContent >
877+ </ Dialog >
878+
879+ { /* Suspend User Confirmation */ }
880+ < AlertDialog open = { ! ! suspendUser } onOpenChange = { ( open ) => { if ( ! open ) setSuspendUser ( null ) ; } } >
881+ < AlertDialogContent >
882+ < AlertDialogHeader >
883+ < AlertDialogTitle > Suspend User</ AlertDialogTitle >
884+ < AlertDialogDescription >
885+ This will delete all entries (meditations, journal, relapses) and reset the streak for { suspendUser ?. name } . This action cannot be undone.
886+ </ AlertDialogDescription >
887+ </ AlertDialogHeader >
888+ < AlertDialogFooter >
889+ < AlertDialogCancel disabled = { isSuspending } > Cancel</ AlertDialogCancel >
890+ < AlertDialogAction onClick = { handleSuspendConfirm } disabled = { isSuspending } className = "bg-destructive text-destructive-foreground hover:bg-destructive/90" >
891+ { isSuspending ? 'Suspending...' : 'Suspend' }
892+ </ AlertDialogAction >
893+ </ AlertDialogFooter >
894+ </ AlertDialogContent >
895+ </ AlertDialog >
786896 </ motion . div >
787897 ) ;
788898} ;
0 commit comments