import { getSessionUser, canMutate, isCustomerOwner } from "@/lib/session"; import { getTranslations } from "next-intl/server"; import { redirect } from "next/navigation"; import { getOrgMembers } from "@/lib/team"; import { Card } from "@/components/ui/card"; import { BackLink } from "@/components/ui/back-link"; import { TeamList } from "@/components/team/team-list"; import { InviteForm } from "@/components/team/invite-form"; import { AccessOverview } from "@/components/team/access-overview"; /** * /team — manage org members. * * Visible to owners and platform users only (`canMutate`). User-role * members are redirected away — they shouldn't browse the roster. * * The page loads members server-side for the initial render. The * `` and `` client components handle live * updates after invites and refreshes. */ export async function generateMetadata() { const t = await getTranslations("common"); return { title: t("team") }; } export default async function TeamPage() { const user = await getSessionUser(); if (!user) redirect("/login"); if (!canMutate(user)) redirect("/dashboard"); // Bug 8: personal accounts have no team to manage. The page is // structurally meaningless and the invite form would create extra // ZITADEL users in a single-user org. Redirect cleanly. The matching // API guards in `/api/team` and `/api/team/invite` enforce the same // rule on direct calls. if (user.isPersonal) redirect("/dashboard"); const t = await getTranslations("team"); const tDashboard = await getTranslations("dashboard"); const members = await getOrgMembers(user.orgId); return (

{t("title")}

{t("description")}

{t("inviteSectionTitle")}

{t("membersSectionTitle")}{" "} ({members.length})

{/* Access overview — single place to see which member can reach which assistant, instead of checking each tenant page. */}

{t("accessTitle")}

{t("accessDescription")}

); }