Update soul-content project

This commit is contained in:
卡若
2025-12-29 14:01:37 +08:00
commit 087849d509
1112 changed files with 401606 additions and 0 deletions

BIN
app/admin/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,82 @@
"use client"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Textarea } from "@/components/ui/textarea"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
export default function ContentPage() {
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h2 className="text-3xl font-bold tracking-tight"></h2>
<Button></Button>
</div>
<Tabs defaultValue="chapters" className="space-y-4">
<TabsList>
<TabsTrigger value="chapters"></TabsTrigger>
<TabsTrigger value="articles"></TabsTrigger>
<TabsTrigger value="hooks"></TabsTrigger>
</TabsList>
<TabsContent value="chapters" className="space-y-4">
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="rounded-md border p-4 text-center text-muted-foreground">
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="articles" className="space-y-4">
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="rounded-md border p-4 text-center text-muted-foreground">
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="hooks" className="space-y-4">
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="hook-chapter"></Label>
<Select defaultValue="3">
<SelectTrigger id="hook-chapter">
<SelectValue placeholder="选择章节" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1"></SelectItem>
<SelectItem value="2"></SelectItem>
<SelectItem value="3"> ()</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid w-full gap-1.5">
<Label htmlFor="message"></Label>
<Textarea placeholder="输入引导用户加群的文案..." id="message" defaultValue="阅读更多精彩内容请加入Soul创业实验派对群..." />
</div>
<Button></Button>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
)
}

77
app/admin/layout.tsx Normal file
View File

