mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
压缩历史至 95081aff
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import { Suspense } from "react"
|
||||
import { LoginPage } from "@/components/auth/login-page"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<LoginPage />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -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 />
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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 />
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { TaskManager } from "@/components/common/admin/tasks"
|
||||
|
||||
/* 任务管理页面 */
|
||||
export default function TasksPage() {
|
||||
return <TaskManager />
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { UsersManager } from "@/components/common/admin/users"
|
||||
|
||||
/* 用户管理页面 */
|
||||
export default function UsersPage() {
|
||||
return <UsersManager />
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { BalanceMain } from "@/components/common/balance/balance-main"
|
||||
|
||||
export default function BalancePage() {
|
||||
return <BalanceMain />
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { HomeMain } from "@/components/common/home/home-main"
|
||||
|
||||
export default function HomePage() {
|
||||
return <HomeMain />
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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 />
|
||||
}
|
||||
@@ -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 />
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { TradeMain } from "@/components/common/trade/trade-main"
|
||||
|
||||
export default function TradePage() {
|
||||
return <TradeMain />
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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 >
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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 |
@@ -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); }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user