Skip to content

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

66 lines2,827 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

About: license, languages, contributors, stars, releases and activity beside the files1import { Link } from "react-router";
2
3import type { Route } from "./+types/stargazers";
4import { Avatar, ButtonLink, EmptyState, TimeAgo } from "../../components/ui";
5import { requireRepo } from "../../lib/access.server";
6import { page } from "../../lib/meta";
7import { repos } from "../../lib/services.server";
8import { unwrap } from "../../lib/session.server";
9
10export function meta({ params, ...args }: Route.MetaArgs) {
11 return page(args, { title: `Stargazers · ${params.owner}/${params.repo} · g1t` });
12}
13
14export async function loader({ params, context, request }: Route.LoaderArgs) {
15 const { viewer } = await requireRepo(context, params, "read");
16 const pageNumber = Math.max(1, Number(new URL(request.url).searchParams.get("page")) || 1);
17 const path = { namespace: params.owner, name: params.repo };
18 const [stargazers, stars] = await Promise.all([repos.stargazers(path, viewer, pageNumber), repos.stars(path, viewer)]);
19 return { stargazers: unwrap(stargazers), total: stars.ok ? stars.value.stars : null, page: pageNumber };
20}
21
22export default function Stargazers({ loaderData }: Route.ComponentProps) {
23 const { stargazers, total, page: at } = loaderData;
24 return (
25 <div className="space-y-4">
26 <h2 className="text-lg font-semibold tracking-tight">
27 Stargazers {total != null && <span className="font-normal text-faint">{total.toLocaleString("en-US")}</span>}
28 </h2>
29 {stargazers.length === 0 ? (
30 <EmptyState title={at > 1 ? "No more stargazers" : "No stars yet"}>
31 {at > 1 ? null : "Star it from the button beside Watch, and you show here."}
32 </EmptyState>
33 ) : (
34 <ul className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
35 {stargazers.map((person) => (
36 <li key={person.username} className="flex items-center gap-3 rounded-xl border border-line bg-surface p-3">
37 <Avatar name={person.username} image={person.avatar} size={36} />
38 <div className="min-w-0">
39 <Link to={`/u/${person.username}`} className="block truncate text-sm font-medium hover:text-accent">
40 {person.username}
41 </Link>
42 <p className="text-xs text-faint">
43 Starred <TimeAgo at={person.starredAt} />
44 </p>
45 </div>
46 </li>
47 ))}
48 </ul>
49 )}
50 {(at > 1 || stargazers.length === 100) && (
51 <div className="flex justify-center gap-2">
52 {at > 1 && (
53 <ButtonLink variant="quiet" to={`?page=${at - 1}`}>
54 Newer
55 </ButtonLink>
56 )}
57 {stargazers.length === 100 && (
58 <ButtonLink variant="quiet" to={`?page=${at + 1}`}>
59 Older
60 </ButtonLink>
61 )}
62 </div>
63 )}
64 </div>
65 );
66}