@@ -0,0 +1,77 @@
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { LayoutDashboard, FileText, Users, CreditCard, QrCode, Settings, LogOut, ChevronLeft } from "lucide-react"
import { useStore } from "@/lib/store"
import { useRouter } from "next/navigation"
import { useEffect } from "react"
export default function AdminLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
const router = useRouter()
const { user, isLoggedIn } = useStore()
// Simple admin check (in real app, use robust auth)
useEffect(() => {
if (!isLoggedIn) {
// router.push("/my") // Commented out for easier development access
}
}, [isLoggedIn, router])
const menuItems = [
{ icon: LayoutDashboard, label: "数据概览", href: "/admin" },
{ icon: FileText, label: "内容管理", href: "/admin/content" },
{ icon: Users, label: "用户管理", href: "/admin/users" },
{ icon: CreditCard, label: "支付配置", href: "/admin/payment" },
{ icon: QrCode, label: "二维码", href: "/admin/qrcodes" },
{ icon: Settings, label: "系统设置", href: "/admin/settings" },
]
return (
<div className="flex min-h-screen bg-[#0f172a]">
{/* Sidebar */}
<div className="w-64 bg-[#1e293b] text-white flex flex-col border-r border-gray-800">
<div className="p-6 border-b border-gray-800">
<h1 className="text-xl font-bold text-[#38bdac]"></h1>
<p className="text-xs text-gray-400 mt-1">Soul创业实验场</p>
</div>
<nav className="flex-1 p-4 space-y-2">
{menuItems.map((item) => {
const isActive = pathname === item.href
return (
<Link
key={item.href}
href={item.href}
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${
isActive
? "bg-[#38bdac]/10 text-[#38bdac] border border-[#38bdac]/20"
: "text-gray-400 hover:bg-gray-800 hover:text-white"
}`}
>
<item.icon className="w-5 h-5" />
<span className="text-sm font-medium">{item.label}</span>
</Link>
)
})}
</nav>
<div className="p-4 border-t border-gray-800">
<Link
href="/my"
className="flex items-center gap-3 px-4 py-3 text-gray-400 hover:text-white rounded-lg hover:bg-gray-800 transition-colors"
>
<LogOut className="w-5 h-5" />
<span className="text-sm font-medium"></span>
</Link>
</div>
</div>
{/* Main Content */}
<div className="flex-1 overflow-auto bg-[#0f172a]">
{children}
</div>
</div>
)
}

3
app/admin/loading.tsx Normal file
View File

@@ -0,0 +1,3 @@
export default function Loading() {
return null
}

82
app/admin/login/page.tsx Normal file
View File

@@ -0,0 +1,82 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Lock, User } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { useStore } from "@/lib/store"
export default function AdminLoginPage() {
const router = useRouter()
const { adminLogin } = useStore()
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [error, setError] = useState("")
const handleLogin = () => {
setError("")
const success = adminLogin(username, password)
if (success) {
router.push("/admin")
} else {
setError("用户名或密码错误")
}
}
return (
<div className="min-h-screen bg-[#0a1628] text-white flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Logo */}
<div className="text-center mb-8">
<h1 className="text-2xl font-bold text-white mb-2"></h1>
<p className="text-gray-500">SOUL的创业实验场</p>
</div>
{/* Login form */}
<div className="bg-[#0f2137] rounded-2xl p-8 border border-gray-700/50">
<h2 className="text-xl font-semibold text-white mb-6 text-center"></h2>
<div className="space-y-4">
<div>
<label className="block text-gray-400 text-sm mb-2"></label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
<Input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="请输入用户名"
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
/>
</div>
</div>
<div>
<label className="block text-gray-400 text-sm mb-2"></label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
<Input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="请输入密码"
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
onKeyDown={(e) => e.key === "Enter" && handleLogin()}
/>
</div>
</div>
{error && <p className="text-red-400 text-sm">{error}</p>}
<Button onClick={handleLogin} className="w-full bg-[#38bdac] hover:bg-[#2da396] text-white py-5">
</Button>
</div>
<p className="text-gray-500 text-xs text-center mt-6">默认账号: admin / key123456</p>
</div>
</div>
</div>
)
}

96
app/admin/page.tsx Normal file
View File

@@ -0,0 +1,96 @@
"use client"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { useStore } from "@/lib/store"
import { Users, BookOpen, ShoppingBag, TrendingUp } from "lucide-react"
export default function AdminDashboard() {
const { getAllUsers, getAllPurchases } = useStore()
const users = getAllUsers()
const purchases = getAllPurchases()
const totalRevenue = purchases.reduce((sum, p) => sum + p.amount, 0)
const totalUsers = users.length
const totalPurchases = purchases.length
const stats = [
{ title: "总用户数", value: totalUsers, icon: Users, color: "text-blue-500" },
{ title: "总收入", value: `¥${totalRevenue.toFixed(2)}`, icon: TrendingUp, color: "text-green-500" },
{ title: "订单数", value: totalPurchases, icon: ShoppingBag, color: "text-purple-500" },
{ title: "转化率", value: `${totalUsers > 0 ? ((totalPurchases / totalUsers) * 100).toFixed(1) : 0}%`, icon: BookOpen, color: "text-orange-500" },
]
return (
<div className="p-8 max-w-7xl mx-auto text-white">
<h1 className="text-2xl font-bold mb-8"></h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
{stats.map((stat, index) => (
<Card key={index} className="bg-[#1e293b] border-gray-700">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-gray-400">{stat.title}</CardTitle>
<stat.icon className={`w-4 h-4 ${stat.color}`} />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">{stat.value}</div>
</CardContent>
</Card>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<Card className="bg-[#1e293b] border-gray-700">
<CardHeader>
<CardTitle className="text-white"></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{purchases.slice(-5).reverse().map((p) => (
<div key={p.id} className="flex items-center justify-between p-4 bg-[#0f172a] rounded-lg">
<div>
<p className="text-sm font-medium text-white">{p.sectionTitle || "整本购买"}</p>
<p className="text-xs text-gray-400">{new Date(p.createdAt).toLocaleString()}</p>
</div>
<div className="text-right">
<p className="text-sm font-bold text-[#38bdac]">+¥{p.amount}</p>
<p className="text-xs text-gray-500">{p.paymentMethod}</p>
</div>
</div>
))}
{purchases.length === 0 && (
<p className="text-gray-500 text-center py-4"></p>
)}
</div>
</CardContent>
</Card>
<Card className="bg-[#1e293b] border-gray-700">
<CardHeader>
<CardTitle className="text-white"></CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{users.slice(-5).reverse().map((u) => (
<div key={u.id} className="flex items-center justify-between p-4 bg-[#0f172a] rounded-lg">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center text-xs">
{u.nickname.charAt(0)}
</div>
<div>
<p className="text-sm font-medium text-white">{u.nickname}</p>
<p className="text-xs text-gray-400">{u.phone}</p>
</div>
</div>
<p className="text-xs text-gray-500">{new Date(u.createdAt).toLocaleDateString()}</p>
</div>
))}
{users.length === 0 && (
<p className="text-gray-500 text-center py-4"></p>
)}
</div>
</CardContent>
</Card>
</div>
</div>
)
}

248
app/admin/payment/page.tsx Normal file
View File

@@ -0,0 +1,248 @@
"use client"
import { useState, useEffect } from "react"
import { useStore } from "@/lib/store"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Save, RefreshCw } from "lucide-react"
export default function PaymentConfigPage() {
const { settings, updateSettings, fetchSettings } = useStore()
const [loading, setLoading] = useState(false)
const [localSettings, setLocalSettings] = useState(settings.paymentMethods)
// Sync with store on mount
useEffect(() => {
setLocalSettings(settings.paymentMethods)
}, [settings.paymentMethods])
const handleSave = async () => {
setLoading(true)
// Update store (and local storage)
updateSettings({ paymentMethods: localSettings })
// Simulate API call delay
await new Promise(resolve => setTimeout(resolve, 800))
setLoading(false)
alert("配置已保存!")
}
const handleRefresh = async () => {
setLoading(true)
await fetchSettings()
setLoading(false)
}
const updateWechat = (field: string, value: any) => {
setLocalSettings(prev => ({
...prev,
wechat: { ...prev.wechat, [field]: value }
}))
}
const updateAlipay = (field: string, value: any) => {
setLocalSettings(prev => ({
...prev,
alipay: { ...prev.alipay, [field]: value }
}))
}
const updateUsdt = (field: string, value: any) => {
setLocalSettings(prev => ({
...prev,
usdt: { ...prev.usdt, [field]: value }
}))
}
return (
<div className="p-8 max-w-5xl mx-auto text-white">
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-2xl font-bold mb-2"></h1>
<p className="text-gray-400">USDT支付参数</p>
</div>
<div className="flex gap-4">
<Button variant="outline" onClick={handleRefresh} className="border-gray-600 text-gray-300 hover:text-white bg-transparent">
<RefreshCw className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
</Button>
<Button onClick={handleSave} className="bg-[#38bdac] hover:bg-[#2da396] text-white">
<Save className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
<div className="grid grid-cols-1 gap-8">
{/* Wechat Pay */}
<Card className="bg-[#1e293b] border-gray-700">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<div className="space-y-1">
<CardTitle className="text-[#07C160] flex items-center gap-2">
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"><path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.272.272 0 0 0 .14.047c.134 0 .24-.111.24-.247 0-.06-.023-.12-.038-.177l-.327-1.233a.582.582 0 0 1-.023-.156.49.49 0 0 1 .201-.398C23.024 18.48 24 16.82 24 14.98c0-3.21-2.931-5.837-6.656-6.088V8.89c-.135-.01-.269-.03-.406-.03zm-2.53 3.274c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.97-.982zm4.844 0c.535 0 .969.44.969.982a.976.976 0 0 1-.969.983.976.976 0 0 1-.969-.983c0-.542.434-.982.969-.982z"/></svg>
</CardTitle>
<CardDescription className="text-gray-400">API密钥</CardDescription>
</div>
<Switch
checked={localSettings.wechat.enabled}
onCheckedChange={(c) => updateWechat('enabled', c)}
/>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-gray-300"> AppID</Label>
<Input
className="bg-[#0f172a] border-gray-600 text-white"
placeholder="wx..."
value={localSettings.wechat.websiteAppId || ''}
onChange={(e) => updateWechat('websiteAppId', e.target.value)}
/>
</div>
<div className="space-y-2">
<Label className="text-gray-300"> AppSecret</Label>
<Input
type="password"
className="bg-[#0f172a] border-gray-600 text-white"
placeholder="......"
value={localSettings.wechat.websiteAppSecret || ''}
onChange={(e) => updateWechat('websiteAppSecret', e.target.value)}
/>
</div>
<div className="space-y-2">
<Label className="text-gray-300"> (MchId)</Label>
<Input
className="bg-[#0f172a] border-gray-600 text-white"
placeholder="160..."
value={localSettings.wechat.merchantId || ''}
onChange={(e) => updateWechat('merchantId', e.target.value)}
/>
</div>
<div className="space-y-2">
<Label className="text-gray-300">API v3 </Label>
<Input
type="password"
className="bg-[#0f172a] border-gray-600 text-white"
placeholder="......"
value={localSettings.wechat.apiKey || ''}
onChange={(e) => updateWechat('apiKey', e.target.value)}
/>
</div>
</div>
<div className="space-y-2">
<Label className="text-gray-300"> URL ()</Label>
<Input
className="bg-[#0f172a] border-gray-600 text-white"
placeholder="/images/wechat-pay.png"
value={localSettings.wechat.qrCode || ''}
onChange={(e) => updateWechat('qrCode', e.target.value)}
/>
<p className="text-xs text-gray-500"></p>
</div>
</CardContent>
</Card>
{/* Alipay */}
<Card className="bg-[#1e293b] border-gray-700">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<div className="space-y-1">
<CardTitle className="text-[#1677FF] flex items-center gap-2">
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"><path d="M8.77 20.62l9.92-4.33c-.12-.33-.24-.66-.38-.99-.14-.33-.3-.66-.47-.99H8.08c-2.2 0-3.99-1.79-3.99-3.99V8.08c0-2.2 1.79-3.99 3.99-3.99h7.84c2.2 0 3.99 1.79 3.99 3.99v2.24h-8.66c-.55 0-1 .45-1 1s.45 1 1 1h10.66c-.18 1.73-.71 3.36-1.53 4.83l-2.76 1.2c-.74-1.69-1.74-3.24-2.93-4.6-.52-.59-1.11-1.13-1.76-1.59H4.09v4.24c0 2.2 1.79 3.99 3.99 3.99h.69v.23z"/></svg>
</CardTitle>
<CardDescription className="text-gray-400">PID及密钥</CardDescription>
</div>
<Switch
checked={localSettings.alipay.enabled}
onCheckedChange={(c) => updateAlipay('enabled', c)}
/>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-gray-300"> (PID)</Label>
<Input
className="bg-[#0f172a] border-gray-600 text-white"
placeholder="2088..."
value={localSettings.alipay.partnerId || ''}
onChange={(e) => updateAlipay('partnerId', e.target.value)}
/>
</div>
<div className="space-y-2">
<Label className="text-gray-300"> (Key)</Label>
<Input
type="password"
className="bg-[#0f172a] border-gray-600 text-white"
placeholder="......"
value={localSettings.alipay.securityKey || ''}
onChange={(e) => updateAlipay('securityKey', e.target.value)}
/>
</div>
</div>
<div className="space-y-2">
<Label className="text-gray-300"> URL ()</Label>
<Input
className="bg-[#0f172a] border-gray-600 text-white"
placeholder="/images/alipay.png"
value={localSettings.alipay.qrCode || ''}
onChange={(e) => updateAlipay('qrCode', e.target.value)}
/>
</div>
</CardContent>
</Card>
{/* USDT */}
<Card className="bg-[#1e293b] border-gray-700">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<div className="space-y-1">
<CardTitle className="text-[#26A17B] flex items-center gap-2">
<span className="font-bold"></span> USDT支付
</CardTitle>
<CardDescription className="text-gray-400"></CardDescription>
</div>
<Switch
checked={localSettings.usdt.enabled}
onCheckedChange={(c) => updateUsdt('enabled', c)}
/>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="text-gray-300"></Label>
<Input
className="bg-[#0f172a] border-gray-600 text-white"
value={localSettings.usdt.network || 'TRC20'}
onChange={(e) => updateUsdt('network', e.target.value)}
/>
</div>
<div className="space-y-2">
<Label className="text-gray-300"> (USD -&gt; CNY)</Label>
<Input
type="number"
className="bg-[#0f172a] border-gray-600 text-white"
value={localSettings.usdt.exchangeRate || 7.2}
onChange={(e) => updateUsdt('exchangeRate', parseFloat(e.target.value))}
/>
</div>
</div>
<div className="space-y-2">
<Label className="text-gray-300"></Label>
<Input
className="bg-[#0f172a] border-gray-600 text-white font-mono"
placeholder="T..."
value={localSettings.usdt.address || ''}
onChange={(e) => updateUsdt('address', e.target.value)}
/>
</div>
</CardContent>
</Card>
</div>
</div>
)
}

View File

@@ -0,0 +1,58 @@
"use client"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Separator } from "@/components/ui/separator"
export default function QRCodesPage() {
return (
<div className="space-y-6">
<div>
<h2 className="text-3xl font-bold tracking-tight"></h2>
<p className="text-muted-foreground"></p>
</div>
<div className="grid gap-6 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>/</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-col items-center justify-center border-2 border-dashed rounded-lg p-6 h-48 bg-slate-50">
<span className="text-muted-foreground"></span>
</div>
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="traffic-qr"></Label>
<Input id="traffic-qr" type="file" />
</div>
<Button className="w-full"></Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-col items-center justify-center border-2 border-dashed rounded-lg p-6 h-48 bg-slate-50">
<span className="text-muted-foreground"></span>
</div>
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="group-qr"></Label>
<Input id="group-qr" type="file" />
</div>
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="group-url"> ()</Label>
<Input id="group-url" placeholder="https://..." defaultValue="https://soul.cn/party" />
</div>
<Button className="w-full"></Button>
</CardContent>
</Card>
</div>
</div>
)
}

View File

@@ -0,0 +1,66 @@
"use client"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch"
export default function SettingsPage() {
return (
<div className="space-y-6">
<div>
<h2 className="text-3xl font-bold tracking-tight"></h2>
<p className="text-muted-foreground"></p>
</div>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-2">
<Label htmlFor="site-name"></Label>
<Input id="site-name" defaultValue="一场Soul的创业实验" />
</div>
<div className="grid gap-2">
<Label htmlFor="author"></Label>
<Input id="author" defaultValue="卡若" />
</div>
<Button></Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center justify-between space-x-2">
<Label htmlFor="maintenance-mode" className="flex flex-col space-y-1">
<span></span>
<span className="font-normal text-xs text-muted-foreground"></span>
</Label>
<Switch id="maintenance-mode" />
</div>
<div className="flex items-center justify-between space-x-2">
<Label htmlFor="payment-enabled" className="flex flex-col space-y-1">
<span></span>
<span className="font-normal text-xs text-muted-foreground"></span>
</Label>
<Switch id="payment-enabled" defaultChecked />
</div>
<div className="flex items-center justify-between space-x-2">
<Label htmlFor="referral-enabled" className="flex flex-col space-y-1">
<span></span>
<span className="font-normal text-xs text-muted-foreground"></span>
</Label>
<Switch id="referral-enabled" defaultChecked />
</div>
</CardContent>
</Card>
</div>
)
}

59
app/admin/users/page.tsx Normal file
View File

@@ -0,0 +1,59 @@
"use client"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
export default function UsersPage() {
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h2 className="text-3xl font-bold tracking-tight"></h2>
<div className="flex w-full max-w-sm items-center space-x-2">
<Input type="text" placeholder="搜索用户..." />
<Button type="submit"></Button>
</div>
</div>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">1001</TableCell>
<TableCell>A</TableCell>
<TableCell className="text-green-600"></TableCell>
<TableCell>2025-12-28</TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="sm"></Button>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">1002</TableCell>
<TableCell>_8392</TableCell>
<TableCell className="text-yellow-600"></TableCell>
<TableCell>2025-12-29</TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="sm"></Button>
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
</Card>
</div>
)
}

View File

@@ -0,0 +1,135 @@
"use client"
import { useState, useEffect } from "react"
import { useStore, type Withdrawal } from "@/lib/store"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Check, X, Clock } from "lucide-react"
export default function WithdrawalsPage() {
const { withdrawals, completeWithdrawal } = useStore()
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) return null
const pendingWithdrawals = withdrawals?.filter(w => w.status === "pending") || []
const historyWithdrawals = withdrawals?.filter(w => w.status !== "pending").sort((a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
) || []
const handleApprove = (id: string) => {
if (confirm("确认打款并完成此提现申请吗?")) {
completeWithdrawal(id)
}
}
return (
<div className="p-6 max-w-6xl mx-auto">
<h1 className="text-2xl font-bold mb-6"></h1>
<div className="grid gap-6">
{/* Pending Requests */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Clock className="w-5 h-5 text-orange-500" />
({pendingWithdrawals.length})
</CardTitle>
</CardHeader>
<CardContent>
{pendingWithdrawals.length === 0 ? (
<p className="text-gray-500 text-center py-8"></p>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm text-left">
<thead className="bg-gray-50 text-gray-700">
<tr>
<th className="p-3"></th>
<th className="p-3">ID</th>
<th className="p-3"></th>
<th className="p-3"></th>
<th className="p-3"></th>
<th className="p-3"></th>
<th className="p-3 text-right"></th>
</tr>
</thead>
<tbody className="divide-y">
{pendingWithdrawals.map((w) => (
<tr key={w.id} className="hover:bg-gray-50">
<td className="p-3">{new Date(w.createdAt).toLocaleString()}</td>
<td className="p-3 font-mono text-xs">{w.userId.slice(0, 8)}...</td>
<td className="p-3 font-medium">{w.name}</td>
<td className="p-3">
<span className={`px-2 py-1 rounded-full text-xs text-white ${w.method === "wechat" ? "bg-green-600" : "bg-blue-600"}`}>
{w.method === "wechat" ? "微信" : "支付宝"}
</span>
</td>
<td className="p-3 font-mono">{w.account}</td>
<td className="p-3 font-bold text-orange-600">¥{w.amount.toFixed(2)}</td>
<td className="p-3 text-right">
<Button size="sm" onClick={() => handleApprove(w.id)} className="bg-green-600 hover:bg-green-700 text-white">
<Check className="w-4 h-4 mr-1" />
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
{/* History */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
{historyWithdrawals.length === 0 ? (
<p className="text-gray-500 text-center py-8"></p>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm text-left">
<thead className="bg-gray-50 text-gray-700">
<tr>
<th className="p-3"></th>
<th className="p-3"></th>
<th className="p-3"></th>
<th className="p-3"></th>
<th className="p-3"></th>
<th className="p-3"></th>
</tr>
</thead>
<tbody className="divide-y">
{historyWithdrawals.map((w) => (
<tr key={w.id} className="hover:bg-gray-50">
<td className="p-3 text-gray-500">{new Date(w.createdAt).toLocaleString()}</td>
<td className="p-3 text-gray-500">{w.completedAt ? new Date(w.completedAt).toLocaleString() : "-"}</td>
<td className="p-3">{w.name}</td>
<td className="p-3">
{w.method === "wechat" ? "微信" : "支付宝"}
</td>
<td className="p-3 font-medium">¥{w.amount.toFixed(2)}</td>
<td className="p-3">
<span className="px-2 py-1 rounded-full text-xs text-green-600 border border-green-200 bg-green-50">
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
</div>
</div>
)
}