'use client'; import React, { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetFooter, } from '@/components/ui/sheet'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogTitle } from '@/components/ui/alert-dialog'; import { toast } from 'sonner'; import { Trash2, Edit, Upload, Plus } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; type AllowedStudent = { id: string; email: string; levels: string; active: boolean; createdAt: string; updatedAt: string; }; export default function AllowedStudentsClient() { const [students, setStudents] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isAddDialogOpen, setIsAddDialogOpen] = useState(false); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); const [isImportLoading, setIsImportLoading] = useState(false); const [editingStudent, setEditingStudent] = useState(null); const [deleteId, setDeleteId] = useState(null); const [newEmail, setNewEmail] = useState(''); const [newLevels, setNewLevels] = useState(''); // Fetch students on mount useEffect(() => { fetchStudents(); }, []); const fetchStudents = async () => { setIsLoading(true); try { const res = await fetch('/api/admin/allowed-students'); if (res.ok) { const data = await res.json(); setStudents(data); } else { toast.error('Failed to fetch students'); } } catch (err) { console.error('Failed to fetch students:', err); toast.error('Error fetching students'); } finally { setIsLoading(false); } }; const handleAddStudent = async () => { if (!newEmail.trim() || !newLevels.trim()) { toast.error('Email and course codes required'); return; } try { const res = await fetch('/api/admin/allowed-students', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: newEmail.trim(), levels: newLevels.trim(), }), }); if (res.status === 201) { toast.success('Student added'); setNewEmail(''); setNewLevels(''); setIsAddDialogOpen(false); fetchStudents(); } else if (res.status === 409) { toast.error('Email already registered'); } else { const data = await res.json(); toast.error(data.error || 'Failed to add student'); } } catch (err) { console.error('Failed to add student:', err); toast.error('Error adding student'); } }; const handleEditStudent = async () => { if (!editingStudent) return; try { const res = await fetch('/api/admin/allowed-students', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: editingStudent.id, email: editingStudent.email, levels: editingStudent.levels, active: editingStudent.active, }), }); if (res.ok) { toast.success('Student updated'); setIsEditDialogOpen(false); setEditingStudent(null); fetchStudents(); } else { const data = await res.json(); toast.error(data.error || 'Failed to update student'); } } catch (err) { console.error('Failed to update student:', err); toast.error('Error updating student'); } }; const handleDeleteStudent = async () => { if (!deleteId) return; try { const res = await fetch(`/api/admin/allowed-students?id=${deleteId}`, { method: 'DELETE', }); if (res.ok) { toast.success('Student removed'); setIsDeleteAlertOpen(false); setDeleteId(null); fetchStudents(); } else { toast.error('Failed to delete student'); } } catch (err) { console.error('Failed to delete student:', err); toast.error('Error deleting student'); } }; const handleImportCSV = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setIsImportLoading(true); try { const formData = new FormData(); formData.append('file', file); const res = await fetch('/api/admin/allowed-students/import', { method: 'POST', body: formData, }); if (res.ok) { const result = await res.json(); toast.success(`Imported: ${result.imported}, Updated: ${result.updated}`); if (result.errors.length > 0) { toast.warning(`${result.errors.length} errors during import`); } fetchStudents(); } else { const data = await res.json(); toast.error(data.error || 'Failed to import students'); } } catch (err) { console.error('Failed to import CSV:', err); toast.error('Error importing students'); } finally { setIsImportLoading(false); } }; if (isLoading) { return
Loading allowed students...
; } return (
Allowed Students ({students.length})
Email Course Codes Status Added Actions {students.length === 0 ? ( No students added yet ) : ( students.map((student) => ( {student.email}
{student.levels.split(',').map((code) => ( {code.trim()} ))}
{student.active ? 'Active' : 'Inactive'} {formatDistanceToNow(new Date(student.createdAt), { addSuffix: true })}
)) )}
{/* Add Student Sheet */} Add Student
setNewEmail(e.target.value)} />
setNewLevels(e.target.value)} />

Comma-separated course codes

{/* Edit Student Sheet */} Edit Student {editingStudent && (
setEditingStudent({ ...editingStudent, email: e.target.value }) } />
setEditingStudent({ ...editingStudent, levels: e.target.value }) } />
)}
{/* Delete Confirmation */} Remove Student Are you sure? The student will be prevented from logging in, but their existing enrollments will remain.
Cancel Remove
); }