index.tsx (4.05 KB)
import { useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute, Link } from "@tanstack/react-router";
import { formatDistanceToNow } from "date-fns";
import { getIssuesByRepoOptions } from "@/api/issues";
import { NotFoundComponent } from "@/components/404-components";
import { buttonVariants } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";

export const Route = createFileRoute("/$owner/$repo/_layout/issues/")({
  component: RouteComponent,
  notFoundComponent: NotFoundComponent,
  loader: async ({ params, context: { queryClient } }) => {
    await queryClient.ensureQueryData(
      getIssuesByRepoOptions({
        owner: params.owner,
        repo: params.repo,
      })
    );
  },
  pendingComponent: PendingComponent,
});

function RouteComponent() {
  const params = Route.useParams();
  const { data: issues } = useSuspenseQuery(
    getIssuesByRepoOptions({
      owner: params.owner,
      repo: params.repo,
    })
  );

  return (
    <div className="mx-auto max-w-5xl space-y-4">
      <div className="flex items-center justify-between pb-4">
        <h2 className="font-semibold text-xl">Issues</h2>
        <Link
          className={buttonVariants({ size: "sm" })}
          params={params}
          to="/$owner/$repo/issues/new"
        >
          New Issue
        </Link>
      </div>

      {issues.length === 0 && (
        <div className="space-y-4 rounded-lg border bg-card p-12 text-center">
          <p className="text-muted-foreground">
            No issues found for this repository.
          </p>
          <Link
            className={buttonVariants()}
            params={params}
            to="/$owner/$repo/issues/new"
          >
            Create Issue
          </Link>
        </div>
      )}

      <div className="divide-y overflow-hidden rounded-lg border">
        {issues.map((issue) => (
          <Link
            className="block px-4 py-4 transition-colors hover:bg-muted/50"
            key={issue.id}
            params={{ ...params, issueNumber: issue.number.toString() }}
            to="/$owner/$repo/issues/$issueNumber"
          >
            <div className="flex items-start gap-3">
              <div className="min-w-0 flex-1">
                <div className="flex items-start gap-2">
                  <h3 className="font-semibold text-base leading-tight">
                    {issue.title}
                  </h3>
                </div>
                <div className="mt-1 flex items-center gap-2 text-muted-foreground text-xs">
                  <span>#{issue.number}</span>
                  <span>•</span>
                  <span>
                    {issue.creatorUsername} opened{" "}
                    {formatDistanceToNow(new Date(issue.createdAt), {
                      addSuffix: true,
                    })}
                  </span>
                </div>
              </div>
            </div>
          </Link>
        ))}
      </div>
    </div>
  );
}

function PendingComponent() {
  const params = Route.useParams();

  return (
    <div className="mx-auto max-w-5xl space-y-4">
      <div className="flex items-center justify-between pb-4">
        <h2 className="font-semibold text-xl">Issues</h2>
        <Link
          className={buttonVariants({ size: "sm" })}
          params={params}
          to="/$owner/$repo/issues/new"
        >
          New Issue
        </Link>
      </div>

      <div className="divide-y overflow-hidden rounded-lg border">
        {Array.from({ length: 5 }, (_, i) => `skeleton-${i}`).map((id) => (
          <div className="block px-4 py-4" key={id}>
            <div className="flex items-start gap-3">
              <div className="min-w-0 flex-1">
                <div className="flex items-start gap-2">
                  <Skeleton className="h-5 w-3/4" />
                </div>
                <div className="mt-1 flex items-center gap-2 text-muted-foreground text-xs">
                  <Skeleton className="h-3 w-32" />
                </div>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}