import { useSuspenseQuery } from "@tanstack/react-query";
import {
createFileRoute,
Link,
Outlet,
useLocation,
} from "@tanstack/react-router";
import {
CircleDotIcon,
CodeIcon,
GitBranchIcon,
GitCommitHorizontalIcon,
GitPullRequestIcon,
SettingsIcon,
} from "lucide-react";
import * as z from "zod";
import { getBranchesQueryOptions } from "@/api/branches";
import { getRepoByOwnerAndNameOpts } from "@/api/repos";
import { getSessionOptions } from "@/api/session";
import { NotFoundComponent } from "@/components/404-components";
import { ErrorComponent } from "@/components/error-component";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { UserProfileButton } from "@/components/user-profile-button";
const searchSchema = z.object({
ref: z.string().optional(),
});
export const Route = createFileRoute("/$owner/$repo/_layout")({
component: RouteComponent,
errorComponent: ErrorComponent,
notFoundComponent: NotFoundComponent,
validateSearch: searchSchema,
loader: async ({ params, context: { queryClient } }) => {
queryClient.prefetchQuery(
getBranchesQueryOptions({
owner: params.owner,
repo: params.repo,
})
);
await queryClient.ensureQueryData(
getRepoByOwnerAndNameOpts({
owner: params.owner,
name: params.repo,
})
);
},
});
function RouteComponent() {
const params = Route.useParams();
const { owner, repo } = params;
const { ref } = Route.useSearch();
const pathname = useLocation({
select: (state) => state.pathname,
});
const { data: session } = useSuspenseQuery(getSessionOptions);
const { data: repository } = useSuspenseQuery(
getRepoByOwnerAndNameOpts({
owner,
name: repo,
})
);
const isOwner = session?.user.id === repository?.ownerId;
// Map route patterns to tab values
const getActiveTab = (path: string): string => {
const basePath = `/${owner}/${repo}`;
// Remove base path to get the relative route
const relativePath = path.startsWith(basePath)
? path.slice(basePath.length)
: path;
// Match routes to tabs
if (relativePath === "" || relativePath === "/") {
return "code";
}
if (relativePath.startsWith("/tree") || relativePath.startsWith("/blob")) {
return "code";
}
if (relativePath.startsWith("/commits")) {
return "commits";
}
if (relativePath.startsWith("/issues")) {
return "issues";
}
if (relativePath.startsWith("/pulls")) {
return "pulls";
}
if (relativePath.startsWith("/settings")) {
return "settings";
}
// Default to code tab
return "code";
};
const activeTab = getActiveTab(pathname);
return (
<div className="my-5">
<div className="border-b">
<nav className="mx-auto max-w-5xl space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Link className="ml-3" to="/">
<GitBranchIcon className="size-5" />
</Link>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link
className="text-primary"
params={{
owner,
}}
to="/$owner"
>
{owner}
</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator>/</BreadcrumbSeparator>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link
className="font-semibold text-primary"
params={{
owner,
repo,
}}
to="/$owner/$repo"
>
{repo}
</Link>
</BreadcrumbLink>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
<UserProfileButton />
</div>
<div>
<Tabs
defaultValue={activeTab ?? "code"}
onChange={() => {}}
value={activeTab ?? "code"}
>
<TabsList>
<TabsTrigger asChild value="code">
<Link params={params} search={{ ref }} to="/$owner/$repo">
<CodeIcon className="opacity-60" />
Code
</Link>
</TabsTrigger>
<TabsTrigger asChild value="commits">
<Link
params={params}
search={{ ref }}
to="/$owner/$repo/commits"
>
<GitCommitHorizontalIcon className="opacity-60" />
Commits
</Link>
</TabsTrigger>
<TabsTrigger asChild value="issues">
<Link params={params} to="/$owner/$repo/issues">
<CircleDotIcon className="opacity-60" />
Issues
</Link>
</TabsTrigger>
<TabsTrigger asChild value="pulls">
<Link params={params} to="/$owner/$repo/pulls">
<GitPullRequestIcon className="opacity-60" />
Pull Requests
</Link>
</TabsTrigger>
{isOwner && (
<TabsTrigger asChild value="settings">
<Link params={params} to="/$owner/$repo/settings">
<SettingsIcon className="opacity-60" />
Settings
</Link>
</TabsTrigger>
)}
</TabsList>
</Tabs>
</div>
</nav>
</div>
<section className="mx-auto my-8 max-w-5xl">
<Outlet />
</section>
</div>
);
}