import { useState, useEffect, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { useLang } from "@/contexts/LanguageContext"; import { isAdminSession, clearAdminSession } from "@/lib/adminSession"; import { toast } from "sonner"; import { type CourseVideo, fetchAllCourseVideos, createCourseVideo, deleteCourseVideo, updateCourseVideoStatus, } from "@/lib/api"; import { motion, AnimatePresence } from "framer-motion"; import { Upload, Video, Trash2, ArrowLeft, Eye, Plus, FileVideo, Clock, Users, Search, Filter, CheckCircle2, AlertCircle, Sparkles } from "lucide-react"; import heroBg from "@/assets/hero-bg.jpg"; import logo from "@/assets/logo.png"; interface VideoItem { id: string; title: string; course: string; courseKey: string; module: string; duration: string; uploadDate: string; status: "published" | "draft" | "processing"; views: number; thumbnail: string; videoUrl?: string; } function thumbForCourseKey(key: string): string { const m: Record = { biz: "📊", product: "💡", marketing: "📢", ops: "⚙️", }; return m[key] || "🎞"; } function mapCourseVideoToItem(v: CourseVideo, lang: "en" | "zh"): VideoItem { return { id: v.id, title: v.title, course: lang === "zh" ? v.courseLabelZh : v.courseLabelEn, courseKey: v.courseKey, module: v.moduleName, duration: v.duration, uploadDate: v.uploadDate, status: v.status as VideoItem["status"], views: v.views, thumbnail: thumbForCourseKey(v.courseKey), videoUrl: v.videoUrl, }; } const courseOptions = [ { key: "biz", label: { zh: "商业模式设计", en: "Business Model Design" } }, { key: "product", label: { zh: "产品开发方法论", en: "Product Development" } }, { key: "marketing", label: { zh: "营销策略", en: "Marketing Strategy" } }, { key: "ops", label: { zh: "运营管理体系", en: "Operations Management" } }, ]; export default function Admin() { const { lang } = useLang(); const navigate = useNavigate(); useEffect(() => { if (!isAdminSession()) { navigate("/admin", { replace: true }); } }, [navigate]); const [videos, setVideos] = useState([]); const [listLoading, setListLoading] = useState(true); const [showUpload, setShowUpload] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [filterCourse, setFilterCourse] = useState("all"); const [dragActive, setDragActive] = useState(false); const [uploadTitle, setUploadTitle] = useState(""); const [uploadCourse, setUploadCourse] = useState("biz"); const [uploadModule, setUploadModule] = useState(""); const [uploadFile, setUploadFile] = useState(null); const [uploadBusy, setUploadBusy] = useState(false); const refreshVideos = useCallback(async () => { setListLoading(true); try { const list = await fetchAllCourseVideos(); setVideos(list.map((v) => mapCourseVideoToItem(v, lang))); } catch { toast.error(lang === "zh" ? "无法加载视频列表" : "Could not load videos"); } finally { setListLoading(false); } }, [lang]); useEffect(() => { refreshVideos(); }, [refreshVideos]); const resetUploadForm = () => { setUploadTitle(""); setUploadCourse("biz"); setUploadModule(lang === "zh" ? "模块1" : "Module 1"); setUploadFile(null); }; const buildUploadFormData = (status: "published" | "draft"): FormData => { const co = courseOptions.find((c) => c.key === uploadCourse); const fd = new FormData(); fd.append("file", uploadFile as File); fd.append("title", uploadTitle.trim()); fd.append("courseKey", uploadCourse); fd.append("courseLabelZh", co?.label.zh ?? ""); fd.append("courseLabelEn", co?.label.en ?? ""); fd.append("moduleName", uploadModule || (lang === "zh" ? "模块1" : "Module 1")); fd.append("status", status); return fd; }; const handleSaveAsDraft = async () => { if (!uploadFile) { toast.error(lang === "zh" ? "请选择视频文件" : "Choose a video file"); return; } setUploadBusy(true); try { const r = await createCourseVideo(buildUploadFormData("draft")); if (!r.ok) { toast.error(r.error || (lang === "zh" ? "保存失败" : "Save failed")); return; } toast.success(lang === "zh" ? "已保存草稿" : "Draft saved"); setShowUpload(false); resetUploadForm(); await refreshVideos(); } catch { toast.error(lang === "zh" ? "上传失败" : "Upload failed"); } finally { setUploadBusy(false); } }; const handleUpload = async (e: React.FormEvent) => { e.preventDefault(); if (!uploadFile) { toast.error(lang === "zh" ? "请选择视频文件" : "Choose a video file"); return; } setUploadBusy(true); try { const r = await createCourseVideo(buildUploadFormData("published")); if (!r.ok) { toast.error(r.error || (lang === "zh" ? "发布失败" : "Publish failed")); return; } toast.success(lang === "zh" ? "已发布,学员可在对应课程页观看" : "Published — visible on the course page"); setShowUpload(false); resetUploadForm(); await refreshVideos(); } catch { toast.error(lang === "zh" ? "上传失败" : "Upload failed"); } finally { setUploadBusy(false); } }; const handleDelete = async (id: string) => { const ok = await deleteCourseVideo(id); if (!ok) { toast.error(lang === "zh" ? "删除失败" : "Delete failed"); return; } setVideos((prev) => prev.filter((v) => v.id !== id)); toast.success(lang === "zh" ? "已删除" : "Deleted"); }; const handlePublish = async (id: string) => { const r = await updateCourseVideoStatus(id, "published"); if (!r.ok) { toast.error(lang === "zh" ? "发布失败" : "Publish failed"); return; } toast.success(lang === "zh" ? "已发布" : "Published"); await refreshVideos(); }; const filteredVideos = videos.filter(v => { const matchSearch = v.title.toLowerCase().includes(searchQuery.toLowerCase()) || v.course.toLowerCase().includes(searchQuery.toLowerCase()); const matchCourse = filterCourse === "all" || v.courseKey === filterCourse; return matchSearch && matchCourse; }); const statusConfig = { published: { color: "text-emerald-600 bg-emerald-500/10", icon: CheckCircle2, label: { zh: "已发布", en: "Published" } }, draft: { color: "text-amber-600 bg-amber-500/10", icon: AlertCircle, label: { zh: "草稿", en: "Draft" } }, processing: { color: "text-blue-600 bg-blue-500/10", icon: Clock, label: { zh: "处理中", en: "Processing" } }, }; // Admin Dashboard return (
{/* Header */}
{/* Stats */}
{[ { label: lang === "zh" ? "总视频数" : "Total Videos", value: videos.length, icon: Video, color: "from-primary to-accent" }, { label: lang === "zh" ? "已发布" : "Published", value: videos.filter(v => v.status === "published").length, icon: CheckCircle2, color: "from-emerald-500 to-teal-400" }, { label: lang === "zh" ? "草稿" : "Drafts", value: videos.filter(v => v.status === "draft").length, icon: AlertCircle, color: "from-amber-500 to-orange-400" }, { label: lang === "zh" ? "总播放量" : "Total Views", value: videos.reduce((sum, v) => sum + v.views, 0).toLocaleString(), icon: Users, color: "from-violet-500 to-purple-400" }, ].map((stat, i) => (

{stat.value}

{stat.label}

))}
{/* Main Content */}
{/* Toolbar */}
setSearchQuery(e.target.value)} placeholder={lang === "zh" ? "搜索视频..." : "Search videos..."} className="w-full h-10 rounded-xl border border-border/50 bg-muted/20 pl-10 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30 transition-all" />
{ resetUploadForm(); setShowUpload(true); }} className="h-10 px-5 rounded-xl bg-gradient-to-r from-primary to-accent text-primary-foreground text-sm font-medium shadow-lg shadow-primary/20 flex items-center gap-2" > {lang === "zh" ? "上传视频" : "Upload Video"}
{/* Video Grid */}
{listLoading && (
{lang === "zh" ? "加载中…" : "Loading…"}
)} {!listLoading && filteredVideos.map((video, i) => { const status = statusConfig[video.status]; return ( {/* Thumbnail */}
{video.thumbnail}
{video.duration}
{/* Info */}

{video.title}

{video.course} · {video.module}

{status.label[lang]}
{video.views} {video.uploadDate}
{(video.status === "draft" || video.status === "processing") && ( )}
); })}
{!listLoading && filteredVideos.length === 0 && (
)}
{/* Upload Modal */} {showUpload && ( { setShowUpload(false); resetUploadForm(); }} > e.stopPropagation()} >

