tia/src/app/admin/children/page.tsx
Mannu fc0e75b5ad fix(admin): scope FamilyProvider out of admin routes, ensure cookies on admin fetches
Root causes:
- tia_admin_session is httpOnly so document.cookie could never read it → all
  client-side cookie checks always failed and redirected before any data fetched
- Sub-pages used localStorage.getItem("admin_token") which was never stored,
  and passed Authorization: Bearer null headers the server ignores

Fixes:
- FamilyProvider: use usePathname() hook instead of window.location.pathname
- admin/layout.tsx: rewrite as server component using verifyAdminSession()
  (new lib/admin-auth.ts helper that uses next/headers cookies()) → server-side
  redirect to /admin-login if session invalid; extract sidebar to AdminSidebar.tsx
- admin/page.tsx: remove broken document.cookie guard (layout handles auth now)
- admin-login/page.tsx: replace document.cookie check with GET /api/admin/auth call
- All 7 admin sub-pages: remove localStorage guard, remove Authorization: Bearer
  headers, add credentials: include to every fetch call

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:16:10 +05:30

104 lines
No EOL
3.4 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
interface Child {
id: string;
name: string;
birthDate: string;
familyId: string;
familyName: string;
age: string;
}
export default function AdminChildren() {
const [children, setChildren] = useState<Child[]>([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState("");
useEffect(() => {
fetchChildren();
}, []);
const fetchChildren = async () => {
try {
const res = await fetch("/api/admin/children", { credentials: "include" });
const data = await res.json();
setChildren(data.children || []);
} catch (err) {
console.error("Failed to fetch children:", err);
}
setLoading(false);
};
const filteredChildren = children.filter((c) =>
c.name.toLowerCase().includes(search.toLowerCase()) ||
c.familyName.toLowerCase().includes(search.toLowerCase())
);
const exportCSV = () => {
const headers = ["Name", "Birth Date", "Family", "Age"];
const rows = filteredChildren.map((c) => [c.name, c.birthDate, c.familyName, c.age]);
const csv = [headers, ...rows].map((row) => row.join(",")).join("\n");
const blob = new Blob([csv], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "children.csv";
a.click();
};
if (loading) {
return <div className="p-6 text-white">Loading...</div>;
}
return (
<div className="p-6 space-y-4">
<div className="flex justify-between items-center">
<div>
<h1 className="text-2xl font-bold">Children</h1>
<p className="text-gray-400">{children.length} total children</p>
</div>
<button onClick={exportCSV} className="px-4 py-2 bg-emerald-600 hover:bg-emerald-700 rounded-lg">
Export CSV
</button>
</div>
<input
type="text"
placeholder="Search children..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-white"
/>
<div className="bg-gray-800 rounded-xl overflow-hidden">
<table className="w-full">
<thead className="bg-gray-700">
<tr>
<th className="px-4 py-3 text-left text-sm font-medium">Child</th>
<th className="px-4 py-3 text-left text-sm font-medium">Birth Date</th>
<th className="px-4 py-3 text-left text-sm font-medium">Age</th>
<th className="px-4 py-3 text-left text-sm font-medium">Family</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700">
{filteredChildren.map((child) => (
<tr key={child.id} className="hover:bg-gray-750">
<td className="px-4 py-3 font-medium">{child.name}</td>
<td className="px-4 py-3 text-sm text-gray-400">
{child.birthDate?.slice(0, 10)}
</td>
<td className="px-4 py-3 text-sm text-gray-400">{child.age}</td>
<td className="px-4 py-3">{child.familyName}</td>
</tr>
))}
</tbody>
</table>
{filteredChildren.length === 0 && (
<div className="p-8 text-center text-gray-500">No children found</div>
)}
</div>
</div>
);
}