Initial commit: Kinship Directory application

This commit is contained in:
2026-09-05 10:33:45 -05:00
commit 0eb4240f0a
51 changed files with 4682 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
"use client";
import { ShieldCheck, UserRound } from "lucide-react";
import { useState } from "react";
type AdminUser = { id: string; name: string; email: string; role: "admin" | "member"; isCurrent: boolean };
export function AdminUsers({ initialUsers }: { initialUsers: AdminUser[] }) {
const [users, setUsers] = useState(initialUsers); const [message, setMessage] = useState(""); const [pendingId, setPendingId] = useState("");
async function changeRole(user: AdminUser) {
const role = user.role === "admin" ? "member" : "admin";
setPendingId(user.id); setMessage("");
try {
const response = await fetch(`/api/admin/users/${user.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ role }) });
const result = await response.json();
if (!response.ok) { setMessage(result.error); return; }
setUsers(users.map((entry) => entry.id === user.id ? { ...entry, role } : entry));
setMessage(`${user.name} is now ${role === "admin" ? "an administrator" : "a member"}.`);
} catch {
setMessage("The role could not be changed. Try again.");
} finally {
setPendingId("");
}
}
return <section className="mt-8 rounded-[1.75rem] bg-white p-6 card-shadow sm:p-8"><div><h2 className="font-warm text-2xl font-bold text-[var(--brand)]">Registered accounts</h2><p className="mt-1 text-sm text-[#697570]">Administrators can edit every family and manage organization branding.</p></div><div className="mt-6 divide-y divide-black/6">{users.map((user) => <div key={user.id} className="flex flex-wrap items-center gap-4 py-4"><span className={`grid size-11 place-items-center rounded-xl ${user.role === "admin" ? "bg-[var(--brand)] text-white" : "bg-[var(--surface)] text-[var(--brand)]"}`}>{user.role === "admin" ? <ShieldCheck size={20}/> : <UserRound size={20}/>}</span><div className="mr-auto min-w-0"><p className="font-bold">{user.name}{user.isCurrent && <span className="ml-2 text-xs font-normal text-[#697570]">You</span>}</p><p className="truncate text-sm text-[#697570]">{user.email}</p></div><span className="rounded-full bg-[var(--surface)] px-3 py-1 text-xs font-bold uppercase tracking-wider text-[var(--brand)]">{user.role}</span>{!user.isCurrent && <button type="button" disabled={pendingId === user.id} onClick={() => changeRole(user)} className="btn-secondary text-sm">{pendingId === user.id ? "Saving..." : user.role === "admin" ? "Make member" : "Make admin"}</button>}</div>)}</div>{message && <p className="mt-4 rounded-xl bg-[var(--surface)] p-3 text-sm font-medium">{message}</p>}</section>;
}