diff --git a/src/app/admin/families/page.tsx b/src/app/admin/families/page.tsx index 16a035b..80c93f5 100644 --- a/src/app/admin/families/page.tsx +++ b/src/app/admin/families/page.tsx @@ -47,6 +47,9 @@ export default function AdminFamilies() { headers: { Authorization: `Bearer ${localStorage.getItem("admin_token")}` }, }); const data = await res.json(); + if (data.error) { + console.error("API error:", data.error); + } setFamilies(data.families || []); } catch (err) { console.error("Failed to fetch families:", err); @@ -54,6 +57,24 @@ export default function AdminFamilies() { setLoading(false); }; + const handleCreateFamily = async () => { + const name = prompt("Family name:"); + if (!name) return; + try { + const res = await fetch("/api/admin/families", { + method: "POST", + headers: { + Authorization: `Bearer ${localStorage.getItem("admin_token")}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ name }), + }); + if (res.ok) fetchFamilies(); + } catch (err) { + console.error("Failed to create:", err); + } + }; + const handleAddMember = async () => { if (!addMember?.email || !addMember?.familyId) return; try { @@ -126,12 +147,20 @@ export default function AdminFamilies() {

Families

{families.length} total families

- +
+ + +
{/* Filters */} diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index 76f5ba3..eace868 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -10,13 +10,22 @@ interface User { familyId: string; familyName: string; createdAt: string; + memberId?: string; +} + +interface Family { + id: string; + name: string; } export default function AdminUsers() { const router = useRouter(); const [users, setUsers] = useState([]); + const [families, setFamilies] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); + const [showAdd, setShowAdd] = useState(false); + const [newUser, setNewUser] = useState({ email: "", name: "", familyId: "", role: "caregiver" }); useEffect(() => { const token = localStorage.getItem("admin_token"); @@ -25,6 +34,7 @@ export default function AdminUsers() { return; } fetchUsers(); + fetchFamilies(); }, []); const fetchUsers = async () => { @@ -40,9 +50,56 @@ export default function AdminUsers() { setLoading(false); }; + const fetchFamilies = async () => { + try { + const res = await fetch("/api/admin/families", { + headers: { Authorization: `Bearer ${localStorage.getItem("admin_token")}` }, + }); + const data = await res.json(); + setFamilies(data.families || []); + } catch (err) { + console.error("Failed to fetch families:", err); + } + }; + + const handleAddUser = async () => { + if (!newUser.email) return; + try { + const res = await fetch("/api/admin/users", { + method: "POST", + headers: { + Authorization: `Bearer ${localStorage.getItem("admin_token")}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(newUser), + }); + if (res.ok) { + fetchUsers(); + setShowAdd(false); + setNewUser({ email: "", name: "", familyId: "", role: "caregiver" }); + } + } catch (err) { + console.error("Failed to create user:", err); + } + }; + + const handleRemoveUser = async (userId: string, memberId?: string) => { + if (!confirm("Delete this user?")) return; + try { + const params = memberId ? `memberId=${memberId}` : `userId=${userId}`; + const res = await fetch(`/api/admin/users?${params}`, { + method: "DELETE", + headers: { Authorization: `Bearer ${localStorage.getItem("admin_token")}` }, + }); + if (res.ok) fetchUsers(); + } catch (err) { + console.error("Failed to delete:", err); + } + }; + const filteredUsers = users.filter((u) => u.email.toLowerCase().includes(search.toLowerCase()) || - u.name.toLowerCase().includes(search.toLowerCase()) + (u.name || "").toLowerCase().includes(search.toLowerCase()) ); const exportCSV = () => { @@ -68,11 +125,41 @@ export default function AdminUsers() {

Users

{users.length} total users

- + {showAdd && ( +
+ setNewUser({ ...newUser, email: e.target.value })} + className="flex-1 bg-gray-700 border border-gray-600 rounded px-3 py-2 text-white min-w-[200px]" + /> + setNewUser({ ...newUser, name: e.target.value })} + className="bg-gray-700 border border-gray-600 rounded px-3 py-2 text-white" + /> + + +
+ )} + User Family Joined + Actions @@ -97,10 +185,18 @@ export default function AdminUsers() {
{user.name || user.email}
{user.email}
- {user.familyName} + {user.familyName || "-"} {user.createdAt?.slice(0, 10)} + + + ))} diff --git a/src/app/api/admin/families/route.ts b/src/app/api/admin/families/route.ts index df662a2..85147a5 100644 --- a/src/app/api/admin/families/route.ts +++ b/src/app/api/admin/families/route.ts @@ -100,7 +100,7 @@ export async function PATCH(request: Request) { } } -// Add member to family +// Add member to family or create family export async function POST(request: Request) { try { const authHeader = request.headers.get("authorization"); @@ -109,8 +109,19 @@ export async function POST(request: Request) { } const body = await request.json(); - const { familyId, email, role, displayName } = body; + const { familyId, email, role, displayName, name } = body; + // Create new family + if (name && !familyId) { + const newFamilyId = crypto.randomUUID(); + await sql` + INSERT INTO families (id, name, tier, max_children, max_members, created_at, updated_at) + VALUES (${newFamilyId}, ${name}, 'free', 1, 2, NOW(), NOW()) + `; + return NextResponse.json({ success: true, familyId: newFamilyId }); + } + + // Add member to existing family if (!familyId || !email) { return NextResponse.json({ error: "familyId and email required" }, { status: 400 }); } diff --git a/src/app/api/admin/users/route.ts b/src/app/api/admin/users/route.ts index 3df4376..58174ae 100644 --- a/src/app/api/admin/users/route.ts +++ b/src/app/api/admin/users/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from "next/server"; import { sql } from "@/db"; +// GET all users export async function GET(request: Request) { try { const authHeader = request.headers.get("authorization"); @@ -8,13 +9,14 @@ export async function GET(request: Request) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } - // Get all users via family_members table with family info + // Get all users const users = await sql` SELECT u.id, u.email, u.name, fm.family_id, + fm.id as member_id, f.name as family_name, u.created_at FROM users u @@ -29,6 +31,7 @@ export async function GET(request: Request) { email: u.email, name: u.name, familyId: u.family_id, + memberId: u.member_id, familyName: u.family_name, createdAt: u.created_at ? new Date(u.created_at).toISOString() : null, })), @@ -37,4 +40,74 @@ export async function GET(request: Request) { console.error("Admin users error:", error); return NextResponse.json({ error: String(error) }, { status: 500 }); } +} + +// Create user or add to family +export async function POST(request: Request) { + try { + const authHeader = request.headers.get("authorization"); + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const body = await request.json(); + const { email, familyId, role, name } = body; + + if (!email) { + return NextResponse.json({ error: "email required" }, { status: 400 }); + } + + // Check if user exists + let existing = await sql`SELECT id FROM users WHERE email = ${email}`; + let userId = existing?.[0]?.id; + + if (!userId) { + userId = crypto.randomUUID(); + await sql` + INSERT INTO users (id, email, name, created_at, updated_at) + VALUES (${userId}, ${email}, ${name || email}, NOW(), NOW()) + `; + } + + // Add to family if familyId provided + if (familyId) { + await sql` + INSERT INTO family_members (id, family_id, user_id, role, display_name, created_at) + VALUES (${crypto.randomUUID()}, ${familyId}, ${userId}, ${role || 'caregiver'}, ${name || email}, NOW()) + ON CONFLICT DO NOTHING + `; + } + + return NextResponse.json({ success: true, userId }); + } catch (error) { + console.error("Admin users error:", error); + return NextResponse.json({ error: String(error) }, { status: 500 }); + } +} + +// Remove user from family +export async function DELETE(request: Request) { + try { + const authHeader = request.headers.get("authorization"); + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const memberId = searchParams.get("memberId"); + const userId = searchParams.get("userId"); + + if (memberId) { + await sql`DELETE FROM family_members WHERE id = ${memberId}`; + } else if (userId) { + // Delete user entirely (careful!) + await sql`DELETE FROM family_members WHERE user_id = ${userId}`; + await sql`DELETE FROM users WHERE id = ${userId}`; + } + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Admin delete error:", error); + return NextResponse.json({ error: String(error) }, { status: 500 }); + } } \ No newline at end of file