refactor: Redesign homepage and header with responsive layout and improved styling

This commit is contained in:
wood chen 2025-02-22 01:30:42 +08:00
parent 3bbf87d875
commit ce3baad450
3 changed files with 271 additions and 120 deletions

View File

@ -1,5 +1,5 @@
import Link from "next/link";
import { ArrowRight, CheckCircle2 } from "lucide-react";
import { ArrowRight, CheckCircle2, Code2, Lock, Users } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
@ -25,20 +25,29 @@ export default function HomePage() {
<div className="flex min-h-screen flex-col">
<Header />
<main className="flex-1">
<Section className="pb-0">
{/* Hero Section */}
<Section className="relative overflow-hidden bg-gradient-to-b from-white to-gray-50 pb-16 pt-24 dark:from-gray-900 dark:to-gray-800">
<div className="absolute inset-0">
<div className="bg-grid-black/[0.02] dark:bg-grid-white/[0.02] absolute inset-0" />
</div>
<Container>
<div className="flex flex-col items-center justify-center space-y-8 text-center">
<TypographyH1 className="text-4xl font-bold tracking-tighter sm:text-5xl md:text-6xl lg:text-7xl">
Q58 Connect
</TypographyH1>
<TypographyLead className="max-w-[600px]">
使 Q58
</TypographyLead>
<div className="relative flex flex-col items-center justify-center space-y-8 text-center">
<div className="space-y-6">
<h1 className="text-4xl font-bold tracking-tighter text-gray-900 dark:text-white sm:text-5xl md:text-6xl lg:text-7xl">
Q58 Connect
</h1>
<div className="mx-auto max-w-[800px] px-4">
<p className="text-base text-gray-500 dark:text-gray-400 sm:text-lg md:text-xl">
OAuth 2.0 Q58
</p>
</div>
</div>
<div className="flex gap-4">
<Link href="/sign-in">
<Button size="lg">
<Button size="lg" className="gap-2">
使
<ArrowRight className="ml-2 h-5 w-5" />
<ArrowRight className="h-5 w-5" />
</Button>
</Link>
</div>
@ -46,18 +55,20 @@ export default function HomePage() {
</Container>
</Section>
<Section>
{/* Features Section */}
<Section className="bg-white py-24 dark:bg-gray-900">
<Container>
<div className="grid gap-8 md:grid-cols-3">
<Card>
<Card className="border-2 bg-white/50 transition-colors hover:border-primary/50 dark:bg-gray-800/50">
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
<Code2 className="h-10 w-10 text-primary" />
<CardTitle className="text-xl"></CardTitle>
<CardDescription className="text-base">
Q58
</CardDescription>
</CardHeader>
<CardContent>
<ul className="space-y-2">
<ul className="space-y-4 text-sm">
<li className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" />
<span> OAuth 2.0 </span>
@ -74,15 +85,16 @@ export default function HomePage() {
</CardContent>
</Card>
<Card>
<Card className="border-2 bg-white/50 transition-colors hover:border-primary/50 dark:bg-gray-800/50">
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
<Lock className="h-10 w-10 text-primary" />
<CardTitle className="text-xl"></CardTitle>
<CardDescription className="text-base">
</CardDescription>
</CardHeader>
<CardContent>
<ul className="space-y-2">
<ul className="space-y-4 text-sm">
<li className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" />
<span>HTTPS </span>
@ -99,15 +111,16 @@ export default function HomePage() {
</CardContent>
</Card>
<Card>
<Card className="border-2 bg-white/50 transition-colors hover:border-primary/50 dark:bg-gray-800/50">
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
<Users className="h-10 w-10 text-primary" />
<CardTitle className="text-xl"></CardTitle>
<CardDescription className="text-base">
</CardDescription>
</CardHeader>
<CardContent>
<ul className="space-y-2">
<ul className="space-y-4 text-sm">
<li className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" />
<span></span>
@ -127,34 +140,36 @@ export default function HomePage() {
</Container>
</Section>
<Section>
{/* Documentation Section */}
<Section className="border-t bg-gray-50 py-24 dark:bg-gray-800">
<Container>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
Q58 Connect
</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="auth" className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="auth"></TabsTrigger>
<TabsTrigger value="callback"></TabsTrigger>
<TabsTrigger value="userinfo"></TabsTrigger>
</TabsList>
<TabsContent value="auth" className="mt-4">
<Card>
<CardHeader>
<CardTitle>1. </CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<pre className="overflow-x-auto rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
<code className="text-sm">
{`const authUrl = 'https://connect.q58.club/oauth/authorize?' +
<div className="mx-auto max-w-4xl">
<Card className="overflow-hidden border-2 p-6">
<CardHeader className="bg-white dark:bg-gray-900">
<CardTitle className="text-2xl"></CardTitle>
<CardDescription className="text-base">
Q58 Connect
</CardDescription>
</CardHeader>
<CardContent className="bg-white p-0 dark:bg-gray-900">
<Tabs defaultValue="auth" className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="auth"></TabsTrigger>
<TabsTrigger value="callback"></TabsTrigger>
<TabsTrigger value="userinfo"></TabsTrigger>
</TabsList>
<TabsContent value="auth" className="mt-4">
<Card>
<CardHeader>
<CardTitle>1. </CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<pre className="overflow-x-auto rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
<code className="text-sm">
{`const authUrl = 'https://connect.q58.club/oauth/authorize?' +
new URLSearchParams({
response_type: 'code', // 必填,固定值
client_id: 'your_client_id', // 必填您的应用ID
@ -164,23 +179,23 @@ export default function HomePage() {
});
window.location.href = authUrl;`}
</code>
</pre>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="callback" className="mt-4">
<Card>
<CardHeader>
<CardTitle>2. </CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<pre className="overflow-x-auto rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
<code className="text-sm">
{`// 获取访问令牌
</code>
</pre>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="callback" className="mt-4">
<Card>
<CardHeader>
<CardTitle>2. </CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<pre className="overflow-x-auto rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
<code className="text-sm">
{`// 获取访问令牌
const response = await fetch('https://connect.q58.club/api/oauth/access_token', {
method: 'POST',
headers: {
@ -193,23 +208,23 @@ const response = await fetch('https://connect.q58.club/api/oauth/access_token',
});
const { access_token, expires_in } = await response.json();`}
</code>
</pre>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="userinfo" className="mt-4">
<Card>
<CardHeader>
<CardTitle>3. </CardTitle>
<CardDescription>
使访
</CardDescription>
</CardHeader>
<CardContent>
<pre className="overflow-x-auto rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
<code className="text-sm">
{`const userInfo = await fetch('https://connect.q58.club/api/oauth/user', {
</code>
</pre>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="userinfo" className="mt-4">
<Card>
<CardHeader>
<CardTitle>3. </CardTitle>
<CardDescription>
使访
</CardDescription>
</CardHeader>
<CardContent>
<pre className="overflow-x-auto rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
<code className="text-sm">
{`const userInfo = await fetch('https://connect.q58.club/api/oauth/user', {
headers: {
'Authorization': \`Bearer \${access_token}\`
}
@ -224,27 +239,31 @@ const { access_token, expires_in } = await response.json();`}
"avatar_url": "https://...",
"groups": ["group1", "group2"]
}`}
</code>
</pre>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</CardContent>
</Card>
</code>
</pre>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</CardContent>
</Card>
</div>
</Container>
</Section>
<Section>
{/* CTA Section */}
<Section className="bg-white py-24 dark:bg-gray-900">
<Container>
<div className="text-center">
<TypographyH2></TypographyH2>
<TypographyMuted className="mt-2">
<h2 className="text-3xl font-bold"></h2>
<p className="mt-2 text-gray-500 dark:text-gray-400">
使 Q58 Connect
</TypographyMuted>
<div className="mt-4">
</p>
<div className="mt-8">
<Link href="/sign-in">
<Button size="lg"></Button>
<Button size="lg" className="min-w-[200px]">
</Button>
</Link>
</div>
</div>
@ -252,9 +271,9 @@ const { access_token, expires_in } = await response.json();`}
</Section>
</main>
<footer className="border-t bg-white py-8 dark:bg-gray-800">
<footer className="border-t bg-white py-12 dark:bg-gray-900">
<Container>
<div className="text-center text-gray-600 dark:text-gray-400">
<div className="text-center text-sm text-gray-500 dark:text-gray-400">
© 2024{" "}
<a
href="https://q58.club"

View File

@ -3,7 +3,7 @@
import { useEffect, useState } from "react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { ChevronDown, User } from "lucide-react";
import { ChevronDown, Menu, User } from "lucide-react";
import { signOut } from "next-auth/react";
import { cn } from "@/lib/utils";
@ -38,7 +38,7 @@ export function HeaderClient({ initialUser }: HeaderClientProps) {
if (!user) return null;
return (
<div className="ml-8 flex items-center space-x-1">
<div className="ml-8 hidden items-center space-x-1 md:flex">
<Link
href="/dashboard"
className={cn(
@ -155,27 +155,157 @@ export function HeaderClient({ initialUser }: HeaderClientProps) {
);
};
// 移动端导航菜单
const renderMobileMenu = () => {
if (!user) return null;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="md:hidden">
<Menu className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-64">
<div className="flex items-center gap-3 border-b px-2 py-3">
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatarUrl || undefined} />
<AvatarFallback>
{user.name?.charAt(0) || user.username?.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<span className="text-sm font-medium">
{user.name || user.username}
</span>
<span className="text-xs text-muted-foreground">
{user.email}
</span>
</div>
</div>
<DropdownMenuItem asChild>
<Link
href="/dashboard"
className={cn("w-full", pathname === "/dashboard" && "bg-accent")}
>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href="/dashboard/clients"
className={cn(
"w-full",
pathname.startsWith("/dashboard/clients") && "bg-accent",
)}
>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href="/dashboard/settings"
className={cn(
"w-full",
pathname === "/dashboard/settings" && "bg-accent",
)}
>
</Link>
</DropdownMenuItem>
{user.role === "ADMIN" && (
<>
<div className="my-1 h-px bg-border" />
<DropdownMenuItem asChild>
<Link
href="/admin"
className={cn("w-full", pathname === "/admin" && "bg-accent")}
>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href="/admin/users"
className={cn(
"w-full",
pathname === "/admin/users" && "bg-accent",
)}
>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href="/admin/clients"
className={cn(
"w-full",
pathname === "/admin/clients" && "bg-accent",
)}
>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href="/admin/authorizations"
className={cn(
"w-full",
pathname === "/admin/authorizations" && "bg-accent",
)}
>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href="/admin/logs"
className={cn(
"w-full",
pathname === "/admin/logs" && "bg-accent",
)}
>
</Link>
</DropdownMenuItem>
</>
)}
<div className="my-1 h-px bg-border" />
<DropdownMenuItem
onClick={handleSignOut}
className="text-red-600 focus:bg-red-50 focus:text-red-600 dark:focus:bg-red-950"
>
退
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};
return (
<div className="flex items-center space-x-4">
{renderNavLinks()}
<ThemeToggle />
{user ? (
<div className="flex items-center gap-3">
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatarUrl || undefined} />
<AvatarFallback>
{user.name?.charAt(0) || user.username?.charAt(0)}
</AvatarFallback>
</Avatar>
<Button
variant="ghost"
size="sm"
onClick={handleSignOut}
className="text-muted-foreground hover:text-red-600"
>
退
</Button>
</div>
<>
<div className="hidden items-center gap-3 md:flex">
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatarUrl || undefined} />
<AvatarFallback>
{user.name?.charAt(0) || user.username?.charAt(0)}
</AvatarFallback>
</Avatar>
<Button
variant="ghost"
size="sm"
onClick={handleSignOut}
className="text-muted-foreground hover:text-red-600"
>
退
</Button>
</div>
{renderMobileMenu()}
</>
) : (
<Button
variant="outline"

View File

@ -1,6 +1,7 @@
import Link from "next/link";
import { getCurrentUser } from "@/lib/session";
import DynamicLogo from "@/components/dynamic-logo";
import { HeaderClient } from "@/components/layout/header-client";
export async function Header() {
@ -12,7 +13,8 @@ export async function Header() {
<div className="flex h-14 items-center justify-between">
<div className="flex items-center">
<Link href="/" className="flex items-center space-x-3">
<h1 className="text-2xl font-bold text-[#25263A] dark:text-white">
<DynamicLogo />
<h1 className="hidden text-2xl font-bold text-[#25263A] dark:text-white sm:block">
Q58 Connect
</h1>
</Link>