| )[role] ?? role;
+}
+
+function initials(name?: string | null, email?: string | null): string {
+ const source = name?.trim() || email?.trim() || "?";
+ return (
+ source
+ .split(/\s+/)
+ .map((w) => w[0])
+ .filter(Boolean)
+ .join("")
+ .slice(0, 2)
+ .toUpperCase() || "?"
+ );
+}
+
+export function CareTeamPanel() {
+ const { data: session } = authClient.useSession();
+ const [members, setMembers] = useState([]);
+ const [invites, setInvites] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [notice, setNotice] = useState(null);
+
+ const [email, setEmail] = useState("");
+ const [role, setRole] = useState<(typeof INVITE_ROLES)[number]>("member");
+ const [inviting, setInviting] = useState(false);
+
+ const load = useCallback(async () => {
+ const { data, error: err } =
+ await authClient.organization.getFullOrganization();
+ if (err || !data) {
+ setError(err?.message ?? "Could not load the care team.");
+ setLoading(false);
+ return;
+ }
+ setMembers((data.members ?? []) as Member[]);
+ setInvites(
+ ((data.invitations ?? []) as Invite[]).filter(
+ (i) => i.status === "pending"
+ )
+ );
+ setError(null);
+ setLoading(false);
+ }, []);
+
+ useEffect(() => {
+ void load();
+ }, [load]);
+
+ const myRole = members.find((m) => m.userId === session?.user?.id)?.role;
+ const canManage = myRole === "owner" || myRole === "admin";
+
+ const invite = async (event: FormEvent) => {
+ event.preventDefault();
+ if (!email.trim() || inviting) return;
+ setInviting(true);
+ setNotice(null);
+ setError(null);
+ const { error: err } = await authClient.organization.inviteMember({
+ email: email.trim(),
+ role,
+ });
+ setInviting(false);
+ if (err) {
+ setError(err.message ?? "Could not send the invitation.");
+ return;
+ }
+ setEmail("");
+ setNotice(`Invitation sent to ${email.trim()}.`);
+ void load();
+ };
+
+ const removeMember = async (memberId: string) => {
+ await authClient.organization.removeMember({ memberIdOrEmail: memberId });
+ void load();
+ };
+
+ const cancelInvite = async (invitationId: string) => {
+ await authClient.organization.cancelInvitation({ invitationId });
+ void load();
+ };
+
+ return (
+
+ {error && (
+
+ {error}
+
+ )}
+ {notice && (
+
+ {notice}
+
+ )}
+
+ {canManage && (
+
+
+
+ )}
+
+
+ {loading ? (
+
+ Loading care team…
+
+ ) : (
+ members.map((m) => {
+ const isSelf = m.userId === session?.user?.id;
+ return (
+
+
+
+ {initials(m.user?.name, m.user?.email)}
+
+
+
+
+ {m.user?.name || m.user?.email || m.userId}
+ {isSelf && (
+
+ (you)
+
+ )}
+
+ {m.user?.email && (
+
+ {m.user.email}
+
+ )}
+
+
+ {roleLabel(m.role)}
+
+ {canManage && !isSelf && m.role !== "owner" && (
+
+ )}
+
+ );
+ })
+ )}
+
+
+ {invites.length > 0 && (
+
+
+ Pending invitations
+
+ {invites.map((inv) => (
+
+
+
+ {roleLabel(inv.role)}
+
+ {canManage && (
+
+ )}
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/frontend/components/settings/settings-view.tsx b/frontend/components/settings/settings-view.tsx
index f1f076a..dc4525a 100644
--- a/frontend/components/settings/settings-view.tsx
+++ b/frontend/components/settings/settings-view.tsx
@@ -8,6 +8,7 @@ import {
SettingsSection,
} from "@/components/settings/settings-parts";
import { SigningPanel } from "@/components/settings/settings-billing";
+import { CareTeamPanel } from "@/components/settings/settings-care-team";
import { ProfilePanel } from "@/components/settings/settings-preferences";
const TABS = [
@@ -71,12 +72,7 @@ export function SettingsView() {
/>
)}
{tab === "Signing" && }
- {tab === "Care team" && (
-
- )}
+ {tab === "Care team" && }
{tab === "Developers" && (
+
diff --git a/frontend/components/sidebar-02/nav-user.tsx b/frontend/components/sidebar-02/nav-user.tsx
index a2103db..bba6546 100644
--- a/frontend/components/sidebar-02/nav-user.tsx
+++ b/frontend/components/sidebar-02/nav-user.tsx
@@ -2,6 +2,7 @@
import { ChevronsUpDown, LogOut, Settings as SettingsIcon, Sun } from "lucide-react";
import Link from "next/link";
+import { useRouter } from "next/navigation";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import {
@@ -19,12 +20,21 @@ import {
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
+import { authClient } from "@/lib/auth-client";
-// Placeholder identity — there is no auth backend yet.
-const user = { name: "Dr. Khalid", role: "Clinician", initials: "K" };
// Open-source repo (placeholder).
const REPO_URL = "https://github.com/temetro/temetro";
+function initialsFromName(name: string): string {
+ const letters = name
+ .split(/\s+/)
+ .map((w) => w[0])
+ .filter(Boolean)
+ .join("")
+ .slice(0, 2);
+ return (letters || "?").toUpperCase();
+}
+
function GitHubIcon({ className }: { className?: string }) {
return (
|