Team UI
All checks were successful
Build and Push / build (push) Successful in 1m26s

This commit is contained in:
2026-04-26 23:07:47 +02:00
parent 22fd5fb2cc
commit a31d05b7c2
17 changed files with 1459 additions and 8 deletions

View File

@@ -0,0 +1,98 @@
"use client";
import { useState, useEffect } from "react";
import { useTranslations } from "next-intl";
interface OrgMember {
userId: string;
email: string;
displayName: string;
givenName: string;
familyName: string;
roles: string[];
}
interface Props {
initialMembers: OrgMember[];
currentUserId: string;
}
/**
* TeamList — renders the org's members. Refreshes after invites by
* polling the API; the InviteForm broadcasts a `team:refresh` window
* event after a successful invite so the list updates immediately
* rather than waiting for the next reload.
*/
export function TeamList({ initialMembers, currentUserId }: Props) {
const t = useTranslations("team");
const [members, setMembers] = useState<OrgMember[]>(initialMembers);
useEffect(() => {
function refresh() {
fetch("/api/team")
.then((r) => (r.ok ? r.json() : null))
.then((data) => {
if (data?.members) setMembers(data.members);
})
.catch(() => {});
}
window.addEventListener("team:refresh", refresh);
return () => window.removeEventListener("team:refresh", refresh);
}, []);
if (members.length === 0) {
return (
<div className="text-sm text-text-secondary text-center py-6 border border-border rounded-xl bg-surface-1">
{t("noMembers")}
</div>
);
}
return (
<div className="bg-surface-1 border border-border rounded-xl overflow-hidden">
<ul className="divide-y divide-border">
{members.map((m) => (
<li
key={m.userId}
className="px-5 py-3 flex items-center justify-between gap-4"
>
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-text-primary truncate">
{m.displayName || m.email}
</span>
{m.userId === currentUserId && (
<span className="text-[10px] uppercase tracking-wider text-accent">
{t("you")}
</span>
)}
</div>
<div className="text-xs text-text-muted truncate font-mono">
{m.email}
</div>
</div>
<div className="flex flex-wrap gap-1.5 justify-end">
{m.roles.length === 0 && (
<span className="text-[10px] uppercase tracking-wider text-text-muted bg-surface-3 px-2 py-0.5 rounded-full">
{t("noRole")}
</span>
)}
{m.roles.map((r) => (
<span
key={r}
className={`text-[10px] uppercase tracking-wider px-2 py-0.5 rounded-full ${
r === "owner"
? "bg-accent/15 text-accent border border-accent/20"
: "bg-surface-3 text-text-secondary border border-border"
}`}
>
{r}
</span>
))}
</div>
</li>
))}
</ul>
</div>
);
}