Skip to content
42 linesCodeBlameRaw
1import { Outlet, data, type ShouldRevalidateFunctionArgs } from "react-router";
2
3import type { ChatSidebar, WorkspaceAgent } 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 me: { id: string; username: string; avatar: string | null };
18 sidebar: ChatSidebar | null;
19 people: Mentionable[];
20 agents: WorkspaceAgent[];
21};
22
23export async function loader({ params, context, request }: Route.LoaderArgs): Promise<ChatLayoutData> {
24 const viewer = requireUser(context, request);
25 if (!roleIn(viewer, params.owner)) throw data(null, { status: 404 });
26 const slug = params.owner.toLowerCase();
27 const [sidebar, { people, agents }] = await Promise.all([sidebarOrNull(slug, viewer), workspacePeople(slug, viewer)]);
28 return { slug, me: { id: viewer.id, username: viewer.username, avatar: viewer.avatar ?? null }, sidebar, people, agents };
29}
30
31/**
32 * Moving between conversations keeps the sidebar: it refreshes itself
33 * (components/chat/sidebar.tsx) and after anything is sent.
34 */
35export function shouldRevalidate({ currentParams, nextParams, formMethod, defaultShouldRevalidate }: ShouldRevalidateFunctionArgs) {
36 if (formMethod && formMethod !== "GET") return defaultShouldRevalidate;
37 return currentParams.owner !== nextParams.owner;
38}
39
40export default function ChatLayout() {
41 return <Outlet />;
42}