Skip to content

Commit ea00bb8

Browse files
authored
Merge pull request #49 from Debugging-Disciples/copilot/fix-admin-edit-suspend-functions
[WIP] Fix missing functions for user role edit and suspend actions
2 parents 785c608 + 35a5bdc commit ea00bb8

2 files changed

Lines changed: 143 additions & 4 deletions

File tree

src/pages/Admin.tsx

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { Textarea } from '@/components/ui/textarea';
99
import { Switch } from '@/components/ui/switch';
1010
import { Badge } from '@/components/ui/badge';
1111
import { 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';
1214
import {
1315
Users,
1416
Lock,
@@ -21,8 +23,9 @@ import {
2123
Clock
2224
} from 'lucide-react';
2325
import { motion } from 'framer-motion';
24-
import { db } from '../utils/firebase';
26+
import { db, adminUpdateUser, adminSuspendUser } from '../utils/firebase';
2527
import { collection, getDocs, query, where, Timestamp } from 'firebase/firestore';
28+
import { toast } from 'sonner';
2629
import {
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
};

src/utils/firebase.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,4 +1236,33 @@ export const getAllUsernames = async (): Promise<UserProfile[]> => {
12361236
}
12371237
};
12381238

1239+
export const adminUpdateUser = async (userId: string, updates: { role?: 'admin' | 'member'; streakDays?: number }): Promise<boolean> => {
1240+
try {
1241+
const userRef = doc(db, 'users', userId);
1242+
await updateDoc(userRef, updates);
1243+
return true;
1244+
} catch (error) {
1245+
console.error('Error updating user:', error);
1246+
return false;
1247+
}
1248+
};
1249+
1250+
export const adminSuspendUser = async (userId: string): Promise<boolean> => {
1251+
try {
1252+
const userRef = doc(db, 'users', userId);
1253+
await updateDoc(userRef, {
1254+
meditations: [],
1255+
journal: [],
1256+
relapses: [],
1257+
streakDays: 0,
1258+
streakStartDate: Timestamp.now(),
1259+
lastCheckIn: null
1260+
});
1261+
return true;
1262+
} catch (error) {
1263+
console.error('Error suspending user:', error);
1264+
return false;
1265+
}
1266+
};
1267+
12391268
export default app;

0 commit comments

Comments
 (0)