diff --git a/src/routes/_authenticated/admin.tsx b/src/routes/_authenticated/admin.tsx
deleted file mode 100644
index f46223f..0000000
--- a/src/routes/_authenticated/admin.tsx
+++ /dev/null
@@ -1,284 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
-import { supabase } from "@/integrations/supabase/client";
-import { useAuth } from "@/hooks/use-auth";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
-import { Plus, Trash2, Lock, UserPlus, Copy } from "lucide-react";
-import { useState } from "react";
-import { toast } from "sonner";
-import { createUserFn } from "@/lib/user-admin.functions";
-import { genTempPassword } from "@/lib/temp-password";
-
-type Role = "admin" | "teacher" | "parent";
-
-export const Route = createFileRoute("/_authenticated/admin")({
- head: () => ({ meta: [{ title: "Admin — School Portal" }] }),
- component: AdminPage,
-});
-
-function AdminPage() {
- const { roles } = useAuth();
- if (!roles.includes("admin")) {
- return
Admins only
;
- }
- return (
-
-
Admin
-
Manage users, roles, classes, and parent-student links.
-
-
- Users & roles
- Classes
- Parent ↔ student
-
-
-
-
-
-
- );
-}
-
-function UsersTab() {
- const qc = useQueryClient();
- const { data } = useQuery({
- queryKey: ["admin-users"],
- queryFn: async () => {
- const { data: profiles } = await supabase.from("profiles").select("id, full_name, email");
- const { data: roles } = await supabase.from("user_roles").select("user_id, role");
- const rolesByUser: Record = {};
- (roles ?? []).forEach((r) => { rolesByUser[r.user_id] = [...(rolesByUser[r.user_id] ?? []), r.role as Role]; });
- return (profiles ?? []).map((p) => ({ ...p, roles: rolesByUser[p.id] ?? [] }));
- },
- });
-
- const setRole = useMutation({
- mutationFn: async ({ userId, role, on }: { userId: string; role: Role; on: boolean }) => {
- if (on) {
- const { error } = await supabase.from("user_roles").insert({ user_id: userId, role });
- if (error && !error.message.includes("duplicate")) throw error;
- } else {
- const { error } = await supabase.from("user_roles").delete().eq("user_id", userId).eq("role", role);
- if (error) throw error;
- }
- },
- onSuccess: () => qc.invalidateQueries({ queryKey: ["admin-users"] }),
- onError: (e: Error) => toast.error(e.message),
- });
-
- const [nu, setNu] = useState({ fullName: "", email: "", role: "teacher" as Role });
- const [created, setCreated] = useState<{ email: string; password: string } | null>(null);
- const addUser = useMutation({
- mutationFn: async () => {
- const password = genTempPassword();
- const res = await createUserFn({ data: { fullName: nu.fullName, email: nu.email, password, role: nu.role } });
- return { email: res.email, password };
- },
- onSuccess: (r) => {
- setCreated(r);
- setNu({ fullName: "", email: "", role: "teacher" });
- qc.invalidateQueries({ queryKey: ["admin-users"] });
- qc.invalidateQueries({ queryKey: ["teachers"] });
- toast.success(`Created ${r.email}`);
- },
- onError: (e: Error) => toast.error(e.message),
- });
-
- return (
-
-
-
Add a user
-
- setNu({ ...nu, fullName: e.target.value })} />
- setNu({ ...nu, email: e.target.value })} />
-
-
-
-
Creates a login with a temporary password (shown once). They can change it after signing in. No email is sent.
- {created && (
-
-
Account created — share these with {created.email}:
-
- Email: {created.email}
- Password: {created.password}
-
-
-
- )}
-
-
-
-
- | Name | Email | Roles |
-
- {(data ?? []).map((u) => (
-
- | {u.full_name || "—"} |
- {u.email} |
-
-
- {(["admin", "teacher", "parent"] as Role[]).map((r) => {
- const on = u.roles.includes(r);
- return ;
- })}
-
- |
-
- ))}
-
-
-
-
- );
-}
-
-function ClassesTab() {
- const qc = useQueryClient();
- const [name, setName] = useState("");
- const [teacherId, setTeacherId] = useState("");
-
- const { data: classes } = useQuery({
- queryKey: ["admin-classes"],
- queryFn: async () => {
- const { data } = await supabase.from("classes").select("*").order("name");
- const tids = Array.from(new Set((data ?? []).map((c) => c.teacher_id).filter(Boolean))) as string[];
- const { data: profs } = tids.length ? await supabase.from("profiles").select("id, full_name").in("id", tids) : { data: [] };
- const map = Object.fromEntries((profs ?? []).map((p) => [p.id, p.full_name]));
- return (data ?? []).map((c) => ({ ...c, teacher_name: c.teacher_id ? map[c.teacher_id] : null }));
- },
- });
- const { data: teachers } = useQuery({
- queryKey: ["teachers"],
- queryFn: async () => {
- const { data: r } = await supabase.from("user_roles").select("user_id").eq("role", "teacher");
- const ids = (r ?? []).map((x) => x.user_id);
- if (ids.length === 0) return [];
- const { data } = await supabase.from("profiles").select("id, full_name, email").in("id", ids);
- return data ?? [];
- },
- });
-
- const add = useMutation({
- mutationFn: async () => {
- const { error } = await supabase.from("classes").insert({ name, teacher_id: teacherId || null });
- if (error) throw error;
- },
- onSuccess: () => { setName(""); setTeacherId(""); qc.invalidateQueries({ queryKey: ["admin-classes"] }); toast.success("Class added"); },
- onError: (e: Error) => toast.error(e.message),
- });
- const del = useMutation({
- mutationFn: async (id: string) => { const { error } = await supabase.from("classes").delete().eq("id", id); if (error) throw error; },
- onSuccess: () => qc.invalidateQueries({ queryKey: ["admin-classes"] }),
- });
-
- return (
-
-
- {(classes ?? []).map((c) => (
-
-
-
{c.name}
-
Teacher: {c.teacher_name ?? "unassigned"}
-
-
-
- ))}
- {classes?.length === 0 &&
No classes yet.
}
-
-
-
New class
-
-
setName(e.target.value)} />
-
-
-
-
-
- );
-}
-
-function LinksTab() {
- const qc = useQueryClient();
- const [parentId, setParentId] = useState("");
- const [studentId, setStudentId] = useState("");
-
- const { data: links } = useQuery({
- queryKey: ["admin-links"],
- queryFn: async () => {
- const { data } = await supabase.from("parent_students").select("id, parent_id, students(first_name, last_name)");
- const ids = (data ?? []).map((d) => d.parent_id);
- const { data: profs } = ids.length ? await supabase.from("profiles").select("id, full_name, email").in("id", ids) : { data: [] };
- const map = Object.fromEntries((profs ?? []).map((p) => [p.id, p]));
- return (data ?? []).map((d) => ({ ...d, profile: map[d.parent_id] }));
- },
- });
- const { data: parents } = useQuery({
- queryKey: ["all-parents"],
- queryFn: async () => {
- const { data: r } = await supabase.from("user_roles").select("user_id").eq("role", "parent");
- const ids = (r ?? []).map((x) => x.user_id);
- if (ids.length === 0) return [];
- const { data } = await supabase.from("profiles").select("id, full_name, email").in("id", ids);
- return data ?? [];
- },
- });
- const { data: students } = useQuery({
- queryKey: ["all-students-link"],
- queryFn: async () => (await supabase.from("students").select("id, first_name, last_name").order("last_name")).data ?? [],
- });
-
- const add = useMutation({
- mutationFn: async () => {
- const { error } = await supabase.from("parent_students").insert({ parent_id: parentId, student_id: studentId });
- if (error) throw error;
- },
- onSuccess: () => { setParentId(""); setStudentId(""); qc.invalidateQueries({ queryKey: ["admin-links"] }); toast.success("Linked"); },
- onError: (e: Error) => toast.error(e.message),
- });
- const del = useMutation({
- mutationFn: async (id: string) => { const { error } = await supabase.from("parent_students").delete().eq("id", id); if (error) throw error; },
- onSuccess: () => qc.invalidateQueries({ queryKey: ["admin-links"] }),
- });
-
- return (
-
-
- {(links ?? []).map((l) => {
- const p = l.profile as { full_name?: string; email?: string } | undefined;
- const s = l.students as { first_name: string; last_name: string } | null;
- return (
-
- {p?.full_name || p?.email} → {s?.first_name} {s?.last_name}
-
-
- );
- })}
- {links?.length === 0 &&
No links yet.
}
-
-
-
Link parent to student
-
-
-
-
-
-
-
- );
-}