feat: page metadata (#24)

page metadata
<img width="590" height="110" alt="CleanShot 2026-02-22 at 14 40 28@2x"
src="https://github.com/user-attachments/assets/f311ac3c-3a79-4e96-980d-5b71289d17c7"
/>
This commit is contained in:
Maxwell
2026-02-22 17:27:18 -08:00
committed by GitHub
parent 08992a634f
commit 416b52de1d
32 changed files with 344 additions and 0 deletions
+22
View File
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import {
getOrg,
@@ -10,6 +11,27 @@ import {
import { OrgDetailContent } from "@/components/orgs/org-detail-content";
import { UserProfileContent } from "@/components/users/user-profile-content";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string }>;
}): Promise<Metadata> {
const { owner } = await params;
const orgData = await getOrg(owner).catch(() => null);
if (orgData) {
return { title: orgData.name || orgData.login };
}
const userData = await getUser(owner).catch(() => null);
if (userData) {
return {
title: userData.name
? `${userData.name} (${userData.login})`
: userData.login,
};
}
return { title: owner };
}
export default async function OwnerPage({ params }: { params: Promise<{ owner: string }> }) {
const { owner } = await params;
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import { getServerSession } from "@/lib/auth";
import { redirect } from "next/navigation";
import {
@@ -10,6 +11,10 @@ import {
import { DashboardContent } from "@/components/dashboard/dashboard-content";
import { all } from "better-all";
export const metadata: Metadata = {
title: "Dashboard",
};
export default async function DashboardPage() {
const session = await getServerSession();
if (!session) return redirect("/");
@@ -1,5 +1,10 @@
import type { Metadata } from "next";
import { ExtensionPageContent } from "@/components/extension/extension-page-content";
export const metadata: Metadata = {
title: "Browser Extension",
};
export default function ExtensionPage() {
return <ExtensionPageContent />;
}
+5
View File
@@ -1,6 +1,11 @@
import type { Metadata } from "next";
import { getAuthenticatedUser, searchIssues } from "@/lib/github";
import { IssuesContent } from "@/components/issues/issues-content";
export const metadata: Metadata = {
title: "Issues",
};
export default async function IssuesPage() {
const user = await getAuthenticatedUser();
if (!user) return null;
@@ -1,6 +1,11 @@
import type { Metadata } from "next";
import { getNotifications } from "@/lib/github";
import { NotificationsContent } from "@/components/notifications/notifications-content";
export const metadata: Metadata = {
title: "Notifications",
};
export default async function NotificationsPage() {
const notifications = await getNotifications(50);
return (
+5
View File
@@ -1,6 +1,11 @@
import type { Metadata } from "next";
import { getUserOrgs } from "@/lib/github";
import { OrgsContent, type OrgListItem } from "@/components/orgs/orgs-content";
export const metadata: Metadata = {
title: "Organizations",
};
export default async function OrgsPage() {
const orgsResponse = await getUserOrgs(50);
@@ -1,7 +1,25 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getWorkflowRun, getWorkflowRunJobs } from "@/lib/github";
import { RunDetail } from "@/components/actions/run-detail";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string; runId: string }>;
}): Promise<Metadata> {
const { owner, repo, runId } = await params;
const runIdNum = parseInt(runId, 10);
if (isNaN(runIdNum)) {
return { title: `Run · ${owner}/${repo}` };
}
const run = await getWorkflowRun(owner, repo, runIdNum);
if (!run) {
return { title: `Run #${runId} · ${owner}/${repo}` };
}
return { title: `${run.name || run.display_title} #${run.run_number} · ${owner}/${repo}` };
}
export default async function WorkflowRunPage({
params,
}: {
@@ -1,7 +1,17 @@
import type { Metadata } from "next";
import { getRepo } from "@/lib/github";
import { RunComparisonPage } from "@/components/actions/run-comparison-page";
import { redirect } from "next/navigation";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `Compare Runs · ${owner}/${repo}` };
}
export default async function CompareRunsPage({
params,
searchParams,
@@ -1,6 +1,16 @@
import type { Metadata } from "next";
import { getRepoWorkflows, getRepoWorkflowRuns } from "@/lib/github";
import { ActionsList } from "@/components/actions/actions-list";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `Actions · ${owner}/${repo}` };
}
export default async function ActionsPage({
params,
searchParams,
@@ -1,6 +1,16 @@
import type { Metadata } from "next";
import { getRepo, getRepoEvents, getCommitActivity } from "@/lib/github";
import { RepoActivityView } from "@/components/repo/repo-activity-view";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `Activity · ${owner}/${repo}` };
}
export default async function ActivityPage({
params,
}: {
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import {
getFileContent,
getRepoBranches,
@@ -15,6 +16,22 @@ const MARKDOWN_EXTENSIONS = new Set(["md", "mdx", "markdown", "mdown", "mkd"]);
const IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "svg", "webp", "ico", "bmp"]);
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string; path: string[] }>;
}): Promise<Metadata> {
const { owner, repo, path: pathSegments } = await params;
const [branches, tags] = await Promise.all([
getRepoBranches(owner, repo),
getRepoTags(owner, repo),
]);
const branchNames = [...branches.map((b) => b.name), ...tags.map((t) => t.name)];
const { path } = parseRefAndPath(pathSegments, branchNames);
const filename = path.split("/").pop() || path;
return { title: `${filename} · ${owner}/${repo}` };
}
function isBinary(content: string): boolean {
// eslint-disable-next-line no-control-regex
return /[\x00-\x08\x0E-\x1F]/.test(content.slice(0, 1000));
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import {
getRepo,
getRepoContents,
@@ -13,6 +14,15 @@ import { MarkdownRenderer } from "@/components/shared/markdown-renderer";
import { TrackView } from "@/components/shared/track-view";
import { deleteBranch } from "../actions";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `Code · ${owner}/${repo}` };
}
export default async function CodePage({
params,
}: {
@@ -1,7 +1,23 @@
import type { Metadata } from "next";
import { getCommit } from "@/lib/github";
import { highlightDiffLines, type SyntaxToken } from "@/lib/shiki";
import { CommitDetail } from "@/components/repo/commit-detail";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string; sha: string }>;
}): Promise<Metadata> {
const { owner, repo, sha } = await params;
const commit = await getCommit(owner, repo, sha);
if (!commit) {
return { title: `Commit ${sha.slice(0, 7)} · ${owner}/${repo}` };
}
const message = commit.commit?.message?.split("\n")[0] || "";
const shortSha = sha.slice(0, 7);
return { title: `${message || shortSha} · ${owner}/${repo}` };
}
export default async function CommitDetailPage({
params,
}: {
@@ -1,6 +1,16 @@
import type { Metadata } from "next";
import { getRepoCommits, getRepo, getRepoBranches } from "@/lib/github";
import { CommitsList } from "@/components/repo/commits-list";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `Commits · ${owner}/${repo}` };
}
export default async function CommitsPage({
params,
}: {
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import {
getRepo,
getCommitActivity,
@@ -8,6 +9,15 @@ import {
} from "@/lib/github";
import { InsightsView } from "@/components/repo/insights-view";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `Insights · ${owner}/${repo}` };
}
export default async function InsightsPage({
params,
}: {
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import {
getIssue,
getIssueComments,
@@ -21,6 +22,24 @@ import { headers } from "next/headers";
import { inngest } from "@/lib/inngest";
import { isItemPinned } from "@/lib/pinned-items-store";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string; number: string }>;
}): Promise<Metadata> {
const { owner, repo, number: numStr } = await params;
const issueNumber = parseInt(numStr, 10);
const issue = await getIssue(owner, repo, issueNumber);
if (!issue) {
return { title: `Issue #${issueNumber} · ${owner}/${repo}` };
}
return {
title: `${issue.title} · Issue #${issueNumber} · ${owner}/${repo}`,
};
}
export default async function IssueDetailPage({
params,
}: {
@@ -1,7 +1,17 @@
import type { Metadata } from "next";
import { getRepoIssuesPage } from "@/lib/github";
import { IssuesList } from "@/components/issue/issues-list";
import { fetchIssuesByAuthor } from "./actions";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `Issues · ${owner}/${repo}` };
}
export default async function IssuesListPage({
params,
}: {
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import { getRepoPageData } from "@/lib/github";
import { TrackView } from "@/components/shared/track-view";
import { RepoOverview, type RepoOverviewProps } from "@/components/repo/repo-overview";
@@ -12,6 +13,15 @@ import {
import { fetchPinnedItemsForRepo } from "./pin-actions";
import { revalidateReadme } from "./readme-actions";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `${owner}/${repo}` };
}
export default async function RepoPage({
params,
}: {
@@ -1,6 +1,16 @@
import type { Metadata } from "next";
import { getUser, getPersonRepoActivity, getRepoContributorStats } from "@/lib/github";
import { PersonDetail } from "@/components/people/person-detail";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string; username: string }>;
}): Promise<Metadata> {
const { owner, repo, username } = await params;
return { title: `${username} · ${owner}/${repo}` };
}
export default async function PersonPage({
params,
}: {
@@ -1,7 +1,17 @@
import type { Metadata } from "next";
import { getOrgMembers, getUser, getOctokit } from "@/lib/github";
import { PeopleList } from "@/components/people/people-list";
import { inviteOrgMember } from "./actions";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `People · ${owner}/${repo}` };
}
export default async function PeoplePage({
params,
}: {
@@ -1,9 +1,23 @@
import type { Metadata } from "next";
import { getPromptRequest, listPromptRequestComments } from "@/lib/prompt-request-store";
import { PromptDetail } from "@/components/prompt-request/prompt-detail";
import { notFound } from "next/navigation";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string; id: string }>;
}): Promise<Metadata> {
const { owner, repo, id } = await params;
const promptRequest = await getPromptRequest(id);
if (!promptRequest) {
return { title: `Prompt · ${owner}/${repo}` };
}
return { title: `${promptRequest.title} · ${owner}/${repo}` };
}
export default async function PromptDetailPage({
params,
}: {
@@ -1,6 +1,16 @@
import type { Metadata } from "next";
import { listPromptRequests } from "@/lib/prompt-request-store";
import { PromptList } from "@/components/prompt-request/prompt-list";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `Prompts · ${owner}/${repo}` };
}
export default async function PromptsPage({
params,
}: {
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import {
getPullRequestBundle,
getPullRequestFiles,
@@ -35,6 +36,24 @@ import { inngest } from "@/lib/inngest";
import { isItemPinned } from "@/lib/pinned-items-store";
import { all } from "better-all";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string; number: string }>;
}): Promise<Metadata> {
const { owner, repo, number: numStr } = await params;
const pullNumber = parseInt(numStr, 10);
const bundle = await getPullRequestBundle(owner, repo, pullNumber);
if (!bundle) {
return { title: `PR #${pullNumber} · ${owner}/${repo}` };
}
return {
title: `${bundle.pr.title} · PR #${pullNumber} · ${owner}/${repo}`,
};
}
type GitHubPRFile = {
filename: string;
status: string;
@@ -1,7 +1,17 @@
import type { Metadata } from "next";
import { getRepoPullRequestsWithStats } from "@/lib/github";
import { PRsList } from "@/components/pr/prs-list";
import { fetchPRsByAuthor, fetchAllCheckStatuses, prefetchPRDetail, fetchPRPage } from "./actions";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `Pull Requests · ${owner}/${repo}` };
}
export default async function PullsListPage({
params,
}: {
@@ -1,8 +1,22 @@
import type { Metadata } from "next";
import { ShieldAlert } from "lucide-react";
import { getRepositoryAdvisory } from "@/lib/github";
import { renderMarkdownToHtml } from "@/components/shared/markdown-renderer";
import { AdvisoryDetail } from "@/components/security/advisory-detail";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string; ghsaId: string }>;
}): Promise<Metadata> {
const { owner, repo, ghsaId } = await params;
const advisory = await getRepositoryAdvisory(owner, repo, ghsaId);
if (!advisory) {
return { title: `Advisory · ${owner}/${repo}` };
}
return { title: `${advisory.summary || ghsaId} · ${owner}/${repo}` };
}
export default async function AdvisoryDetailPage({
params,
}: {
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import { ShieldAlert } from "lucide-react";
import { getRepoSecurityTabData, getFileContent } from "@/lib/github";
import { renderMarkdownToHtml } from "@/components/shared/markdown-renderer";
@@ -17,6 +18,15 @@ async function fetchSecurityPolicy(owner: string, repo: string): Promise<string
return null;
}
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string }>;
}): Promise<Metadata> {
const { owner, repo } = await params;
return { title: `Security · ${owner}/${repo}` };
}
export default async function SecurityPage({
params,
}: {
@@ -1,5 +1,10 @@
import type { Metadata } from "next";
import { Settings } from "lucide-react";
export const metadata: Metadata = {
title: "Settings",
};
export default function SettingsPage() {
return (
<div className="py-16 text-center">
@@ -1,7 +1,23 @@
import type { Metadata } from "next";
import { getRepoContents, getRepoBranches, getRepoTags } from "@/lib/github";
import { parseRefAndPath } from "@/lib/github-utils";
import { FileList } from "@/components/repo/file-list";
export async function generateMetadata({
params,
}: {
params: Promise<{ owner: string; repo: string; path: string[] }>;
}): Promise<Metadata> {
const { owner, repo, path: pathSegments } = await params;
const [branches, tags] = await Promise.all([
getRepoBranches(owner, repo),
getRepoTags(owner, repo),
]);
const branchNames = [...branches.map((b) => b.name), ...tags.map((t) => t.name)];
const { path } = parseRefAndPath(pathSegments, branchNames);
return { title: `${path || "/"} · ${owner}/${repo}` };
}
export default async function TreePage({
params,
}: {
+5
View File
@@ -1,6 +1,11 @@
import type { Metadata } from "next";
import { getUserRepos } from "@/lib/github";
import { ReposContent } from "@/components/repos/repos-content";
export const metadata: Metadata = {
title: "Repositories",
};
export default async function ReposPage() {
const repos = await getUserRepos("updated", 50);
return <ReposContent repos={repos} />;
+5
View File
@@ -1,5 +1,10 @@
import type { Metadata } from "next";
import { SearchContent } from "@/components/search/search-content";
export const metadata: Metadata = {
title: "Search",
};
export default async function SearchPage({
searchParams,
}: {
+5
View File
@@ -1,6 +1,11 @@
import type { Metadata } from "next";
import { getTrendingRepos } from "@/lib/github";
import { TrendingContent } from "@/components/trending/trending-content";
export const metadata: Metadata = {
title: "Trending",
};
export default async function TrendingPage() {
const [weekly, daily, monthly] = await Promise.all([
getTrendingRepos(undefined, "weekly", 25),
@@ -1,3 +1,4 @@
import type { Metadata } from "next";
import { getUser, getUserPublicRepos, getUserPublicOrgs, getContributionData } from "@/lib/github";
import { UserProfileContent } from "@/components/users/user-profile-content";
import { ExternalLink, User } from "lucide-react";
@@ -30,6 +31,19 @@ function UnknownUserPage({ username }: { username: string }) {
);
}
export async function generateMetadata({
params,
}: {
params: Promise<{ username: string }>;
}): Promise<Metadata> {
const { username } = await params;
const userData = await getUser(username).catch(() => null);
if (!userData) {
return { title: username };
}
return { title: userData.name ? `${userData.name} (${userData.login})` : userData.login };
}
export default async function UserProfilePage({
params,
}: {