91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
/**
|
|
* Renders the "invitees" tab of a profile, which shows whom the user invited,
|
|
* and whom they invited in turn.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import { useMemo } from "react";
|
|
import { Link } from "react-router";
|
|
import { Trans } from "@lingui/react/macro";
|
|
import type { Invitee } from "../../../shared/user.ts";
|
|
import type { FetchState } from "../../hooks/shared/useFetchState.ts";
|
|
import { Avatar } from "../shared/Avatar.tsx";
|
|
import { ListState } from "../shared/ListState.tsx";
|
|
import { ProfileSection } from "./ProfileSection.tsx";
|
|
|
|
type InviteTreeNode = Invitee & { children: InviteTreeNode[] };
|
|
|
|
/**
|
|
* Returns the invitees of `parentId` among `entries`, each with theirs. The
|
|
* server answers the whole tree as a flat list.
|
|
*/
|
|
function buildInviteTree(
|
|
entries: Invitee[],
|
|
parentId: string,
|
|
): InviteTreeNode[] {
|
|
return entries
|
|
.filter((e) => e.invitedById === parentId)
|
|
.map((e) => ({ ...e, children: buildInviteTree(entries, e.id) }));
|
|
}
|
|
|
|
/** Renders the invite tree, each invitee nested under who invited them. */
|
|
function InviteTreeList({ nodes }: { nodes: InviteTreeNode[] }) {
|
|
return (
|
|
<ul className="invite-tree">
|
|
{nodes.map((node) => (
|
|
<li key={node.id} className="invite-tree-node">
|
|
<Link
|
|
to={`/users/${node.username}`}
|
|
className="invite-tree-user"
|
|
>
|
|
<Avatar
|
|
userId={node.id}
|
|
username={node.username}
|
|
hasAvatar={!!node.avatarMime}
|
|
size={24}
|
|
/>
|
|
@{node.username}
|
|
</Link>
|
|
{node.children.length > 0 && <InviteTreeList nodes={node.children} />}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
}
|
|
|
|
/** Passed to {@link ProfileInviteesTab} by `UserPublicProfile`. */
|
|
export interface ProfileInviteesTabProps {
|
|
userId: string;
|
|
/**
|
|
* Loaded when the tab is first opened, from
|
|
* `GET /api/users/:username/invitees`.
|
|
*/
|
|
state: FetchState<Invitee[]>;
|
|
}
|
|
|
|
/** Renders the invitees of `userId` as a tree. */
|
|
export function ProfileInviteesTab(
|
|
{ userId, state }: ProfileInviteesTabProps,
|
|
): React.JSX.Element {
|
|
const nodes = useMemo(
|
|
() => state.status === "loaded" ? buildInviteTree(state.data, userId) : [],
|
|
[state, userId],
|
|
);
|
|
return (
|
|
<ProfileSection title={<Trans>Invitees</Trans>}>
|
|
<ListState
|
|
state={state}
|
|
isEmpty={({ data }) => data.length === 0}
|
|
empty={
|
|
<p className="empty-state invite-tree-empty">
|
|
<Trans>No invitees yet.</Trans>
|
|
</p>
|
|
}
|
|
>
|
|
{() => <InviteTreeList nodes={nodes} />}
|
|
</ListState>
|
|
</ProfileSection>
|
|
);
|
|
}
|