Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

62 linesCodeBlameRaw
1import { Outlet, data, type ShouldRevalidateFunctionArgs } from "react-router";
2
3import { type ChatSidebar, shownUsername } from "@g1t/contracts";
4
5import type { Route } from "./+types/layout";
6import type { Mentionable } from "../../../lib/chat";
7import { sidebarOrNull, workspacePeople } from "../../../lib/chat.server";
8import { requireUser, roleIn } from "../../../lib/session.server";
9
10/**
11 * Chat mode: what its sidebar shows (the shell draws it, from this data)
12 * and who can be mentioned or messaged. Each part is empty rather than an
13 * error when its service does not answer.
14 */
15export type ChatLayoutData = {
16 slug: string;
17 /** The viewer's role in the workspace; null for a guest, who is not in it. */
18 role: string | null;
19 /** The viewer, named as chat names them (`memberName`). */
20 me: { id: string; username: string; display_username: string | null; display_name: string; avatar: string | null };
21 sidebar: ChatSidebar | null;
22 people: Mentionable[];
23};
24
25export async function loader({ params, context, request }: Route.LoaderArgs): Promise<ChatLayoutData> {
26 const viewer = requireUser(context, request);
27 const role = roleIn(viewer, params.owner);
28 const slug = params.owner.toLowerCase();
29 // Someone outside the workspace comes in as a guest: the chat service
30 // answers with only what they may see (its public channels, and those
31 // they were asked into), and who may be mentioned is for members alone.
32 const [sidebar, people] = await Promise.all([sidebarOrNull(slug, viewer), role ? workspacePeople(slug, viewer) : Promise.resolve([])]);
33 if (!role && !sidebar) throw data(null, { status: 404 });
34 const self = people.find((person) => person.name === viewer.username.toLowerCase());
35 const display_username = viewer.display_username ?? self?.display_username ?? null;
36 return {
37 slug,
38 role,
39 me: {
40 id: viewer.id,
41 username: viewer.username,
42 display_username,
43 display_name: self?.display_name ?? shownUsername({ username: viewer.username, display_username }),
44 avatar: viewer.avatar ?? null,
45 },
46 sidebar,
47 people,
48 };
49}
50
51/**
52 * Moving between conversations keeps the sidebar: it refreshes itself
53 * (components/chat/sidebar.tsx) and after anything is sent.
54 */
55export function shouldRevalidate({ currentParams, nextParams, formMethod, defaultShouldRevalidate }: ShouldRevalidateFunctionArgs) {
56 if (formMethod && formMethod !== "GET") return defaultShouldRevalidate;
57 return currentParams.owner !== nextParams.owner;
58}
59
60export default function ChatLayout() {
61 return <Outlet />;
62}