Fix blank /students/new and /students/$id (missing Outlet)
students.tsx was the list AND the parent of students.new / students.$id but rendered no <Outlet/>, so child routes never showed. Make students.tsx an Outlet layout and move the list to students.index.tsx. Add an errorComponent to students.new as a safety net. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+22
-2
@@ -20,6 +20,7 @@ import { Route as AuthenticatedDashboardRouteImport } from './routes/_authentica
|
|||||||
import { Route as AuthenticatedCalendarRouteImport } from './routes/_authenticated/calendar'
|
import { Route as AuthenticatedCalendarRouteImport } from './routes/_authenticated/calendar'
|
||||||
import { Route as AuthenticatedAttendanceRouteImport } from './routes/_authenticated/attendance'
|
import { Route as AuthenticatedAttendanceRouteImport } from './routes/_authenticated/attendance'
|
||||||
import { Route as AuthenticatedAdminRouteImport } from './routes/_authenticated/admin'
|
import { Route as AuthenticatedAdminRouteImport } from './routes/_authenticated/admin'
|
||||||
|
import { Route as AuthenticatedStudentsIndexRouteImport } from './routes/_authenticated/students.index'
|
||||||
import { Route as AuthenticatedStudentsNewRouteImport } from './routes/_authenticated/students.new'
|
import { Route as AuthenticatedStudentsNewRouteImport } from './routes/_authenticated/students.new'
|
||||||
import { Route as AuthenticatedStudentsIdRouteImport } from './routes/_authenticated/students.$id'
|
import { Route as AuthenticatedStudentsIdRouteImport } from './routes/_authenticated/students.$id'
|
||||||
|
|
||||||
@@ -77,6 +78,12 @@ const AuthenticatedAdminRoute = AuthenticatedAdminRouteImport.update({
|
|||||||
path: '/admin',
|
path: '/admin',
|
||||||
getParentRoute: () => AuthenticatedRouteRoute,
|
getParentRoute: () => AuthenticatedRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AuthenticatedStudentsIndexRoute =
|
||||||
|
AuthenticatedStudentsIndexRouteImport.update({
|
||||||
|
id: '/',
|
||||||
|
path: '/',
|
||||||
|
getParentRoute: () => AuthenticatedStudentsRoute,
|
||||||
|
} as any)
|
||||||
const AuthenticatedStudentsNewRoute =
|
const AuthenticatedStudentsNewRoute =
|
||||||
AuthenticatedStudentsNewRouteImport.update({
|
AuthenticatedStudentsNewRouteImport.update({
|
||||||
id: '/new',
|
id: '/new',
|
||||||
@@ -102,6 +109,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/students': typeof AuthenticatedStudentsRouteWithChildren
|
'/students': typeof AuthenticatedStudentsRouteWithChildren
|
||||||
'/students/$id': typeof AuthenticatedStudentsIdRoute
|
'/students/$id': typeof AuthenticatedStudentsIdRoute
|
||||||
'/students/new': typeof AuthenticatedStudentsNewRoute
|
'/students/new': typeof AuthenticatedStudentsNewRoute
|
||||||
|
'/students/': typeof AuthenticatedStudentsIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
@@ -113,9 +121,9 @@ export interface FileRoutesByTo {
|
|||||||
'/forms': typeof AuthenticatedFormsRoute
|
'/forms': typeof AuthenticatedFormsRoute
|
||||||
'/ledger': typeof AuthenticatedLedgerRoute
|
'/ledger': typeof AuthenticatedLedgerRoute
|
||||||
'/messages': typeof AuthenticatedMessagesRoute
|
'/messages': typeof AuthenticatedMessagesRoute
|
||||||
'/students': typeof AuthenticatedStudentsRouteWithChildren
|
|
||||||
'/students/$id': typeof AuthenticatedStudentsIdRoute
|
'/students/$id': typeof AuthenticatedStudentsIdRoute
|
||||||
'/students/new': typeof AuthenticatedStudentsNewRoute
|
'/students/new': typeof AuthenticatedStudentsNewRoute
|
||||||
|
'/students': typeof AuthenticatedStudentsIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
@@ -132,6 +140,7 @@ export interface FileRoutesById {
|
|||||||
'/_authenticated/students': typeof AuthenticatedStudentsRouteWithChildren
|
'/_authenticated/students': typeof AuthenticatedStudentsRouteWithChildren
|
||||||
'/_authenticated/students/$id': typeof AuthenticatedStudentsIdRoute
|
'/_authenticated/students/$id': typeof AuthenticatedStudentsIdRoute
|
||||||
'/_authenticated/students/new': typeof AuthenticatedStudentsNewRoute
|
'/_authenticated/students/new': typeof AuthenticatedStudentsNewRoute
|
||||||
|
'/_authenticated/students/': typeof AuthenticatedStudentsIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
@@ -148,6 +157,7 @@ export interface FileRouteTypes {
|
|||||||
| '/students'
|
| '/students'
|
||||||
| '/students/$id'
|
| '/students/$id'
|
||||||
| '/students/new'
|
| '/students/new'
|
||||||
|
| '/students/'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
@@ -159,9 +169,9 @@ export interface FileRouteTypes {
|
|||||||
| '/forms'
|
| '/forms'
|
||||||
| '/ledger'
|
| '/ledger'
|
||||||
| '/messages'
|
| '/messages'
|
||||||
| '/students'
|
|
||||||
| '/students/$id'
|
| '/students/$id'
|
||||||
| '/students/new'
|
| '/students/new'
|
||||||
|
| '/students'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
@@ -177,6 +187,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticated/students'
|
| '/_authenticated/students'
|
||||||
| '/_authenticated/students/$id'
|
| '/_authenticated/students/$id'
|
||||||
| '/_authenticated/students/new'
|
| '/_authenticated/students/new'
|
||||||
|
| '/_authenticated/students/'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
@@ -264,6 +275,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticatedAdminRouteImport
|
preLoaderRoute: typeof AuthenticatedAdminRouteImport
|
||||||
parentRoute: typeof AuthenticatedRouteRoute
|
parentRoute: typeof AuthenticatedRouteRoute
|
||||||
}
|
}
|
||||||
|
'/_authenticated/students/': {
|
||||||
|
id: '/_authenticated/students/'
|
||||||
|
path: '/'
|
||||||
|
fullPath: '/students/'
|
||||||
|
preLoaderRoute: typeof AuthenticatedStudentsIndexRouteImport
|
||||||
|
parentRoute: typeof AuthenticatedStudentsRoute
|
||||||
|
}
|
||||||
'/_authenticated/students/new': {
|
'/_authenticated/students/new': {
|
||||||
id: '/_authenticated/students/new'
|
id: '/_authenticated/students/new'
|
||||||
path: '/new'
|
path: '/new'
|
||||||
@@ -284,11 +302,13 @@ declare module '@tanstack/react-router' {
|
|||||||
interface AuthenticatedStudentsRouteChildren {
|
interface AuthenticatedStudentsRouteChildren {
|
||||||
AuthenticatedStudentsIdRoute: typeof AuthenticatedStudentsIdRoute
|
AuthenticatedStudentsIdRoute: typeof AuthenticatedStudentsIdRoute
|
||||||
AuthenticatedStudentsNewRoute: typeof AuthenticatedStudentsNewRoute
|
AuthenticatedStudentsNewRoute: typeof AuthenticatedStudentsNewRoute
|
||||||
|
AuthenticatedStudentsIndexRoute: typeof AuthenticatedStudentsIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticatedStudentsRouteChildren: AuthenticatedStudentsRouteChildren = {
|
const AuthenticatedStudentsRouteChildren: AuthenticatedStudentsRouteChildren = {
|
||||||
AuthenticatedStudentsIdRoute: AuthenticatedStudentsIdRoute,
|
AuthenticatedStudentsIdRoute: AuthenticatedStudentsIdRoute,
|
||||||
AuthenticatedStudentsNewRoute: AuthenticatedStudentsNewRoute,
|
AuthenticatedStudentsNewRoute: AuthenticatedStudentsNewRoute,
|
||||||
|
AuthenticatedStudentsIndexRoute: AuthenticatedStudentsIndexRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticatedStudentsRouteWithChildren =
|
const AuthenticatedStudentsRouteWithChildren =
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { supabase } from "@/integrations/supabase/client";
|
||||||
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Plus, ChevronRight } from "lucide-react";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_authenticated/students/")({
|
||||||
|
head: () => ({ meta: [{ title: "Students — School Portal" }] }),
|
||||||
|
component: StudentsIndexPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
function StudentsIndexPage() {
|
||||||
|
const { roles } = useAuth();
|
||||||
|
const isAdmin = roles.includes("admin");
|
||||||
|
|
||||||
|
const { data: students } = useQuery({
|
||||||
|
queryKey: ["students"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from("students")
|
||||||
|
.select("id, first_name, last_name, dob, allergies, photo_release, class_id, classes(name)")
|
||||||
|
.order("last_name");
|
||||||
|
if (error) throw error;
|
||||||
|
return data ?? [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-8 max-w-6xl">
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-semibold">Students</h1>
|
||||||
|
<p className="text-muted-foreground text-sm">{students?.length ?? 0} students visible to you</p>
|
||||||
|
</div>
|
||||||
|
{isAdmin && (
|
||||||
|
<Link to="/students/new">
|
||||||
|
<Button><Plus className="h-4 w-4 mr-1" /> Add student</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-card border rounded-lg divide-y">
|
||||||
|
{(students ?? []).map((s) => (
|
||||||
|
<Link to="/students/$id" params={{ id: s.id }} key={s.id} className="flex items-center justify-between px-4 py-3 hover:bg-muted/50">
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">{s.last_name}, {s.first_name}</div>
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
{(s.classes as { name: string } | null)?.name ?? "No class"} · {s.allergies ? `Allergies: ${s.allergies}` : "No allergies"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
{students?.length === 0 && <div className="p-6 text-sm text-muted-foreground text-center">No students yet.</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,6 +15,12 @@ import { toast } from "sonner";
|
|||||||
export const Route = createFileRoute("/_authenticated/students/new")({
|
export const Route = createFileRoute("/_authenticated/students/new")({
|
||||||
head: () => ({ meta: [{ title: "New student — School Portal" }] }),
|
head: () => ({ meta: [{ title: "New student — School Portal" }] }),
|
||||||
component: NewStudentPage,
|
component: NewStudentPage,
|
||||||
|
errorComponent: ({ error }) => (
|
||||||
|
<div className="p-8 max-w-2xl">
|
||||||
|
<h1 className="text-lg font-semibold text-destructive">This page hit an error</h1>
|
||||||
|
<pre className="mt-3 whitespace-pre-wrap break-words text-xs bg-muted p-3 rounded border">{error?.message}{"\n\n"}{error?.stack}</pre>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Guardian = {
|
type Guardian = {
|
||||||
|
|||||||
@@ -1,59 +1,7 @@
|
|||||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { supabase } from "@/integrations/supabase/client";
|
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Plus, ChevronRight } from "lucide-react";
|
|
||||||
|
|
||||||
|
// Layout route for /students. The list lives in students.index.tsx; the child
|
||||||
|
// routes (students.new, students.$id) render here through the Outlet.
|
||||||
export const Route = createFileRoute("/_authenticated/students")({
|
export const Route = createFileRoute("/_authenticated/students")({
|
||||||
head: () => ({ meta: [{ title: "Students — School Portal" }] }),
|
component: () => <Outlet />,
|
||||||
component: StudentsPage,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function StudentsPage() {
|
|
||||||
const { roles } = useAuth();
|
|
||||||
const isAdmin = roles.includes("admin");
|
|
||||||
|
|
||||||
const { data: students } = useQuery({
|
|
||||||
queryKey: ["students"],
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data, error } = await supabase
|
|
||||||
.from("students")
|
|
||||||
.select("id, first_name, last_name, dob, allergies, photo_release, class_id, classes(name)")
|
|
||||||
.order("last_name");
|
|
||||||
if (error) throw error;
|
|
||||||
return data ?? [];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-8 max-w-6xl">
|
|
||||||
<div className="flex justify-between items-center mb-6">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-semibold">Students</h1>
|
|
||||||
<p className="text-muted-foreground text-sm">{students?.length ?? 0} students visible to you</p>
|
|
||||||
</div>
|
|
||||||
{isAdmin && (
|
|
||||||
<Link to="/students/new">
|
|
||||||
<Button><Plus className="h-4 w-4 mr-1" /> Add student</Button>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-card border rounded-lg divide-y">
|
|
||||||
{(students ?? []).map((s) => (
|
|
||||||
<Link to="/students/$id" params={{ id: s.id }} key={s.id} className="flex items-center justify-between px-4 py-3 hover:bg-muted/50">
|
|
||||||
<div>
|
|
||||||
<div className="font-medium">{s.last_name}, {s.first_name}</div>
|
|
||||||
<div className="text-xs text-muted-foreground">
|
|
||||||
{(s.classes as { name: string } | null)?.name ?? "No class"} · {s.allergies ? `Allergies: ${s.allergies}` : "No allergies"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
{students?.length === 0 && <div className="p-6 text-sm text-muted-foreground text-center">No students yet.</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user