Skip to content
70 linesCodeBlameRaw
1/**
2 * What a not-found page says. A private project, issue or workspace and one
3 * that does not exist must look the same to someone who cannot see it, so
4 * the words depend only on what kind of thing the address names and on who
5 * is looking, never on whether it exists.
6 */
7
8/** The kind of thing an address names. */
9export type MissingKind = "project" | "issue" | "pull" | "workspace" | "person" | "page";
10
11const KINDS: readonly MissingKind[] = ["project", "issue", "pull", "workspace", "person", "page"];
12
13/** Names that are pages of g1t itself, never a workspace. */
14const RESERVED = new Set([
15 "settings", "explore", "search", "new", "u", "pricing", "avatars", "workspaces", "login", "logout",
16 "register", "verify", "confirm-email", "forgot", "reset", "device", "oauth", "invite",
17]);
18
19/**
20 * The kind a loader said it could not find (`data({ kind }, { status: 404 })`),
21 * else the kind the address names. Only the shape of the address is used.
22 */
23export function missingKind(errorData: unknown, pathname: string): MissingKind {
24 const said = (errorData as { kind?: unknown } | null | undefined)?.kind;
25 if (typeof said === "string" && (KINDS as readonly string[]).includes(said)) return said as MissingKind;
26 const parts = pathname.split("/").filter(Boolean);
27 if (parts[0] === "u" && parts.length === 2) return "person";
28 if (parts.length === 0 || RESERVED.has(parts[0]!)) return "page";
29 if (parts.length === 1) return "workspace";
30 if (parts[1] === "-") return "page";
31 if (parts.length === 2) return "project";
32 if (parts[2] === "issues" && parts.length === 4 && /^\d+$/.test(parts[3]!)) return "issue";
33 if (parts[2] === "pull" && parts.length === 4 && /^\d+$/.test(parts[3]!)) return "pull";
34 return "page";
35}
36
37export type NotFoundCopy = {
38 title: string;
39 body: string;
40 /** Who is signed in, for "Signed in as". Null when no one is. */
41 signedInAs: string | null;
42 /** Offer to sign in and come back. */
43 signIn: boolean;
44};
45
46/**
47 * What the page says to whoever is looking. Signed out, it says that the
48 * page may be private, and to sign in if it is yours; signed in, that it
49 * does not exist or they cannot reach it. Never which of the two.
50 */
51export function notFoundCopy(kind: MissingKind, username: string | null | undefined): NotFoundCopy {
52 // People are public: a name either belongs to someone or it does not.
53 if (kind === "person") {
54 return { title: "Nothing here", body: "No one on g1t goes by that name.", signedInAs: null, signIn: false };
55 }
56 if (!username) {
57 return {
58 title: "Nothing here",
59 body: "This page doesn't exist, or it's private. Sign in if it's yours.",
60 signedInAs: null,
61 signIn: true,
62 };
63 }
64 return {
65 title: "Nothing here",
66 body: "This page doesn't exist, or you don't have access to it.",
67 signedInAs: username,
68 signIn: false,
69 };
70}