diff --git a/frontend/src/app/senators/[id]/loading.tsx b/frontend/src/app/senators/[id]/loading.tsx
new file mode 100644
index 0000000..1a217c7
--- /dev/null
+++ b/frontend/src/app/senators/[id]/loading.tsx
@@ -0,0 +1,5 @@
+import LoadingSpinner from "@/components/ui/LoadingSpinner";
+
+export default function SenatorDetailLoading() {
+ return ;
+}
diff --git a/frontend/src/app/senators/[id]/not-found.tsx b/frontend/src/app/senators/[id]/not-found.tsx
new file mode 100644
index 0000000..d1480a3
--- /dev/null
+++ b/frontend/src/app/senators/[id]/not-found.tsx
@@ -0,0 +1,17 @@
+import Link from "next/link";
+import { Button } from "@/components/ui/button";
+
+export default function SenatorNotFound() {
+ return (
+
+
Senator Not Found
+
+ We couldn't find the senator you're looking for. They may no
+ longer be active or the ID is incorrect.
+
+
+
+ );
+}
diff --git a/frontend/src/app/senators/[id]/page.tsx b/frontend/src/app/senators/[id]/page.tsx
new file mode 100644
index 0000000..a37e0ae
--- /dev/null
+++ b/frontend/src/app/senators/[id]/page.tsx
@@ -0,0 +1,131 @@
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { ApiError, getSenatorById } from "@/lib/api";
+import Link from "next/link";
+import { notFound } from "next/navigation";
+
+export const dynamic = "force-dynamic";
+
+function initials(name: string) {
+ return name
+ .split(" ")
+ .map((s) => s[0]?.toUpperCase() ?? "")
+ .slice(0, 2)
+ .join("");
+}
+
+export default async function SenatorDetailPage({
+ params,
+}: {
+ params: Promise<{ id: string }>;
+}) {
+ const resolvedParams = await params;
+ let senator;
+
+ try {
+ senator = await getSenatorById(resolvedParams.id);
+ } catch (error) {
+ if (error instanceof ApiError && error.status === 404) {
+ notFound();
+ }
+ throw error;
+ }
+
+ const fullName = `${senator.first_name} ${senator.last_name}`;
+
+ return (
+
+
+ {senator.headshot_url ? (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ ) : (
+
+ {initials(fullName)}
+
+ )}
+
+
+
+
+
+ Details
+
+
+
+ District:{" "}
+ {senator.district_id}
+
+
+ Session:{" "}
+ {senator.session_number}
+
+
+ Status:{" "}
+ {senator.is_active ? "Active" : "Inactive"}
+
+
+
+
+
+
Committee Assignments
+
+
+
+
+ Committee
+ Role
+
+
+
+ {senator.committees.map((c) => (
+
+
+
+ {c.committee_name}
+
+
+ {c.role}
+
+ ))}
+ {senator.committees.length === 0 && (
+
+
+ No committee assignments.
+
+
+ )}
+
+
+
+
+
+ );
+}