Skip to content
83 linesCodeBlameRaw
1import { Link } from "react-router";
2
3import type { Route } from "./+types/invitations";
4import { InvitationList } from "../components/invitation-list";
5import { answerInvitation, loadInvitations } from "../lib/invitations.server";
6import { declinedLine } from "../lib/invitations";
7import { page } from "../lib/meta";
8import { assertSameOrigin, nextPath, requireUser } from "../lib/session.server";
9import { Card } from "../components/ui/card";
10
11export function meta(args: Route.MetaArgs) {
12 return page(args, { title: "Invitations · g1t" });
13}
14
15/**
16 * The workspace invitations waiting for the signed-in person's answer.
17 * Nobody joins a workspace without saying yes here (or on the invite's own
18 * page). Linked from the inbox item and the invitation email.
19 */
20export async function loader({ request, context }: Route.LoaderArgs) {
21 const user = requireUser(context, request);
22 const next = nextPath(request);
23 return {
24 invitations: await loadInvitations(user),
25 hasWorkspace: (user.workspaces ?? []).length > 0,
26 next: next === "/" ? null : next,
27 };
28}
29
30export async function action({ request, context }: Route.ActionArgs) {
31 assertSameOrigin(request);
32 const user = requireUser(context, request);
33 return (await answerInvitation(user, await request.formData(), request)) ?? null;
34}
35
36export default function Invitations({ loaderData, actionData }: Route.ComponentProps) {
37 const { invitations, hasWorkspace, next } = loaderData;
38 return (
39 <main className="mx-auto max-w-2xl px-4 py-12">
40 <h1 className="text-xl font-semibold">Invitations</h1>
41 <p className="mt-2 text-sm text-muted">
42 Workspaces you are invited to join. You join one only when you accept; declining tells whoever invited you.
43 </p>
44 {actionData?.declined !== undefined && (
45 <p className="mt-4 text-sm text-muted" role="status">
46 {declinedLine(actionData.declined)}
47 </p>
48 )}
49 <div className="mt-6">
50 {invitations.length > 0 ? (
51 <InvitationList
52 invitations={invitations}
53 error={actionData?.invitationError}
54 errorFor={actionData?.invitationId}
55 next={next}
56 />
57 ) : (
58 <Card tone="plain" className="border-dashed px-6 py-10 text-center">
59 <p className="text-sm font-medium">No invitations waiting</p>
60 <p className="mt-1 text-sm text-muted">
61 {hasWorkspace ? (
62 <>
63 When someone invites you to a workspace, it shows here and in your{" "}
64 <Link to="/notifications" className="underline underline-offset-4 hover:text-fg">
65 notifications
66 </Link>
67 .
68 </>
69 ) : (
70 <>
71 <Link to="/workspaces/new" className="underline underline-offset-4 hover:text-fg">
72 Create your workspace
73 </Link>
74 , or ask a workspace's owners to invite you by your username.
75 </>
76 )}
77 </p>
78 </Card>
79 )}
80 </div>
81 </main>
82 );
83}