Skip to content
81 linesCodeBlameRaw
1/**
2 * Pieces of the teams pages: a team in a list, and its badges.
3 */
4import { EyeOff, UsersRound } from "lucide-react";
5import { Link, useOutletContext } from "react-router";
6
7import { type Team, teamHandle } from "@g1t/contracts";
8
9import type { PeopleAgent } from "../lib/people";
10import { teamCounts, teamPath } from "../lib/teams";
11import { Badge } from "./ui/badge";
12import { TeamKindMark } from "./people";
13
14/**
15 * What each page of a team gets from the team's layout: the team, the
16 * agents on it (added, then those whose home team it is), and which of
17 * them were added (the others are on it through their own profile).
18 */
19export type TeamContext = { team: Team; agents: PeopleAgent[]; added: string[] };
20
21/** The team whose page this is. */
22export function useTeam(): Team {
23 return useOutletContext<TeamContext>().team;
24}
25
26/** The agents on the team whose page this is, and which were added to it. */
27export function useTeamAgents(): { agents: PeopleAgent[]; added: string[] } {
28 const { agents, added } = useOutletContext<TeamContext>();
29 return { agents, added };
30}
31
32/** Secret, and the viewer's place in the team. */
33export function TeamBadges({ team }: { team: Pick<Team, "visibility" | "viewer_role"> }) {
34 return (
35 <>
36 {team.visibility === "secret" && (
37 <Badge>
38 <EyeOff size={10} />
39 Secret
40 </Badge>
41 )}
42 {team.viewer_role === "maintainer" && <Badge tone="accent">Maintainer</Badge>}
43 {team.viewer_role === "member" && <Badge>Member</Badge>}
44 </>
45 );
46}
47
48/** One team in a list: its name, handle, what it is for, and how big it is. */
49export function TeamRow({ team, agents, children }: { team: Team; agents?: number; children?: React.ReactNode }) {
50 const agentsHere = agents ?? team.agents_count ?? 0;
51 return (
52 <li className="flex flex-wrap items-start gap-3 px-4 py-3 transition-colors hover:bg-surface/60 sm:flex-nowrap">
53 <span className="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md bg-raised text-muted ring-1 ring-line">
54 <UsersRound size={14} />
55 </span>
56 <div className="min-w-0 grow basis-48">
57 <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
58 <TeamKindMark people={team.members_count} agents={agentsHere} />
59 <Link to={teamPath(team.workspace, team.slug)} prefetch="intent" className="font-medium hover:underline">
60 {team.name}
61 </Link>
62 <span className="font-mono text-xs text-faint">{teamHandle(team)}</span>
63 <TeamBadges team={team} />
64 </div>
65 {team.description && <p className="mt-0.5 line-clamp-2 text-sm text-muted">{team.description}</p>}
66 <p className="mt-1 text-xs text-faint">
67 {teamCounts(team, agentsHere)}
68 {team.parent && (
69 <>
70 {" · in "}
71 <Link to={teamPath(team.workspace, team.parent.slug)} className="hover:text-fg hover:underline">
72 {team.parent.name}
73 </Link>
74 </>
75 )}
76 </p>
77 </div>
78 {children}
79 </li>
80 );
81}