Spaces:
Runtime error
Runtime error
feat: Implement admin moderation history, audit logging for detection actions, and a new admin navigation and opportunity hub.
5480d48 | import { getAnalysisQueue } from "@/features/admin/actions/analysis"; | |
| import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; | |
| import { Badge } from "@/components/ui/badge"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Video, Clock, CheckCircle2, AlertCircle, Play, ArrowRight } from "lucide-react"; | |
| import Link from "next/link"; | |
| import { formatDistanceToNow } from "date-fns"; | |
| export const metadata = { | |
| title: "Video Hub | Vault Admin", | |
| }; | |
| export default async function AnalysisQueuePage(props: { | |
| searchParams: Promise<{ status?: string }>; | |
| }) { | |
| const searchParams = await props.searchParams; | |
| const status = searchParams.status; | |
| const queue = await getAnalysisQueue(status); | |
| const getStatusColor = (status: string) => { | |
| switch (status) { | |
| case 'pending_analysis': return 'bg-amber-500/10 text-amber-500 border-amber-500/20'; | |
| case 'in_progress': return 'bg-blue-500/10 text-blue-500 border-blue-500/20'; | |
| case 'awaiting_approval': return 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20'; | |
| default: return 'bg-slate-500/10 text-slate-500 border-slate-500/20'; | |
| } | |
| }; | |
| const getStatusLabel = (status: string) => { | |
| switch (status) { | |
| case 'pending_analysis': return 'In Intake'; | |
| case 'in_progress': return 'In Pipeline'; | |
| case 'awaiting_approval': return 'Ready for Review'; | |
| default: return status; | |
| } | |
| }; | |
| return ( | |
| <div className="space-y-6"> | |
| <div className="flex flex-col gap-2"> | |
| <div className="flex items-center gap-2 text-xs text-muted-foreground mb-1"> | |
| <Link href="/admin/dashboard" className="hover:text-primary transition-colors">Dashboard</Link> | |
| <span>/</span> | |
| <span className="text-foreground font-medium">Video Hub</span> | |
| </div> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <h1 className="text-3xl font-bold tracking-tight">Video Hub</h1> | |
| <p className="text-muted-foreground">Manage video content submitted for AI product discovery.</p> | |
| </div> | |
| {status && ( | |
| <Badge variant="outline" className="h-8 gap-2"> | |
| <span className="w-2 h-2 rounded-full bg-primary animate-pulse" /> | |
| Filtering by: {getStatusLabel(status)} | |
| <Button variant="ghost" size="icon" className="h-4 w-4 ml-2 p-0" asChild> | |
| <Link href="/admin/analysis-queue">×</Link> | |
| </Button> | |
| </Badge> | |
| )} | |
| </div> | |
| </div> | |
| <div className="grid gap-4"> | |
| {queue.length === 0 ? ( | |
| <Card className="flex flex-col items-center justify-center py-12 text-center"> | |
| <CheckCircle2 className="h-12 w-12 text-muted-foreground mb-4 opacity-20" /> | |
| <CardTitle>Queue is empty</CardTitle> | |
| <CardDescription>No videos currently require analysis review.</CardDescription> | |
| </Card> | |
| ) : ( | |
| queue.map((video) => ( | |
| <Card key={video.id} className="overflow-hidden bg-card/40 backdrop-blur-md border-border/40 hover:border-primary/50 transition-colors"> | |
| <div className="flex flex-col md:flex-row"> | |
| <div className="relative w-full md:w-48 aspect-video md:aspect-auto"> | |
| <img | |
| src={video.thumbnailUrl || ''} | |
| alt={video.title} | |
| className="absolute inset-0 w-full h-full object-cover" | |
| /> | |
| {video.scanStatus === 'in_progress' && ( | |
| <div className="absolute inset-0 bg-background/60 backdrop-blur-sm flex items-center justify-center"> | |
| <div className="flex flex-col items-center gap-2"> | |
| <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div> | |
| <span className="text-xs font-medium">Analyzing...</span> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| <div className="flex-1 p-6 flex flex-col gap-4"> | |
| <div className="flex flex-col gap-1"> | |
| <div className="flex items-center justify-between gap-4"> | |
| <h3 className="font-semibold text-lg line-clamp-1">{video.title}</h3> | |
| <Badge variant="outline" className={getStatusColor(video.scanStatus)}> | |
| {getStatusLabel(video.scanStatus)} | |
| </Badge> | |
| </div> | |
| <div className="flex items-center gap-2 text-sm text-muted-foreground"> | |
| <span>{video.channel?.channelName}</span> | |
| <span>•</span> | |
| <span>{formatDistanceToNow(new Date(video.createdAt))} ago</span> | |
| </div> | |
| </div> | |
| <div className="mt-auto flex items-center justify-between gap-4"> | |
| <div className="flex items-center gap-4 text-xs"> | |
| <div className="flex items-center gap-1.5"> | |
| <Video className="h-3.5 w-3.5 opacity-50" /> | |
| <span>{video.platform}</span> | |
| </div> | |
| {video.scanStatus === 'awaiting_approval' && ( | |
| <div className="flex items-center gap-1.5 text-emerald-500 font-medium"> | |
| <AlertCircle className="h-3.5 w-3.5" /> | |
| <span>Ready for Approval</span> | |
| </div> | |
| )} | |
| </div> | |
| <Button size="sm" asChild> | |
| <Link href={`/admin/analysis/${video.id}`}> | |
| {video.scanStatus === 'awaiting_approval' ? 'Review Results' : 'Start Review'} | |
| <ArrowRight className="ml-2 h-4 w-4" /> | |
| </Link> | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| </Card> | |
| )) | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |