Spaces:
Build error
Build error
| "use client" | |
| import { useState, useEffect } from "react" | |
| import { Button } from "@/components/ui/button" | |
| import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" | |
| import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" | |
| import { Badge } from "@/components/ui/badge" | |
| import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" | |
| import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" | |
| import { toast } from "sonner" | |
| import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" | |
| import axios from "axios" | |
| import YouTube from "react-youtube" | |
| const fetchReviewQueue = async () => { | |
| const response = await axios.get("/api/review-queue") | |
| return response.data | |
| } | |
| const updateReviewItem = async (id, status, metadata) => { | |
| const response = await axios.put(`/api/review-queue/${id}`, { status, metadata }) | |
| return response.data | |
| } | |
| export function ReviewQueue() { | |
| const [selectedItem, setSelectedItem] = useState(null) | |
| const queryClient = useQueryClient() | |
| const { data: queue, isLoading, error } = useQuery({ | |
| queryKey: ["reviewQueue"], | |
| queryFn: fetchReviewQueue, | |
| }) | |
| const mutation = useMutation({ | |
| mutationFn: updateReviewItem, | |
| onSuccess: () => { | |
| queryClient.invalidateQueries(["reviewQueue"]) | |
| toast.success("Item updated successfully") | |
| setSelectedItem(null) | |
| }, | |
| onError: (error) => { | |
| toast.error(`Error updating item: ${error.message}`) | |
| }, | |
| }) | |
| const handleApprove = (item) => { | |
| mutation.mutate({ id: item.id, status: "approved", metadata: item.metadata }) | |
| } | |
| const handleReject = (item) => { | |
| mutation.mutate({ id: item.id, status: "rejected", metadata: item.metadata }) | |
| } | |
| const handleEdit = (item) => { | |
| setSelectedItem(item) | |
| } | |
| const handleSaveEdit = () => { | |
| mutation.mutate({ | |
| id: selectedItem.id, | |
| status: "approved", | |
| metadata: selectedItem.metadata, | |
| }) | |
| } | |
| if (isLoading) return <div className="p-4">Loading...</div> | |
| if (error) return <Alert variant="destructive"><AlertTitle>Error</AlertTitle><AlertDescription>{error.message}</AlertDescription></Alert> | |
| return ( | |
| <div className="flex flex-col gap-4"> | |
| <Card> | |
| <CardHeader> | |
| <CardTitle>Review Queue</CardTitle> | |
| <CardDescription>Manage content flagged for manual review</CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| <Table> | |
| <TableHeader> | |
| <TableRow> | |
| <TableHead>Title</TableHead> | |
| <TableHead>Channel</TableHead> | |
| <TableHead>Status</TableHead> | |
| <TableHead>Actions</TableHead> | |
| </TableRow> | |
| </TableHeader> | |
| <TableBody> | |
| {queue?.map((item) => ( | |
| <TableRow key={item.id}> | |
| <TableCell className="font-medium">{item.title}</TableCell> | |
| <TableCell>{item.channel}</TableCell> | |
| <TableCell> | |
| <Badge variant={item.status === "flagged" ? "destructive" : "secondary"}> | |
| {item.status} | |
| </Badge> | |
| </TableCell> | |
| <TableCell> | |
| <DropdownMenu> | |
| <DropdownMenuTrigger asChild> | |
| <Button variant="ghost" className="h-8 w-8 p-0"> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width="24" | |
| height="24" | |
| viewBox="0 0 24 24" | |
| fill="none" | |
| stroke="currentColor" | |
| strokeWidth="2" | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| className="h-4 w-4" | |
| > | |
| <path d="M5 12h14" /> | |
| <path d="M12 5v14" /> | |
| </svg> | |
| </Button> | |
| </DropdownMenuTrigger> | |
| <DropdownMenuContent align="end"> | |
| <DropdownMenuItem onClick={() => handleApprove(item)}>Approve</DropdownMenuItem> | |
| <DropdownMenuItem onClick={() => handleReject(item)}>Reject</DropdownMenuItem> | |
| <DropdownMenuItem onClick={() => handleEdit(item)}>Edit Metadata</DropdownMenuItem> | |
| </DropdownMenuContent> | |
| </DropdownMenu> | |
| </TableCell> | |
| </TableRow> | |
| ))} | |
| </TableBody> | |
| </Table> | |
| </CardContent> | |
| </Card> | |
| {selectedItem && ( | |
| <Card> | |
| <CardHeader> | |
| <CardTitle>Edit Metadata</CardTitle> | |
| <CardDescription>Review and edit content details</CardDescription> | |
| </CardHeader> | |
| <CardContent className="grid gap-4"> | |
| <div className="grid gap-2"> | |
| <div className="aspect-video"> | |
| <YouTube videoId={selectedItem.videoId} opts={{ width: "100%", height: "100%" }} /> | |
| </div> | |
| <div className="grid gap-1"> | |
| <div className="font-medium">{selectedItem.title}</div> | |
| <div className="text-sm text-muted-foreground">{selectedItem.channel}</div> | |
| </div> | |
| </div> | |
| <div className="grid gap-2"> | |
| <div className="font-medium">Transcript</div> | |
| <div className="rounded-md border p-4 text-sm"> | |
| {selectedItem.transcript} | |
| </div> | |
| </div> | |
| <div className="grid gap-2"> | |
| <div className="font-medium">Extracted Metadata</div> | |
| <div className="grid gap-2"> | |
| <div className="flex items-center gap-2"> | |
| <div className="text-sm text-muted-foreground">Series:</div> | |
| <input | |
| type="text" | |
| value={selectedItem.metadata.series || ""} | |
| onChange={(e) => setSelectedItem({ | |
| ...selectedItem, | |
| metadata: { ...selectedItem.metadata, series: e.target.value } | |
| })} | |
| className="rounded-md border px-2 py-1 text-sm" | |
| /> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <div className="text-sm text-muted-foreground">Characters:</div> | |
| <input | |
| type="text" | |
| value={selectedItem.metadata.characters || ""} | |
| onChange={(e) => setSelectedItem({ | |
| ...selectedItem, | |
| metadata: { ...selectedItem.metadata, characters: e.target.value } | |
| })} | |
| className="rounded-md border px-2 py-1 text-sm" | |
| /> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <div className="text-sm text-muted-foreground">Themes:</div> | |
| <input | |
| type="text" | |
| value={selectedItem.metadata.themes || ""} | |
| onChange={(e) => setSelectedItem({ | |
| ...selectedItem, | |
| metadata: { ...selectedItem.metadata, themes: e.target.value } | |
| })} | |
| className="rounded-md border px-2 py-1 text-sm" | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| </CardContent> |