Skip to content
56 linesCodeBlameRaw
1import type { Invite, InvitesOverview, User } from "@g1t/contracts";
2
3import { type BringInto, bringIntoChoices, inviteDraft, peoplePages } from "./invites";
4import { readCookie } from "./mission";
5import { billing, identity } from "./services.server";
6import { WORKSPACE_COOKIE, chosenWorkspace } from "./workspace-choice";
7
8/** What the Invites section's forms answer. */
9export type InviteActionData = { inviteCreated?: Invite; inviteError?: string; inviteRevoked?: boolean } | null;
10
11/**
12 * The workspaces an invite to g1t can also invite its person to
13 * (`bringIntoChoices`), and the People pages where a workspace invitation
14 * is made instead (`peoplePages`).
15 */
16export type BringIntoChoices = {
17 options: BringInto[];
18 note: string | null;
19 people: { slug: string; name: string; to: string }[];
20};
21
22/** A person's invites, or null when they cannot make any yet (email unconfirmed). */
23export async function loadInvites(user: User): Promise<InvitesOverview | null> {
24 if (!user.verified) return null;
25 return identity.listInvites(user).catch(() => null);
26}
27
28/**
29 * The workspaces an invite can also invite its person to, and the People
30 * pages to invite someone to a workspace from, the one the person is in
31 * (the shell's current workspace, from its cookie) first. A workspace
32 * whose plan cannot be checked is not offered: identity would refuse it.
33 */
34export async function loadBringInto(user: User, request: Request): Promise<BringIntoChoices> {
35 const memberships = user.workspaces ?? [];
36 const owned = memberships.filter((m) => m.role === "owner").map((m) => m.slug);
37 const free = owned.length > 0 ? await billing.freeWorkspaces(owned).catch(() => owned) : [];
38 const current = chosenWorkspace(memberships, readCookie(request.headers.get("cookie"), WORKSPACE_COOKIE))?.slug ?? null;
39 return { ...bringIntoChoices(memberships, free, current), people: peoplePages(memberships, current) };
40}
41
42/** Handles the Invites section's intents; null for any other intent. */
43export async function inviteAction(user: User, form: FormData): Promise<InviteActionData | undefined> {
44 switch (form.get("intent")) {
45 case "create-invite": {
46 // A workspace goes with it only when its box is ticked (`inviteDraft`).
47 const result = await identity.createInvite(user, inviteDraft(form));
48 return result.ok ? { inviteCreated: result.value } : { inviteError: result.error.message };
49 }
50 case "revoke-invite": {
51 const result = await identity.revokeInvite(user, String(form.get("id") ?? ""));
52 return result.ok ? { inviteRevoked: true } : { inviteError: result.error.message };
53 }
54 }
55 return undefined;
56}