Skip to content
82 linesCodeBlameRaw
1import { Form } from "react-router";
2
3import type { WorkspaceInvitation } from "@g1t/contracts";
4
5import { ErrorText, SubmitButton } from "./ui";
6import { Avatar } from "./ui/avatar";
7import { Card } from "./ui/card";
8import { UserCard } from "./user-card";
9import { invitationLine } from "../lib/invitations";
10
11/**
12 * The workspace invitations waiting for the person's answer, each with
13 * Accept and Decline. Accepting joins with the role it names; declining
14 * tells whoever sent it. `next` is where accepting goes, when not to the
15 * workspace itself.
16 */
17export function InvitationList({
18 invitations,
19 error,
20 errorFor,
21 next,
22}: {
23 invitations: WorkspaceInvitation[];
24 error?: string | null;
25 errorFor?: string | null;
26 next?: string | null;
27}) {
28 return (
29 <Card asChild tone="plain" divided>
30 <ul>
31 {invitations.map((invitation) => (
32 <li key={invitation.id} className="px-4 py-4">
33 <div className="flex flex-wrap items-center gap-3">
34 <Avatar name={invitation.workspace.slug} image={invitation.workspace.avatar} size={32} square />
35 <div className="min-w-0 grow basis-48">
36 <p className="text-sm font-medium">
37 {invitation.workspace.name}{" "}
38 <span className="font-mono text-xs font-normal text-faint">{invitation.workspace.slug}</span>
39 </p>
40 <p className="mt-0.5 text-xs text-muted">
41 {invitation.invitedBy ? (
42 <>
43 <UserCard username={invitation.invitedBy.username}>
44 <span className="font-mono text-fg/90">@{invitation.invitedBy.username}</span>
45 </UserCard>{" "}
46 </>
47 ) : (
48 "The g1t team "
49 )}
50 {invitationLine(invitation)}
51 </p>
52 </div>
53 <div className="flex items-center gap-2">
54 <Form method="post">
55 <input type="hidden" name="intent" value="decline-invitation" />
56 <input type="hidden" name="id" value={invitation.id} />
57 <input type="hidden" name="workspace" value={invitation.workspace.name} />
58 <SubmitButton variant="outline" match={{ intent: "decline-invitation", id: invitation.id }} pending="Declining…">
59 Decline
60 </SubmitButton>
61 </Form>
62 <Form method="post">
63 <input type="hidden" name="intent" value="accept-invitation" />
64 <input type="hidden" name="id" value={invitation.id} />
65 {next && <input type="hidden" name="next" value={next} />}
66 <SubmitButton match={{ intent: "accept-invitation", id: invitation.id }} pending="Joining…">
67 Accept
68 </SubmitButton>
69 </Form>
70 </div>
71 </div>
72 {errorFor === invitation.id && (
73 <div className="mt-2">
74 <ErrorText>{error}</ErrorText>
75 </div>
76 )}
77 </li>
78 ))}
79 </ul>
80 </Card>
81 );
82}