g1t/apps/web/app/routes/repo/commits.tsx

52 lines1,883 bytesCodeBlame
1import type { Route } from "./+types/commits";
2import { Avatar, EmptyState, TimeAgo } from "../../components/ui";
3import { repos } from "../../lib/services.server";
4import { getViewer, unwrap } from "../../lib/session.server";
5
6const PAGE_SIZE = 50;
7
8export function meta({ params }: Route.MetaArgs) {
9 return [{ title: `Commits · ${params.owner}/${params.repo} · g1t` }];
10}
11
12export async function loader({ params, context }: Route.LoaderArgs) {
13 const path = { namespace: params.owner, name: params.repo };
14 return {
15 commits: unwrap(
16 await repos.log(path, getViewer(context), null, PAGE_SIZE),
17 ),
18 };
19}
20
21export default function Commits({ loaderData }: Route.ComponentProps) {
22 const { commits } = loaderData;
23 if (commits.length === 0) {
24 return <EmptyState title="No commits yet" />;
25 }
26 return (
27 <ol className="divide-y divide-line overflow-hidden rounded-xl border border-line">
28 {commits.map((commit) => {
29 const [subject, ...body] = commit.message.split("\n");
30 return (
31 <li key={commit.hash} className="flex items-start gap-3 px-4 py-3">
32 <Avatar name={commit.author.name} size={24} />
33 <div className="min-w-0 grow">
34 <p className="truncate font-medium">{subject}</p>
35 {body.join("\n").trim() && (
36 <p className="mt-1 line-clamp-2 text-sm whitespace-pre-line text-muted">
37 {body.join("\n").trim()}
38 </p>
39 )}
40 <p className="mt-1 text-xs text-faint">
41 {commit.author.name} committed <TimeAgo at={commit.authoredAt} />
42 </p>
43 </div>
44 <span className="shrink-0 rounded-md border border-line px-2 py-0.5 font-mono text-xs text-muted">
45 {commit.hash.slice(0, 7)}
46 </span>
47 </li>
48 );
49 })}
50 </ol>
51 );
52}