Spaces:
Runtime error
Runtime error
File size: 7,669 Bytes
304bdf5 5480d48 304bdf5 5480d48 304bdf5 5480d48 304bdf5 5480d48 304bdf5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | 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>
);
}
|