Files
soul/app/admin/content/page.tsx

336 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState } from "react"
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"
import { Badge } from "@/components/ui/badge"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
import { bookData } from "@/lib/book-data"
import {
FileText,
BookOpen,
Settings2,
ChevronRight,
CheckCircle,
Edit3,
Save,
X,
RefreshCw,
Link2,
} from "lucide-react"
interface EditingSection {
id: string
title: string
price: number
content?: string
}
export default function ContentPage() {
const [expandedParts, setExpandedParts] = useState<string[]>(["part-1"])
const [editingSection, setEditingSection] = useState<EditingSection | null>(null)
const [isSyncing, setIsSyncing] = useState(false)
const [feishuDocUrl, setFeishuDocUrl] = useState("")
const [showFeishuModal, setShowFeishuModal] = useState(false)
const togglePart = (partId: string) => {
setExpandedParts((prev) => (prev.includes(partId) ? prev.filter((id) => id !== partId) : [...prev, partId]))
}
const totalSections = bookData.reduce(
(sum, part) => sum + part.chapters.reduce((cSum, ch) => cSum + ch.sections.length, 0),
0,
)
const handleEditSection = (section: { id: string; title: string; price: number }) => {
setEditingSection({
id: section.id,
title: section.title,
price: section.price,
content: "",
})
}
const handleSaveSection = () => {
if (editingSection) {
// 保存到本地存储或API
console.log("[v0] Saving section:", editingSection)
alert(`已保存章节: ${editingSection.title}`)
setEditingSection(null)
}
}
const handleSyncFeishu = async () => {
if (!feishuDocUrl) {
alert("请输入飞书文档链接")
return
}
setIsSyncing(true)
// 模拟同步过程
await new Promise((resolve) => setTimeout(resolve, 2000))
setIsSyncing(false)
setShowFeishuModal(false)
alert("飞书文档同步成功!")
}
return (
<div className="p-8 max-w-6xl mx-auto">
<div className="flex justify-between items-center mb-8">
<div>
<h2 className="text-2xl font-bold text-white"></h2>
<p className="text-gray-400 mt-1">
{bookData.length} · {totalSections}
</p>
</div>
<Button onClick={() => setShowFeishuModal(true)} className="bg-[#38bdac] hover:bg-[#2da396] text-white">
<FileText className="w-4 h-4 mr-2" />
</Button>
</div>
{/* 飞书同步弹窗 */}
<Dialog open={showFeishuModal} onOpenChange={setShowFeishuModal}>
<DialogContent className="bg-[#0f2137] border-gray-700 text-white max-w-lg">
<DialogHeader>
<DialogTitle className="text-white flex items-center gap-2">
<Link2 className="w-5 h-5 text-[#38bdac]" />
</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label className="text-gray-300"></Label>
<Input
className="bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
placeholder="https://xxx.feishu.cn/docx/..."
value={feishuDocUrl}
onChange={(e) => setFeishuDocUrl(e.target.value)}
/>
<p className="text-xs text-gray-500">访</p>
</div>
<div className="bg-[#38bdac]/10 border border-[#38bdac]/30 rounded-lg p-3">
<p className="text-[#38bdac] text-sm">
</p>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setShowFeishuModal(false)}
className="border-gray-600 text-gray-300 hover:bg-gray-700/50 bg-transparent"
>
</Button>
<Button
onClick={handleSyncFeishu}
disabled={isSyncing}
className="bg-[#38bdac] hover:bg-[#2da396] text-white"
>
{isSyncing ? (
<>
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
...
</>
) : (
<>
<RefreshCw className="w-4 h-4 mr-2" />
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 章节编辑弹窗 */}
<Dialog open={!!editingSection} onOpenChange={() => setEditingSection(null)}>
<DialogContent className="bg-[#0f2137] border-gray-700 text-white max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-white flex items-center gap-2">
<Edit3 className="w-5 h-5 text-[#38bdac]" />
</DialogTitle>
</DialogHeader>
{editingSection && (
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label className="text-gray-300"></Label>
<Input
className="bg-[#0a1628] border-gray-700 text-white"
value={editingSection.title}
onChange={(e) => setEditingSection({ ...editingSection, title: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label className="text-gray-300"> ()</Label>
<Input
type="number"
className="bg-[#0a1628] border-gray-700 text-white w-32"
value={editingSection.price}
onChange={(e) => setEditingSection({ ...editingSection, price: Number(e.target.value) })}
/>
</div>
<div className="space-y-2">
<Label className="text-gray-300"></Label>
<Textarea
className="bg-[#0a1628] border-gray-700 text-white min-h-[200px] placeholder:text-gray-500"
placeholder="此处显示章节内容支持Markdown格式..."
value={editingSection.content}
onChange={(e) => setEditingSection({ ...editingSection, content: e.target.value })}
/>
</div>
</div>
)}
<DialogFooter>
<Button
variant="outline"
onClick={() => setEditingSection(null)}
className="border-gray-600 text-gray-300 hover:bg-gray-700/50 bg-transparent"
>
<X className="w-4 h-4 mr-2" />
</Button>
<Button onClick={handleSaveSection} className="bg-[#38bdac] hover:bg-[#2da396] text-white">
<Save className="w-4 h-4 mr-2" />
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Tabs defaultValue="chapters" className="space-y-6">
<TabsList className="bg-[#0f2137] border border-gray-700/50 p-1">
<TabsTrigger
value="chapters"
className="data-[state=active]:bg-[#38bdac]/20 data-[state=active]:text-[#38bdac] text-gray-400"
>
<BookOpen className="w-4 h-4 mr-2" />
</TabsTrigger>
<TabsTrigger
value="hooks"
className="data-[state=active]:bg-[#38bdac]/20 data-[state=active]:text-[#38bdac] text-gray-400"
>
<Settings2 className="w-4 h-4 mr-2" />
</TabsTrigger>
</TabsList>
<TabsContent value="chapters" className="space-y-4">
{bookData.map((part, partIndex) => (
<Card key={part.id} className="bg-[#0f2137] border-gray-700/50 shadow-xl overflow-hidden">
<CardHeader
className="cursor-pointer hover:bg-[#162840] transition-colors"
onClick={() => togglePart(part.id)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-[#38bdac] font-mono text-sm">0{partIndex + 1}</span>
<CardTitle className="text-white">{part.title}</CardTitle>
<Badge variant="outline" className="text-gray-400 border-gray-600">
{part.chapters.reduce((sum, ch) => sum + ch.sections.length, 0)}
</Badge>
</div>
<ChevronRight
className={`w-5 h-5 text-gray-400 transition-transform ${
expandedParts.includes(part.id) ? "rotate-90" : ""
}`}
/>
</div>
</CardHeader>
{expandedParts.includes(part.id) && (
<CardContent className="pt-0 pb-4">
<div className="space-y-3 pl-8 border-l-2 border-gray-700">
{part.chapters.map((chapter) => (
<div key={chapter.id} className="space-y-2">
<h4 className="font-medium text-gray-300">{chapter.title}</h4>
<div className="space-y-1">
{chapter.sections.map((section) => (
<div
key={section.id}
className="flex items-center justify-between py-2 px-3 rounded-lg hover:bg-[#162840] text-sm group transition-colors"
>
<div className="flex items-center gap-2">
<CheckCircle className="w-4 h-4 text-[#38bdac]" />
<span className="text-gray-400">{section.title}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-[#38bdac] font-medium">
{section.price === 0 ? "免费" : `¥${section.price}`}
</span>
<Button
variant="ghost"
size="sm"
onClick={() => handleEditSection(section)}
className="text-gray-500 hover:text-[#38bdac] hover:bg-[#38bdac]/10 opacity-0 group-hover:opacity-100 transition-opacity"
>
<Edit3 className="w-4 h-4 mr-1" />
</Button>
</div>
</div>
))}
</div>
</div>
))}
</div>
</CardContent>
)}
</Card>
))}
</TabsContent>
<TabsContent value="hooks" className="space-y-4">
<Card className="bg-[#0f2137] border-gray-700/50 shadow-xl">
<CardHeader>
<CardTitle className="text-white"></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="hook-chapter" className="text-gray-300">
</Label>
<Select defaultValue="3">
<SelectTrigger id="hook-chapter" className="bg-[#0a1628] border-gray-700 text-white">
<SelectValue placeholder="选择章节" />
</SelectTrigger>
<SelectContent className="bg-[#0f2137] border-gray-700">
<SelectItem value="1" className="text-white hover:bg-[#38bdac]/20 focus:bg-[#38bdac]/20">
</SelectItem>
<SelectItem value="2" className="text-white hover:bg-[#38bdac]/20 focus:bg-[#38bdac]/20">
</SelectItem>
<SelectItem value="3" className="text-white hover:bg-[#38bdac]/20 focus:bg-[#38bdac]/20">
()
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid w-full gap-1.5">
<Label htmlFor="message" className="text-gray-300">
</Label>
<Textarea
placeholder="输入引导用户加群的文案..."
id="message"
className="bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
defaultValue="阅读更多精彩内容请加入Soul创业实验派对群..."
/>
</div>
<Button className="bg-[#38bdac] hover:bg-[#2da396] text-white"></Button>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
)
}