'use client'; // Ops publish management (版本管理) — lists published frozen coursewares from // the registry (L2) with their versions and bundle URLs. This is the ops // workbench's "manage published courseware" view; the registry API is public // read-only for the learner player, so this page needs no token to load. import { useCallback, useEffect, useState } from 'react'; import { Copy, RefreshCw, Rocket } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useI18n } from '@/lib/hooks/use-i18n'; import { useRouter } from 'next/navigation'; export interface PublishedItem { coursewareId: string; version: number; title: string; language?: string; status: string; publishedAt: string; contentHash: string; sceneCount: number; quizSceneCount: number; bundleUrl: string; } export default function PublishManagementPage() { const { t } = useI18n(); const router = useRouter(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [copiedId, setCopiedId] = useState(null); const load = useCallback(async () => { setLoading(true); setError(null); try { const response = await fetch('/api/coursewares'); const body = (await response.json()) as { items?: PublishedItem[] }; if (!response.ok || !Array.isArray(body.items)) { throw new Error(`HTTP ${response.status}`); } setItems(body.items); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setLoading(false); } }, []); useEffect(() => { void load(); }, [load]); const copyUrl = async (item: PublishedItem) => { await navigator.clipboard.writeText(item.bundleUrl); setCopiedId(`${item.coursewareId}@${item.version}`); setTimeout(() => setCopiedId(null), 1500); }; const formatDate = (iso: string) => new Date(iso).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }); return (

{t('publish.manageTitle')}

{t('publish.manageDesc')}

{loading &&

{t('publish.loading')}

} {error && (
{t('publish.loadFailed')}: {error}
)} {!loading && !error && items.length === 0 && (

{t('publish.empty')}

)}
{items.map((item) => (
{item.title}
{item.coursewareId} v{item.version} {formatDate(item.publishedAt)} {t('publish.sceneCount', { count: item.sceneCount })} {item.contentHash.slice(0, 12)}…
{item.bundleUrl}
))}
); }