index.tsx

 1// Repository picker page (/). Auto-redirects when there is exactly one repo.
 2// Shows a list when multiple repos are registered.
 3
 4import { useReadQuery } from "@apollo/client/react";
 5import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
 6import { GitFork, FolderOpen, AlertCircle } from "lucide-react";
 7import { useEffect } from "react";
 8
 9import { type RepositoriesQuery, RepositoriesDocument } from "@/__generated__/graphql";
10
11export const Route = createFileRoute("/")({
12  component: RouteComponent,
13  loader: async ({ context: { preloadQuery } }) => {
14    const repositoriesRef = preloadQuery<RepositoriesQuery>(RepositoriesDocument);
15    return { repositoriesRef: await preloadQuery.toPromise(repositoriesRef) };
16  },
17});
18
19function repoSlug(name: string | null | undefined): string {
20  return name ?? "_";
21}
22
23function repoLabel(name: string | null | undefined): string {
24  return name ?? "default";
25}
26
27function RouteComponent() {
28  const { repositoriesRef } = Route.useLoaderData();
29  const { data, error } = useReadQuery(repositoriesRef);
30  const navigate = useNavigate();
31
32  // Auto-redirect when there is exactly one repo — no need to pick.
33  useEffect(() => {
34    if (data?.repositories.nodes.length === 1) {
35      void navigate({
36        to: "/$repo",
37        params: { repo: repoSlug(data.repositories.nodes[0]?.name) },
38        replace: true,
39      });
40    }
41  }, [data, navigate]);
42
43  return (
44    <div className="mx-auto max-w-lg py-12">
45      <div className="mb-8 flex items-center gap-3">
46        <GitFork className="text-muted-foreground size-6" />
47        <h1 className="text-xl font-semibold">Repositories</h1>
48      </div>
49
50      {error && (
51        <div className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-2 rounded-md border px-4 py-3 text-sm">
52          <AlertCircle className="size-4 shrink-0" />
53          Failed to load repositories: {error.message}
54        </div>
55      )}
56
57      <div className="divide-border border-border divide-y rounded-md border">
58        {data?.repositories.nodes.map((repo) => (
59          <Link
60            key={repoSlug(repo.name)}
61            to="/$repo"
62            params={{ repo: repoSlug(repo.name) }}
63            className="hover:bg-muted/50 flex items-center gap-3 px-4 py-4 transition-colors"
64          >
65            <FolderOpen className="text-muted-foreground size-5 shrink-0" />
66            <p className="text-foreground font-medium">{repoLabel(repo.name)}</p>
67          </Link>
68        ))}
69
70        {data?.repositories.totalCount === 0 && (
71          <p className="text-muted-foreground px-4 py-8 text-center text-sm">
72            No repositories found.
73          </p>
74        )}
75      </div>
76    </div>
77  );
78}