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

View File

@ -3,7 +3,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { usePathname, useRouter } from "next/navigation"; 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 { signOut } from "next-auth/react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -38,7 +38,7 @@ export function HeaderClient({ initialUser }: HeaderClientProps) {
if (!user) return null; if (!user) return null;
return ( return (
<div className="ml-8 flex items-center space-x-1"> <div className="ml-8 hidden items-center space-x-1 md:flex">
<Link <Link
href="/dashboard" href="/dashboard"
className={cn( 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 ( return (
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
{renderNavLinks()} {renderNavLinks()}
<ThemeToggle /> <ThemeToggle />
{user ? ( {user ? (
<div className="flex items-center gap-3"> <>
<Avatar className="h-8 w-8"> <div className="hidden items-center gap-3 md:flex">
<AvatarImage src={user.avatarUrl || undefined} /> <Avatar className="h-8 w-8">
<AvatarFallback> <AvatarImage src={user.avatarUrl || undefined} />
{user.name?.charAt(0) || user.username?.charAt(0)} <AvatarFallback>
</AvatarFallback> {user.name?.charAt(0) || user.username?.charAt(0)}
</Avatar> </AvatarFallback>
<Button </Avatar>
variant="ghost" <Button
size="sm" variant="ghost"
onClick={handleSignOut} size="sm"
className="text-muted-foreground hover:text-red-600" onClick={handleSignOut}
> className="text-muted-foreground hover:text-red-600"
退 >
</Button> 退
</div> </Button>
</div>
{renderMobileMenu()}
</>
) : ( ) : (
<Button <Button
variant="outline" variant="outline"

View File

@ -1,6 +1,7 @@
import Link from "next/link"; import Link from "next/link";
import { getCurrentUser } from "@/lib/session"; import { getCurrentUser } from "@/lib/session";
import DynamicLogo from "@/components/dynamic-logo";
import { HeaderClient } from "@/components/layout/header-client"; import { HeaderClient } from "@/components/layout/header-client";
export async function Header() { export async function Header() {
@ -12,7 +13,8 @@ export async function Header() {
<div className="flex h-14 items-center justify-between"> <div className="flex h-14 items-center justify-between">
<div className="flex items-center"> <div className="flex items-center">
<Link href="/" className="flex items-center space-x-3"> <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 Q58 Connect
</h1> </h1>
</Link> </Link>