压缩历史至 95081aff

This commit is contained in:
ryan
2026-06-08 20:34:27 +08:00
commit 8a782525de
435 changed files with 71146 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
import { Suspense } from "react"
import { LoginPage } from "@/components/auth/login-page"
export default function Page() {
return (
<Suspense>
<LoginPage />
</Suspense>
)
}
+18
View File
@@ -0,0 +1,18 @@
import { LegalPageLayout } from "@/components/common/docs/legal-page-layout";
import { apiSections, DOCS_LAST_UPDATED } from "@/components/common/docs/api";
export default function ApiDocPage() {
return (
<LegalPageLayout
title="API 接口文档"
lastUpdated={DOCS_LAST_UPDATED}
sections={apiSections}
description={
<p className="text-muted-foreground text-sm leading-relaxed">
LINUX DO Credit 提供简单、强大的 API 接口,支持多种编程语言和开发环境。通过标准化的 HTTP 接口,您可以轻松将积分服务集成到您的应用中。
</p>
}
/>
);
}
@@ -0,0 +1,19 @@
import { LegalPageLayout } from "@/components/common/docs/legal-page-layout";
import { howToUseSections } from "@/components/common/docs/how-to-use";
import { DOCS_LAST_UPDATED } from "@/components/common/docs/api";
export default function HowToUsePage() {
return (
<LegalPageLayout
title="平台使用指南"
lastUpdated={DOCS_LAST_UPDATED}
sections={howToUseSections}
description={
<p className="text-muted-foreground text-sm leading-relaxed">
欢迎使用 LINUX DO Credit。本文档将引导您快速了解平台的核心功能、角色定义及使用流程,帮助您更好地使用我们的服务。
</p>
}
/>
);
}
@@ -0,0 +1,20 @@
import { LegalPageLayout } from "@/components/common/docs/legal-page-layout"
import { privacySections } from "@/components/common/docs/privacy"
import { TERMS_LAST_UPDATED } from "@/components/common/docs/terms"
export default function PrivacyPolicyPage() {
return (
<LegalPageLayout
title="隐私政策 (Privacy)"
lastUpdated={TERMS_LAST_UPDATED}
sections={privacySections}
description={
<span>
我们非常重视您的隐私保护。本政策将向您透明地展示我们如何收集、使用、存储您的个人信息,
<br className="hidden md:block" />
以及您享有的相关权利。
</span>
}
/>
)
}
@@ -0,0 +1,19 @@
import { LegalPageLayout } from "@/components/common/docs/legal-page-layout"
import { termsSections, TERMS_LAST_UPDATED } from "@/components/common/docs/terms"
export default function TermsOfServicePage() {
return (
<LegalPageLayout
title="服务协议 (Terms)"
lastUpdated={TERMS_LAST_UPDATED}
sections={termsSections}
description={
<span>
欢迎使用 LINUX DO Credit。本协议详述了您在使用本平台服务时的权利与义务。
<br className="hidden md:block" />
为了保障您的合法权益,请您仔细阅读以下条款。
</span>
}
/>
)
}
+29
View File
@@ -0,0 +1,29 @@
"use client"
import { useEffect } from "react"
import { UserPayConfigs } from "@/components/common/admin/credit"
import { useUser } from "@/contexts/user-context"
import { AdminProvider, useAdmin } from "@/contexts/admin-context"
/* 用户积分配置页面 */
export default function UserPayConfigPage() {
return (
<AdminProvider>
<UserPayConfigPageContent />
</AdminProvider>
)
}
/* 用户积分配置页面内容 */
function UserPayConfigPageContent() {
const { user } = useUser()
const { refetchUserPayConfigs } = useAdmin()
useEffect(() => {
if (user?.is_admin) {
refetchUserPayConfigs()
}
}, [user?.is_admin, refetchUserPayConfigs])
return <UserPayConfigs />
}
+35
View File
@@ -0,0 +1,35 @@
"use client"
import { AdminUsersProvider } from "@/contexts/admin-users-context"
import { useUser } from "@/contexts/user-context"
import { LoadingPage } from "@/components/layout/loading"
import { ErrorPage } from "@/components/layout/error"
export default function AdminLayout({
children,
}: {
children: React.ReactNode
}) {
const { user, loading } = useUser()
/* 等待用户信息加载完成 */
if (loading) {
return <LoadingPage text="管理后台" badgeText="管理" />
}
/* 权限检查:只有管理员才能访问 */
if (!user?.is_admin) {
return (
<ErrorPage
title="访问被拒绝"
message="您没有权限访问此页面"
/>
)
}
return (
<AdminUsersProvider>
{children}
</AdminUsersProvider>
)
}
+30
View File
@@ -0,0 +1,30 @@
"use client"
import { useEffect } from "react"
import { SystemConfigs } from "@/components/common/admin/system"
import { useUser } from "@/contexts/user-context"
import { AdminProvider, useAdmin } from "@/contexts/admin-context"
/* 系统配置页面 */
export default function SystemConfigPage() {
return (
<AdminProvider>
<SystemConfigPageContent />
</AdminProvider>
)
}
/* 系统配置页面内容 */
function SystemConfigPageContent() {
const { user } = useUser()
const { refetchSystemConfigs } = useAdmin()
useEffect(() => {
if (user?.is_admin) {
refetchSystemConfigs()
}
}, [user?.is_admin, refetchSystemConfigs])
return <SystemConfigs />
}
+8
View File
@@ -0,0 +1,8 @@
"use client"
import { TaskManager } from "@/components/common/admin/tasks"
/* 任务管理页面 */
export default function TasksPage() {
return <TaskManager />
}
+8
View File
@@ -0,0 +1,8 @@
"use client"
import { UsersManager } from "@/components/common/admin/users"
/* 用户管理页面 */
export default function UsersPage() {
return <UsersManager />
}
+5
View File
@@ -0,0 +1,5 @@
import { BalanceMain } from "@/components/common/balance/balance-main"
export default function BalancePage() {
return <BalanceMain />
}
+5
View File
@@ -0,0 +1,5 @@
import { HomeMain } from "@/components/common/home/home-main"
export default function HomePage() {
return <HomeMain />
}
+57
View File
@@ -0,0 +1,57 @@
"use client"
import { motion } from "motion/react"
import { usePathname } from "next/navigation"
import { useState } from "react"
import { AppSidebar } from "@/components/layout/sidebar"
import { SiteHeader } from "@/components/layout/header"
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
import { UserProvider } from "@/contexts/user-context"
import { MerchantProvider } from "@/contexts/merchant-context"
export default function MainLayout({
children,
}: {
children: React.ReactNode
}) {
const pathname = usePathname()
const [isFullWidth, setIsFullWidth] = useState(false)
return (
<UserProvider>
<MerchantProvider>
<SidebarProvider
className="h-screen"
style={
{
"--header-height": "60px",
} as React.CSSProperties
}
>
<AppSidebar />
<SidebarInset className="flex flex-col min-w-0 h-screen">
<SiteHeader isFullWidth={isFullWidth} onToggleFullWidth={setIsFullWidth} />
<div className="flex flex-1 flex-col bg-background overflow-y-auto overflow-x-hidden min-w-0 hide-scrollbar">
<div className={`w-full mx-auto px-4 sm:px-6 md:px-8 lg:px-12 min-w-0 transition-all duration-300 ease-in-out ${!isFullWidth ? "max-w-[1320px]" : "max-w-full"}`}>
<motion.div
key={pathname}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
duration: 0.5,
ease: "easeOut",
}}
className="w-full"
>
{children}
</motion.div>
</div>
</div>
</SidebarInset>
</SidebarProvider>
</MerchantProvider>
</UserProvider>
)
}
+5
View File
@@ -0,0 +1,5 @@
import { LeaderboardMain } from "@/components/common/leaderboard/leaderboard-main"
export default function LeaderboardPage() {
return <LeaderboardMain />
}
@@ -0,0 +1,10 @@
import { Suspense } from "react"
import { MerchantOnline } from "@/components/common/merchant/merchant-online"
export default function Page() {
return (
<Suspense>
<MerchantOnline />
</Suspense>
)
}
+5
View File
@@ -0,0 +1,5 @@
import { MerchantMain } from "@/components/common/merchant/merchant-main"
export default function MerchantPage() {
return <MerchantMain />
}
@@ -0,0 +1,5 @@
import { AppearanceMain } from "@/components/common/settings/appearance"
export default function AppearancePage() {
return <AppearanceMain />
}
@@ -0,0 +1,5 @@
import { NotificationsMain } from "@/components/common/settings/notifications"
export default function NotificationsPage() {
return <NotificationsMain />
}
+77
View File
@@ -0,0 +1,77 @@
import Link from "next/link"
import { Card, CardTitle, CardDescription, CardContent } from "@/components/ui/card"
import { UserRound, Bell, Shield, Palette } from "lucide-react"
/* 设置项 */
const settingsItems = [
{
title: "个人资料",
description: "查看您的账户信息",
icon: UserRound,
href: "/settings/profile",
category: "个人设置",
},
{
title: "通知设置",
description: "设置您的通知偏好",
icon: Bell,
href: "/settings/notifications",
category: "账户设置",
},
{
title: "安全设置",
description: "管理您的账户安全",
icon: Shield,
href: "/settings/security",
category: "账户设置",
},
{
title: "外观设置",
description: "自定义界面主题和显示",
icon: Palette,
href: "/settings/appearance",
category: "个人设置",
},
]
export default function SettingsPage() {
const groupedSettings = settingsItems.reduce((acc, item) => {
if (!acc[item.category]) {
acc[item.category] = []
}
acc[item.category].push(item)
return acc
}, {} as Record<string, typeof settingsItems>)
return (
<div className="space-y-6 py-6">
<div className="font-semibold">设置</div>
{Object.entries(groupedSettings).map(([category, items]) => (
<div key={category} className="space-y-4">
<div className="font-medium text-sm text-muted-foreground">{category}</div>
<div className="grid grid-cols-2 gap-4">
{items.map((item) => (
<Link key={item.href} href={item.href}>
<Card className="py-2 border border-dashed shadow-none hover:bg-muted/50 transition-colors cursor-pointer h-full">
<CardContent>
<div className="flex items-center gap-4">
<item.icon className="size-5 text-primary" />
<div>
<CardTitle className="mb-1 text-sm">{item.title}</CardTitle>
<CardDescription className="text-xs">
{item.description}
</CardDescription>
</div>
</div>
</CardContent>
</Card>
</Link>
))}
</div>
</div>
))}
</div>
)
}
@@ -0,0 +1,5 @@
import { ProfileMain } from "@/components/common/settings/profile"
export default function ProfilePage() {
return <ProfileMain />
}
@@ -0,0 +1,5 @@
import { SecurityMain } from "@/components/common/settings/security"
export default function SecurityPage() {
return <SecurityMain />
}
+5
View File
@@ -0,0 +1,5 @@
import { TradeMain } from "@/components/common/trade/trade-main"
export default function TradePage() {
return <TradeMain />
}
+22
View File
@@ -0,0 +1,22 @@
"use client"
import { MerchantProvider } from "@/contexts/merchant-context"
import { UserProvider } from "@/contexts/user-context"
/**
* 积分服务页面布局
* 为积分服务相关的页面提供社区用户和开发者上下文
*/
export default function PayLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<UserProvider>
<MerchantProvider>
{children}
</MerchantProvider>
</UserProvider>
)
}
+10
View File
@@ -0,0 +1,10 @@
import { Suspense } from "react"
import { PayingOnline } from "@/components/common/pay/paying/paying-online"
export default function Page() {
return (
<Suspense>
<PayingOnline />
</Suspense >
)
}
+10
View File
@@ -0,0 +1,10 @@
import { Suspense } from "react"
import { PayingMain } from "@/components/common/pay/paying/paying-main"
export default function Page() {
return (
<Suspense>
<PayingMain />
</Suspense>
)
}
+16
View File
@@ -0,0 +1,16 @@
/**
* 红包页面布局
* 提供简洁的布局,不包含侧边栏和用户上下文
* 允许未登录用户访问红包详情
*/
export default function RedEnvelopeLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="min-h-screen w-full">
{children}
</div>
)
}
@@ -0,0 +1,32 @@
import type { Metadata } from "next"
import { headers } from "next/headers"
import { RedEnvelopeClaimPage } from "@/components/common/redenvelope/red-envelope-claim"
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
const { id } = await params
const requestHeaders = await headers()
const protocol = requestHeaders.get("x-forwarded-proto") ?? "https"
const host = requestHeaders.get("x-forwarded-host") ?? requestHeaders.get("host") ?? ""
const baseUrl = host ? `${protocol}://${host}` : ""
const title = "你收到一个LDC红包"
const description = "点击打开领取"
const url = baseUrl ? `${baseUrl}/redenvelope/${id}` : undefined
const image = baseUrl ? `${baseUrl}/red-envelope.svg` : "/red-envelope.svg"
return {
title,
description,
openGraph: {
title,
description,
url,
type: "website",
images: [{ url: image, width: 384, height: 512, alt: "红包" }],
}
}
}
export default async function RedEnvelopePage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
return <RedEnvelopeClaimPage id={id} />
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+196
View File
@@ -0,0 +1,196 @@
@import "tailwindcss";
@import "tw-animate-css";
@plugin "@tailwindcss/typography";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans), system-ui, -apple-system, BlinkMacSystemFont, var(--font-noto-sans-sc), sans-serif;
--font-mono: var(--font-geist-mono), 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', var(--font-noto-sans-sc), monospace;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(58.5% 51% 277.117);
--primary-foreground: oklch(98.5% 0% 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: hsl(0 0% 98%);
--sidebar-foreground: hsl(240 5.3% 26.1%);
--sidebar-primary: hsl(240 5.9% 10%);
--sidebar-primary-foreground: hsl(0 0% 98%);
--sidebar-accent: hsl(240 4.8% 95.9%);
--sidebar-accent-foreground: hsl(240 5.9% 10%);
--sidebar-border: hsl(220 13% 91%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
}
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: hsl(240 5.9% 10%);
--sidebar-foreground: hsl(240 4.8% 95.9%);
--sidebar-primary: hsl(224.3 76.3% 48%);
--sidebar-primary-foreground: hsl(0 0% 100%);
--sidebar-accent: hsl(240 3.7% 15.9%);
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
--sidebar-border: hsl(240 3.7% 15.9%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
@theme inline {
/*
* Aurora 动画配置
*/
--animate-aurora: aurora 120s linear infinite;
@keyframes aurora {
from {
background-position:
50% 50%,
50% 50%;
}
to {
background-position:
350% 50%,
350% 50%;
}
}
/*
* 红包动画配置
*/
--animate-redenvelope-shake: redenvelope-shake 0.5s ease-in-out;
--animate-redenvelope-glow: redenvelope-glow 2s ease-in-out infinite;
--animate-gold-particle: gold-particle 1s ease-out forwards;
@keyframes redenvelope-shake {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(-5deg); }
75% { transform: rotate(5deg); }
}
@keyframes redenvelope-glow {
0%, 100% { box-shadow: 0 0 20px rgba(255, 215, 0, 0.3); }
50% { box-shadow: 0 0 40px rgba(255, 215, 0, 0.6); }
}
@keyframes gold-particle {
0% { transform: translateY(0) scale(1); opacity: 1; }
100% { transform: translateY(-100px) scale(0); opacity: 0; }
}
/*
* 铃铛响铃动画配置
* 使用微妙的摆动效果,优雅且不过度
*/
--animate-bell-ring: bell-ring 0.6s cubic-bezier(0.36, 0.07, 0.19, 0.97);
@keyframes bell-ring {
0% { transform: rotate(0deg); }
10% { transform: rotate(14deg); }
20% { transform: rotate(-12deg); }
30% { transform: rotate(10deg); }
40% { transform: rotate(-8deg); }
50% { transform: rotate(6deg); }
60% { transform: rotate(-4deg); }
70% { transform: rotate(2deg); }
80% { transform: rotate(-1deg); }
100% { transform: rotate(0deg); }
}
}
+67
View File
@@ -0,0 +1,67 @@
import type { Metadata } from "next";
import { Inter, Noto_Sans_SC, Geist_Mono } from 'next/font/google';
import { Toaster } from "@/components/ui/sonner";
import { ThemeProvider } from "@/components/layout/theme-provider";
import { CustomThemeProvider } from "@/lib/theme";
import { BellRingProvider } from "@/contexts/bell-ring-context";
import { NotificationSettingsProvider } from "@/contexts/notification-settings-context";
import { RiskBlockDialog } from "@/components/common/risk/risk-block-dialog";
import "./globals.css";
const inter = Inter({
variable: '--font-inter',
subsets: ['latin'],
display: 'swap',
});
const notoSansSC = Noto_Sans_SC({
variable: '--font-noto-sans-sc',
subsets: ['latin'],
display: 'swap',
weight: ['300', '400', '500', '600', '700']
});
const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
});
export const metadata: Metadata = {
title: "LINUX DO Credit",
description: "Linux Do 社区积分服务平台",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html
lang="zh-CN"
className={`${ inter.variable } ${ notoSansSC.variable } ${ geistMono.variable } hide-scrollbar font-sans`}
suppressHydrationWarning
>
<body
className={`${ inter.variable } ${ notoSansSC.variable } ${ geistMono.variable } hide-scrollbar font-sans antialiased`}
>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<CustomThemeProvider>
<NotificationSettingsProvider>
<BellRingProvider>
{children}
<RiskBlockDialog />
<Toaster position="top-center" />
</BellRingProvider>
</NotificationSettingsProvider>
</CustomThemeProvider>
</ThemeProvider>
</body>
</html>
);
}
+79
View File
@@ -0,0 +1,79 @@
"use client"
import { useRouter } from "next/navigation";
import Link from "next/link";
import { motion } from "motion/react";
import { Home, ArrowLeft } from "lucide-react";
import { Button } from "@/components/ui/button";
/**
* 404 Not Found Page
* 当用户访问不存在的页面时显示
*/
export default function NotFound() {
const router = useRouter();
return (
<div className="relative min-h-screen w-full flex flex-col items-center justify-center bg-background overflow-hidden selection:bg-primary/20">
<div className="absolute inset-0 -z-10 overflow-hidden">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-primary/20 rounded-full blur-[120px] opacity-20 animate-pulse" />
</div>
<div className="container px-6 flex flex-col items-center text-center z-10">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8, ease: "easeOut" }}
className="relative mb-6"
>
<h1 className="text-[12rem] md:text-[16rem] font-bold leading-none tracking-tighter text-transparent bg-clip-text bg-gradient-to-b from-foreground/10 to-foreground/5 select-none">
404
</h1>
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-2xl font-medium tracking-[0.2em] text-foreground/80 uppercase">Page Not Found</span>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.8 }}
className="space-y-8"
>
<p className="text-muted-foreground max-w-[400px] mx-auto text-sm leading-relaxed">
抱歉,您访问的页面似已迷失在数字星云中。
<br />
请检查路径或返回安全地带。
</p>
<div className="flex justify-center gap-4">
<Button
variant="secondary"
size="sm"
onClick={() => router.back()}
className="rounded-full w-24 text-xs border-foreground/10 hover:bg-foreground/10 transition-all duration-300"
>
<ArrowLeft className="size-3 opacity-70" />
上一页
</Button>
<Link href="/">
<Button
variant="default"
size="sm"
className="rounded-full w-24 text-xs hover:bg-primary/80 transition-all duration-300"
>
<Home className="size-3" />
首页
</Button>
</Link>
</div>
</motion.div>
</div>
<div className="absolute bottom-8 text-xs text-muted-foreground/30 font-mono">
ERR_HTTP_NOT_FOUND
</div>
</div>
);
}
+15
View File
@@ -0,0 +1,15 @@
"use client";
import { HeroSection } from "@/components/home/hero-section";
import { DeveloperSection } from "@/components/home/developer-section";
import { FooterSection } from "@/components/home/footer-section";
export default function HomePage() {
return (
<div className="relative w-full min-h-screen bg-transparent text-foreground font-sans overflow-x-hidden selection:bg-primary selection:text-primary-foreground z-10">
<HeroSection />
<DeveloperSection />
<FooterSection />
</div>
)
}