4.8 KB142 lines
Blame
1import type { Metadata } from "next";
2import Link from "next/link";
3import { timeAgoFromDate, groveApiUrl } from "@/lib/utils";
4
5interface Props {
6 params: Promise<{ owner: string; repo: string }>;
7 searchParams: Promise<{ status?: string }>;
8}
9
10export async function generateMetadata({ params }: Props): Promise<Metadata> {
11 const { repo } = await params;
12 return { title: `Diffs · ${repo}` };
13}
14
15async function getDiffs(owner: string, repo: string, status: string) {
16 try {
17 const res = await fetch(
18 `${groveApiUrl}/api/repos/${owner}/${repo}/diffs?status=${status}`,
19 { cache: "no-store" }
20 );
21 if (!res.ok) return null;
22 return res.json();
23 } catch {
24 return null;
25 }
26}
27
28const statusStyles: Record<string, { bg: string; text: string; border: string }> = {
29 open: { bg: "var(--status-open-bg)", text: "var(--status-open-text)", border: "var(--status-open-border)" },
30 landed: { bg: "var(--status-merged-bg)", text: "var(--status-merged-text)", border: "var(--status-merged-border)" },
31 closed: { bg: "var(--status-closed-bg)", text: "var(--status-closed-text)", border: "var(--status-closed-border)" },
32};
33
34export default async function DiffsPage({
35 params,
36 searchParams,
37}: Props) {
38 const { owner, repo } = await params;
39 const { status: statusParam } = await searchParams;
40 const status = statusParam ?? "open";
41
42 const data = await getDiffs(owner, repo, status);
43
44 return (
45 <>
46 <div className="flex items-center justify-between mb-4">
47 <div className="flex gap-4 text-sm">
48 {(["open", "landed", "closed"] as const).map((s) => (
49 <Link
50 key={s}
51 href={`/${owner}/${repo}/diffs?status=${s}`}
52 className="capitalize"
53 style={{
54 color: status === s ? "var(--text-primary)" : "var(--text-muted)",
55 fontWeight: status === s ? 600 : 400,
56 }}
57 >
58 {s}
59 </Link>
60 ))}
61 </div>
62 <Link
63 href={`/${owner}/${repo}/diffs/new`}
64 className="text-sm px-3 py-1"
65 style={{
66 backgroundColor: "var(--accent)",
67 color: "var(--accent-text)",
68 }}
69 >
70 New diff
71 </Link>
72 </div>
73
74 {data?.diffs?.length ? (
75 <div style={{ border: "1px solid var(--border-subtle)" }}>
76 <table className="w-full text-sm">
77 <tbody>
78 {data.diffs.map((d: any, i: number) => {
79 const style = statusStyles[d.status ?? status] ?? statusStyles.open;
80 return (
81 <tr
82 key={d.id}
83 className="hover-row"
84 style={{
85 borderTop:
86 i > 0 ? "1px solid var(--divide)" : undefined,
87 }}
88 >
89 <td className="py-2.5 pl-3 pr-3">
90 <div className="flex items-center gap-2">
91 <span
92 className="text-xs px-1.5 py-0.5 shrink-0"
93 style={{
94 backgroundColor: style.bg,
95 color: style.text,
96 border: `1px solid ${style.border}`,
97 }}
98 >
99 {d.status ?? status}
100 </span>
101 <Link
102 href={`/${owner}/${repo}/diffs/${d.number}`}
103 className="hover:underline truncate"
104 style={{ color: "var(--accent)" }}
105 >
106 {d.title}
107 </Link>
108 </div>
109 <div
110 className="text-xs mt-0.5 ml-0"
111 style={{ color: "var(--text-muted)" }}
112 >
113 D{d.number} by {d.author_name}
114 </div>
115 </td>
116 <td
117 className="py-2 text-xs font-mono text-right w-28 hidden sm:table-cell"
118 style={{ color: "var(--text-faint)" }}
119 >
120 {d.head_commit?.slice(0, 8)}
121 </td>
122 <td
123 className="py-2 pr-3 text-xs text-right w-20"
124 style={{ color: "var(--text-faint)" }}
125 >
126 {timeAgoFromDate(d.created_at)}
127 </td>
128 </tr>
129 );
130 })}
131 </tbody>
132 </table>
133 </div>
134 ) : (
135 <p className="text-sm py-8 text-center" style={{ color: "var(--text-faint)" }}>
136 No {status} diffs.
137 </p>
138 )}
139 </>
140 );
141}
142