Skip to content

Commit 06621e5

Browse files
committed
contributor card added and toc improved
1 parent 294c8e0 commit 06621e5

6 files changed

Lines changed: 196 additions & 2 deletions

File tree

app/[[...slug]]/page.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { notFound } from "next/navigation";
99
import defaultMdxComponents from "fumadocs-ui/mdx";
1010
import { getGithubLastEdit } from "fumadocs-core/server";
1111
import { Metadata } from "next";
12+
import TocContributorCard from "@/components/toc-contributor-card";
13+
import { getFileContributors } from "@/lib/utils/github";
1214

1315
export default async function Page(props: {
1416
params: Promise<{ slug?: string[] }>;
@@ -25,11 +27,24 @@ export default async function Page(props: {
2527
path: `content/${page.file.path}`,
2628
});
2729

30+
const contributors = await getFileContributors(
31+
"shield-auth",
32+
"docs",
33+
`content/${page.file.path}`,
34+
);
35+
2836
return (
2937
<DocsPage
3038
toc={page.data.toc}
31-
tableOfContent={{ style: "clerk" }}
32-
tableOfContentPopover={{ style: "clerk" }}
39+
tableOfContent={{
40+
style: "clerk",
41+
single: false,
42+
footer: <TocContributorCard contributors={contributors} />,
43+
}}
44+
tableOfContentPopover={{
45+
style: "clerk",
46+
footer: <TocContributorCard contributors={contributors} />,
47+
}}
3348
full={page.data.full}
3449
lastUpdate={time ? new Date(time) : undefined}
3550
editOnGithub={{
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { GitHubContributor } from "@/lib/utils/github";
2+
import Image from "next/image";
3+
4+
const TocContributorCard = ({
5+
contributors,
6+
}: {
7+
contributors: GitHubContributor[];
8+
}): React.ReactNode => (
9+
<div className="flex flex-col gap-2 p-4 rounded-lg border-2 border-fd-border mt-5">
10+
<div className="text-xs">Contributors of this page</div>
11+
<div className="grid gap-2">
12+
{contributors.map((contributor) => (
13+
<Contributor key={contributor.username} contributor={contributor} />
14+
))}
15+
</div>
16+
</div>
17+
);
18+
19+
const Contributor = ({ contributor }: { contributor: GitHubContributor }) => (
20+
<div className="flex items-center gap-2">
21+
<Image
22+
src={contributor.avatarUrl}
23+
height={48}
24+
width={48}
25+
alt={`${contributor.username} avatar`}
26+
className="rounded-full bg-fd-border shrink-0"
27+
/>
28+
<div className="flex flex-col">
29+
<a href={contributor.profileUrl} className="text-sm font-medium">
30+
{contributor.username}
31+
</a>
32+
<div className="text-xs text-gray-500 dark:text-gray-400">
33+
{contributor.commitCount} commit(s)
34+
</div>
35+
{/* <div className="text-xs text-gray-500 dark:text-gray-400"> */}
36+
{/* {contributor.lastCommit} */}
37+
{/* </div> */}
38+
</div>
39+
</div>
40+
);
41+
42+
export default TocContributorCard;

content/getting-started/index.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
title: Getting Started
3+
description: Setup and start integrating the Shield in your existing system.
4+
---
5+
6+
<Cards>
7+
<Card
8+
title="Installation"
9+
href="https://docs.shield.rs/getting-started/installation"
10+
>
11+
We have a docker images for amd64 and arm64.
12+
</Card>
13+
</Cards>

content/getting-started/installation.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Before you begin, ensure you have the following installed:
1414

1515
### 1. Clone the repository
1616

17+
#### Using HTTPS
18+
1719
```bash
1820
git clone https://github.com/shield-auth/shield.git
1921
cd shield

lib/utils/github.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// types.ts
2+
export interface GitHubContributor {
3+
username: string;
4+
avatarUrl: string;
5+
profileUrl: string;
6+
commitCount: number;
7+
firstCommit: string;
8+
lastCommit: string;
9+
}
10+
11+
interface GitHubCommit {
12+
sha: string;
13+
commit: {
14+
author: {
15+
name: string;
16+
date: string;
17+
};
18+
};
19+
author: {
20+
login: string;
21+
avatar_url: string;
22+
html_url: string;
23+
} | null;
24+
}
25+
26+
// utils/github.ts
27+
export async function getFileContributors(
28+
owner: string,
29+
repo: string,
30+
filePath: string,
31+
githubToken?: string,
32+
): Promise<GitHubContributor[]> {
33+
const headers: HeadersInit = {
34+
Accept: "application/vnd.github.v3+json",
35+
...(githubToken && { Authorization: `token ${githubToken}` }),
36+
};
37+
38+
try {
39+
const response = await fetch(
40+
`https://api.github.com/repos/${owner}/${repo}/commits?path=${encodeURIComponent(filePath)}`,
41+
{ headers },
42+
);
43+
44+
if (!response.ok) {
45+
throw new Error(`GitHub API error: ${response.statusText}`);
46+
}
47+
48+
const commits: GitHubCommit[] = await response.json();
49+
const contributorsMap = new Map<string, GitHubContributor>();
50+
51+
commits.forEach((commit) => {
52+
const author = commit.author || {
53+
login: commit.commit.author.name,
54+
avatar_url: "",
55+
html_url: "",
56+
};
57+
58+
const username = author.login;
59+
60+
if (!contributorsMap.has(username)) {
61+
contributorsMap.set(username, {
62+
username,
63+
avatarUrl: author.avatar_url,
64+
profileUrl: author.html_url,
65+
commitCount: 0,
66+
firstCommit: commit.commit.author.date,
67+
lastCommit: commit.commit.author.date,
68+
});
69+
}
70+
71+
const contributor = contributorsMap.get(username)!;
72+
contributor.commitCount += 1;
73+
74+
// Update first and last commit dates
75+
if (
76+
new Date(commit.commit.author.date) < new Date(contributor.firstCommit)
77+
) {
78+
contributor.firstCommit = commit.commit.author.date;
79+
}
80+
if (
81+
new Date(commit.commit.author.date) > new Date(contributor.lastCommit)
82+
) {
83+
contributor.lastCommit = commit.commit.author.date;
84+
}
85+
});
86+
87+
return Array.from(contributorsMap.values()).sort(
88+
(a, b) => b.commitCount - a.commitCount,
89+
);
90+
} catch (error) {
91+
console.error("Error fetching file contributors:", error);
92+
throw error;
93+
}
94+
}
95+
96+
// Example usage in a Next.js API route (app/api/contributors/route.ts):
97+
// import { NextResponse } from "next/server";
98+
//
99+
// export async function GET(request: Request) {
100+
// const { searchParams } = new URL(request.url);
101+
// const owner = searchParams.get("owner");
102+
// const repo = searchParams.get("repo");
103+
// const filePath = searchParams.get("filePath");
104+
//
105+
// if (!owner || !repo || !filePath) {
106+
// return NextResponse.json(
107+
// { error: "Missing required parameters" },
108+
// { status: 400 },
109+
// );
110+
// }
111+
//
112+
// try {
113+
// const contributors = await getFileContributors(owner, repo, filePath);
114+
// return NextResponse.json(contributors);
115+
// } catch (error) {
116+
// return NextResponse.json(
117+
// { error: "Failed to fetch contributors" },
118+
// { status: 500 },
119+
// );
120+
// }
121+
// }

next.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const config = {
77
reactStrictMode: true,
88
output: "export",
99
images: {
10+
unoptimized: true,
1011
remotePatterns: [
1112
{
1213
protocol: "https",

0 commit comments

Comments
 (0)