Skip to content

Commit 1938f7c

Browse files
authored
GitHub login feature and personal homepage (#127)
1 parent a81ad4f commit 1938f7c

File tree

9 files changed

+329
-14
lines changed

9 files changed

+329
-14
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { handlers } from "@/app/auth" // Referring to the auth.ts we just created
2+
export const { GET, POST } = handlers

app/auth.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
import NextAuth from "next-auth"
3+
import GitHub from "next-auth/providers/github"
4+
5+
export const { handlers, signIn, signOut, auth } = NextAuth({
6+
providers: [GitHub],
7+
})

app/middleware.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { auth as middleware } from "@/app/auth"

app/page.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import Link from 'next/link';
55
// import VulnerabilityList from '@/components/CveList';
66
import { message } from 'antd';
77
import Image from 'next/image';
8+
import SignIn from '@/components/sign-in';
9+
import { SessionProvider } from "next-auth/react"
10+
811

912
const HomePage: React.FC = () => {
1013
const [searchQuery, setSearchQuery] = useState('');
@@ -49,13 +52,15 @@ const HomePage: React.FC = () => {
4952
</div>
5053
</Link>
5154

52-
<nav>
55+
<nav className="flex items-center gap-6">
5356
<ul className="flex space-x-5">
54-
5557
<li><a href="#" className="hover:underline">About</a></li>
5658
<li><a href="#" className="hover:underline">Documentation</a></li>
5759
<li><a href="#" className="hover:underline">Blog</a></li>
5860
</ul>
61+
<SessionProvider>
62+
<SignIn />
63+
</SessionProvider>
5964
</nav>
6065
</header>
6166
{/* 搜索部分 */}

app/profile/layout.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import '@/app/ui/global.css';
2+
import { SessionProvider } from "next-auth/react"
3+
4+
export default function RootLayout({
5+
children,
6+
}: {
7+
children: React.ReactNode
8+
}) {
9+
return (
10+
<html lang="cn">
11+
<body>
12+
<SessionProvider>
13+
{children}
14+
</SessionProvider>
15+
</body>
16+
</html>
17+
)
18+
}

app/profile/page.tsx

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
'use client';
2+
3+
import { useSession } from 'next-auth/react';
4+
import Image from 'next/image';
5+
import Link from 'next/link';
6+
import { Tabs } from 'antd';
7+
import { useState } from 'react';
8+
9+
export default function ProfilePage() {
10+
const { data: session } = useSession();
11+
const [activeTab, setActiveTab] = useState('1');
12+
13+
if (!session) {
14+
return (
15+
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
16+
<div className="text-center">
17+
<h1 className="text-2xl font-bold text-gray-800">Please login first</h1>
18+
</div>
19+
</div>
20+
);
21+
}
22+
23+
return (
24+
<div className="min-h-screen bg-gray-100">
25+
{/* Top bar: Return Home button on the left, Profile Info on the right */}
26+
<header className="bg-white border-b">
27+
<div className="max-w-6xl mx-auto flex justify-between items-center px-4 py-6">
28+
{/* Left: Return Home Button */}
29+
<div>
30+
<Link href="/">
31+
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition duration-200">
32+
Return Home
33+
</button>
34+
</Link>
35+
</div>
36+
{/* Right: Profile Info */}
37+
<div className="flex items-center space-x-6">
38+
<div className="relative">
39+
<Image
40+
src={session.user?.image || '/default-avatar.png'}
41+
alt="Profile"
42+
width={100}
43+
height={100}
44+
className="rounded-full"
45+
/>
46+
</div>
47+
<div>
48+
<h1 className="text-2xl font-bold mb-1">{session.user?.name}</h1>
49+
<p className="text-gray-600 mb-2">{session.user?.email}</p>
50+
<button className="px-4 py-2 bg-gray-100 rounded-md hover:bg-gray-200">
51+
Edit Profile
52+
</button>
53+
</div>
54+
</div>
55+
</div>
56+
</header>
57+
58+
{/* Main Content Area (Tabs) */}
59+
<div className="max-w-6xl mx-auto px-4 py-6">
60+
<Tabs
61+
activeKey={activeTab}
62+
onChange={setActiveTab}
63+
items={[
64+
{
65+
key: '1',
66+
label: 'Overview',
67+
children: <OverviewTab />,
68+
},
69+
{
70+
key: '2',
71+
label: 'My Favorites',
72+
children: <FavoritesTab />,
73+
},
74+
{
75+
key: '3',
76+
label: 'Uploads',
77+
children: <UploadsTab />,
78+
},
79+
{
80+
key: '4',
81+
label: 'Analysis History',
82+
children: <AnalysisHistoryTab />,
83+
},
84+
]}
85+
/>
86+
</div>
87+
</div>
88+
);
89+
}
90+
91+
// Overview Tab component
92+
function OverviewTab() {
93+
return (
94+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
95+
{/* Activity Stats */}
96+
<div className="bg-white p-6 rounded-lg shadow">
97+
<h3 className="text-lg font-semibold mb-4">Activity Stats</h3>
98+
<div className="space-y-4">
99+
<div className="flex justify-between">
100+
<span>Analysis Count</span>
101+
<span className="font-semibold">123</span>
102+
</div>
103+
<div className="flex justify-between">
104+
<span>Favorites Count</span>
105+
<span className="font-semibold">45</span>
106+
</div>
107+
<div className="flex justify-between">
108+
<span>Crate Uploads</span>
109+
<span className="font-semibold">12</span>
110+
</div>
111+
</div>
112+
</div>
113+
114+
{/* Recent Activity */}
115+
<div className="bg-white p-6 rounded-lg shadow">
116+
<h3 className="text-lg font-semibold mb-4">Recent Activity</h3>
117+
<div className="space-y-4">
118+
<div className="flex items-center space-x-3">
119+
<div className="flex-1">
120+
<p className="text-sm">Analyzed tokio crate</p>
121+
<p className="text-xs text-gray-500">2 hours ago</p>
122+
</div>
123+
</div>
124+
<div className="flex items-center space-x-3">
125+
<div className="flex-1">
126+
<p className="text-sm">Favorited serde crate</p>
127+
<p className="text-xs text-gray-500">1 day ago</p>
128+
</div>
129+
</div>
130+
</div>
131+
</div>
132+
</div>
133+
);
134+
}
135+
136+
// My Favorites Tab component
137+
function FavoritesTab() {
138+
return (
139+
<div className="space-y-4">
140+
<div className="bg-white p-6 rounded-lg shadow">
141+
<h3 className="text-lg font-semibold mb-4">My Favorites</h3>
142+
{/* Favorites list */}
143+
<div className="space-y-4">
144+
{/* Sample favorite item */}
145+
<div className="border-b pb-4">
146+
<div className="flex justify-between items-start">
147+
<div>
148+
<h4 className="text-lg font-medium">serde</h4>
149+
<p className="text-sm text-gray-600">
150+
A generic serialization/deserialization framework
151+
</p>
152+
</div>
153+
<button className="text-red-500 hover:text-red-600">
154+
Unfavorite
155+
</button>
156+
</div>
157+
</div>
158+
{/* More favorite items... */}
159+
</div>
160+
</div>
161+
</div>
162+
);
163+
}
164+
165+
// Uploads Tab component
166+
function UploadsTab() {
167+
return (
168+
<div className="space-y-4">
169+
<div className="bg-white p-6 rounded-lg shadow">
170+
<h3 className="text-lg font-semibold mb-4">Uploads</h3>
171+
{/* Uploads list */}
172+
<div className="space-y-4">
173+
{/* Sample upload item */}
174+
<div className="border-b pb-4">
175+
<div className="flex justify-between items-start">
176+
<div>
177+
<h4 className="text-lg font-medium">my-crate</h4>
178+
<p className="text-sm text-gray-600">Upload Time: 2024-03-20</p>
179+
</div>
180+
<button className="text-blue-500 hover:text-blue-600">
181+
View Analysis
182+
</button>
183+
</div>
184+
</div>
185+
{/* More upload items... */}
186+
</div>
187+
</div>
188+
</div>
189+
);
190+
}
191+
192+
// Analysis History Tab component
193+
function AnalysisHistoryTab() {
194+
return (
195+
<div className="space-y-4">
196+
<div className="bg-white p-6 rounded-lg shadow">
197+
<h3 className="text-lg font-semibold mb-4">Analysis History</h3>
198+
{/* Analysis history list */}
199+
<div className="space-y-4">
200+
{/* Sample analysis item */}
201+
<div className="border-b pb-4">
202+
<div className="flex justify-between items-start">
203+
<div>
204+
<h4 className="text-lg font-medium">tokio (v1.36.0)</h4>
205+
<p className="text-sm text-gray-600">Analysis Time: 2024-03-21</p>
206+
</div>
207+
<button className="text-blue-500 hover:text-blue-600">
208+
View Report
209+
</button>
210+
</div>
211+
</div>
212+
{/* More analysis items... */}
213+
</div>
214+
</div>
215+
</div>
216+
);
217+
}

components/sign-in.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use client';
2+
3+
import { signIn, signOut, useSession } from 'next-auth/react';
4+
import Image from 'next/image';
5+
import Link from 'next/link';
6+
import { Dropdown } from 'antd';
7+
import type { MenuProps } from 'antd';
8+
9+
export default function SignInButton() {
10+
const { data: session, status } = useSession();
11+
12+
if (status === 'loading') {
13+
return (
14+
<button className="bg-gray-200 text-gray-400 px-4 py-2 rounded-md cursor-not-allowed">
15+
Loading...
16+
</button>
17+
);
18+
}
19+
20+
if (session) {
21+
const items: MenuProps['items'] = [
22+
{
23+
key: '1',
24+
label: <Link href="/profile">个人中心</Link>,
25+
},
26+
{
27+
key: '2',
28+
label: <span onClick={() => signOut()}>退出登录</span>,
29+
},
30+
];
31+
32+
return (
33+
<Dropdown menu={{ items }} placement="bottomRight">
34+
<div className="flex items-center gap-2 cursor-pointer">
35+
<div className="flex items-center gap-2 px-2 py-1 rounded-full bg-gray-100 hover:bg-gray-200 transition-colors duration-200">
36+
{session.user?.image && (
37+
<Image
38+
src={session.user.image}
39+
alt={session.user.name || 'User avatar'}
40+
width={32}
41+
height={32}
42+
className="rounded-full"
43+
/>
44+
)}
45+
<span className="text-sm text-gray-700">{session.user?.name}</span>
46+
</div>
47+
</div>
48+
</Dropdown>
49+
);
50+
}
51+
52+
return (
53+
<button
54+
onClick={() => signIn('github')}
55+
className="flex items-center gap-2 px-4 py-2 bg-gray-800 hover:bg-gray-700 text-white rounded-md transition-colors duration-200"
56+
>
57+
<svg
58+
className="w-5 h-5"
59+
fill="currentColor"
60+
viewBox="0 0 24 24"
61+
aria-hidden="true"
62+
>
63+
<path
64+
fillRule="evenodd"
65+
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
66+
clipRule="evenodd"
67+
/>
68+
</svg>
69+
登录
70+
</button>
71+
);
72+
}

next.config.mjs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,7 @@
44
export default {
55
output: "standalone",
66
reactStrictMode: true,
7-
// async redirects() {
8-
// return [
9-
// {
10-
// source: '/',
11-
12-
// destination: '/hompage',
13-
// permanent: false,
14-
// },
15-
// ];
16-
// },
7+
images: {
8+
domains: ['avatars.githubusercontent.com'], // 允许 GitHub 头像
9+
},
1710
};

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"clsx": "^2.1.1",
1717
"d3": "^7.9.0",
1818
"next": "^15.1.0",
19-
"next-auth": "4.24.11",
19+
"next-auth": "^5.0.0-beta.25",
2020
"postcss": "8.4.49",
2121
"react": "18.3.1",
2222
"react-dom": "18.3.1",
@@ -36,4 +36,4 @@
3636
"engines": {
3737
"node": ">=20.12.0"
3838
}
39-
}
39+
}

0 commit comments

Comments
 (0)