{lang === "zh" ? "上传新视频" : "Upload New Video"}

{/* Video Title */}
setUploadTitle(e.target.value)} placeholder={lang === "zh" ? "输入视频标题" : "Enter video title"} className="w-full h-11 rounded-xl border border-border bg-background px-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30 shadow-sm transition-all" required />
{/* Course Selection */}
{/* Module */}
{/* File Upload */}
{ e.preventDefault(); setDragActive(true); }} onDragLeave={() => setDragActive(false)} onDrop={(e) => { e.preventDefault(); setDragActive(false); if (e.dataTransfer.files[0]) setUploadFile(e.dataTransfer.files[0]); }} className={`relative border-2 border-dashed rounded-xl p-6 text-center transition-all cursor-pointer ${ dragActive ? "border-primary bg-primary/10" : "border-border hover:border-primary/40 bg-background shadow-sm" }`} > e.target.files?.[0] && setUploadFile(e.target.files[0])} className="absolute inset-0 opacity-0 cursor-pointer" /> {uploadFile ? (
{uploadFile.name}
) : ( <>

{lang === "zh" ? "拖拽文件到这里或点击上传" : "Drag & drop or click to upload"}

MP4, MOV, AVI (max 2GB)

)}
{/* Draft Preview */} {(uploadTitle || uploadFile) && (

{lang === "zh" ? "📋 上传预览" : "📋 Upload Preview"}

{courseOptions.find(c => c.key === uploadCourse)?.label.zh.charAt(0) === "商" ? "📊" : "📋"}

{uploadTitle || (lang === "zh" ? "未命名视频" : "Untitled Video")}

{courseOptions.find(c => c.key === uploadCourse)?.label[lang]} · {uploadModule || (lang === "zh" ? "模块1" : "Module 1")}

{uploadFile && (

{uploadFile.name} ({(uploadFile.size / 1024 / 1024).toFixed(1)} MB)

)}
)} {/* Actions */}
)}
); }