diff --git a/app/api/oauth/authorize/route.ts b/app/api/oauth/authorize/route.ts
new file mode 100644
index 0000000..fa692c1
--- /dev/null
+++ b/app/api/oauth/authorize/route.ts
@@ -0,0 +1,19 @@
+import { redirect } from 'next/navigation';
+
+export async function GET() {
+ const CLIENT_ID = process.env.LINUX_DO_CLIENT_ID;
+ const REDIRECT_URI = `${process.env.NEXT_PUBLIC_APP_URL}/api/oauth/callback`;
+
+ if (!CLIENT_ID || !process.env.NEXT_PUBLIC_APP_URL) {
+ return new Response('Missing configuration', { status: 500 });
+ }
+
+ const params = new URLSearchParams({
+ client_id: CLIENT_ID,
+ redirect_uri: REDIRECT_URI,
+ response_type: 'code',
+ scope: 'user', // As per user guide
+ });
+
+ redirect(`https://connect.linux.do/oauth2/authorize?${params.toString()}`);
+}
diff --git a/app/api/oauth/callback/route.ts b/app/api/oauth/callback/route.ts
new file mode 100644
index 0000000..b030faf
--- /dev/null
+++ b/app/api/oauth/callback/route.ts
@@ -0,0 +1,57 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+export async function GET(request: NextRequest) {
+ const searchParams = request.nextUrl.searchParams;
+ const code = searchParams.get('code');
+
+ if (!code) {
+ return NextResponse.redirect(new URL('/settings?error=no_code', request.url));
+ }
+
+ const CLIENT_ID = process.env.LINUX_DO_CLIENT_ID;
+ const CLIENT_SECRET = process.env.LINUX_DO_CLIENT_SECRET;
+ const REDIRECT_URI = `${process.env.NEXT_PUBLIC_APP_URL}/api/oauth/callback`;
+
+ if (!CLIENT_ID || !CLIENT_SECRET || !process.env.NEXT_PUBLIC_APP_URL) {
+ return NextResponse.redirect(new URL('/settings?error=config_missing', request.url));
+ }
+
+ try {
+ // 1. Exchange code for token
+ const tokenResponse = await fetch('https://connect.linux.do/oauth2/token', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ client_id: CLIENT_ID,
+ client_secret: CLIENT_SECRET,
+ code,
+ redirect_uri: REDIRECT_URI,
+ grant_type: 'authorization_code',
+ }),
+ });
+
+ const tokenData = await tokenResponse.json();
+
+ if (!tokenData.access_token) {
+ throw new Error('Failed to get access token');
+ }
+
+ // 2. Get User Info
+ const userResponse = await fetch('https://connect.linux.do/api/user', {
+ headers: { Authorization: `Bearer ${tokenData.access_token}` },
+ });
+
+ const userData = await userResponse.json();
+
+ // 3. Redirect with data
+ const redirectUrl = new URL('/settings', request.url);
+ redirectUrl.searchParams.set('auth_success', 'true');
+ redirectUrl.searchParams.set('token', tokenData.access_token);
+ redirectUrl.searchParams.set('user', JSON.stringify(userData));
+
+ return NextResponse.redirect(redirectUrl);
+ } catch (error) {
+ console.error('OAuth Error:', error);
+ return NextResponse.redirect(new URL('/settings?error=auth_failed', request.url));
+ }
+}
diff --git a/app/settings/page.tsx b/app/settings/page.tsx
index 18fc909..e94d291 100644
--- a/app/settings/page.tsx
+++ b/app/settings/page.tsx
@@ -8,6 +8,7 @@ import { SourceSettings } from '@/components/settings/SourceSettings';
import { SortSettings } from '@/components/settings/SortSettings';
import { DataSettings } from '@/components/settings/DataSettings';
import { SettingsHeader } from '@/components/settings/SettingsHeader';
+import { AccountSettings } from '@/components/settings/AccountSettings';
import { useSettingsPage } from './hooks/useSettingsPage';
export default function SettingsPage() {
@@ -38,10 +39,13 @@ export default function SettingsPage() {
return (
-
+
{/* Header */}
+ {/* Account Settings */}
+
+
{/* Source Management */}
{
+ const authSuccess = searchParams.get('auth_success');
+ const token = searchParams.get('token');
+ const userStr = searchParams.get('user');
+
+ if (authSuccess && token && userStr) {
+ try {
+ const userData = JSON.parse(userStr) as LinuxDoUser;
+ // Clear existing data before logging in new user
+ clearHistory();
+ clearSearchHistory();
+
+ login(userData, token);
+
+ // Clean URL
+ router.replace('/settings');
+ } catch (e) {
+ console.error('Failed to parse user data', e);
+ }
+ }
+ }, [searchParams, login, clearHistory, clearSearchHistory, router]);
+
+ const handleLogin = () => {
+ // Clear data before redirecting to ensure clean state
+ clearHistory();
+ clearSearchHistory();
+ router.push('/api/oauth/authorize');
+ };
+
+ const handleLogout = () => {
+ logout();
+ clearHistory();
+ clearSearchHistory();
+ };
+
+ return (
+
+
+
+
+ {!isAuthenticated ? (
+
+
登录 Linux DO 以同步您的账号信息
+
+
+ ) : (
+
+
+

+
+
{user?.name || user?.username}
+
@{user?.username}
+
+
+ Trust Level {user?.trust_level}
+
+ {user?.active && (
+
+ Active
+
+ )}
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+}
diff --git a/components/settings/SettingsHeader.tsx b/components/settings/SettingsHeader.tsx
index 03281e6..de72f2b 100644
--- a/components/settings/SettingsHeader.tsx
+++ b/components/settings/SettingsHeader.tsx
@@ -4,7 +4,7 @@ export function SettingsHeader() {
const router = useRouter();
return (
-
+