import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Textarea } from "@/components/ui/textarea"; import { useToast } from "@/hooks/use-toast"; import { Edit, MessageCircle, MessageSquare, ThumbsUp, Trash, X, } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import type { AnnotationWithUser } from "../../../../shared/schema"; interface AnnotationSystemProps { workId: string; selectedLineNumber: number | null; onClose: () => void; translationId?: string; } export function AnnotationSystem({ workId, selectedLineNumber, onClose, translationId, }: AnnotationSystemProps) { const { toast } = useToast(); const [annotations, setAnnotations] = useState([]); const [isLoading, setIsLoading] = useState(true); const [newAnnotation, setNewAnnotation] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [editingAnnotationId, setEditingAnnotationId] = useState( null, ); const [editText, setEditText] = useState(""); const annotationRef = useRef(null); // Mock user data - in a real app this would come from auth const currentUser = { id: "1", name: "Anonymous", avatar: null, }; // Fetch annotations for the selected line useEffect(() => { if (!selectedLineNumber) return; setIsLoading(true); // Simulate API call to get annotations for the selected line setTimeout(() => { // These would be fetched from the API in a real app const mockAnnotations: AnnotationWithUser[] = [ { id: "1" as const, workId: workId, translationId: translationId, lineNumber: selectedLineNumber, userId: "2" as const, user: { name: "Literary Scholar", avatar: undefined, }, likes: 5, liked: false, content: "This line demonstrates the poet's use of alliteration, creating a rhythmic pattern that emphasizes the emotional tone.", type: "analysis" as const, isOfficial: false, createdAt: new Date(Date.now() - 1000000).toISOString(), }, { id: "2" as const, workId: workId, translationId: translationId, lineNumber: selectedLineNumber, userId: "3" as const, user: { name: "Translator", avatar: undefined, }, likes: 3, liked: false, content: "The original meaning in Russian contains a wordplay that is difficult to capture in English. A more literal translation might read as...", type: "translation" as const, isOfficial: false, createdAt: new Date(Date.now() - 5000000).toISOString(), }, ]; setAnnotations(mockAnnotations); setIsLoading(false); }, 600); }, [workId, selectedLineNumber, translationId]); // Submit new annotation const handleSubmitAnnotation = async () => { if (!newAnnotation.trim() || !selectedLineNumber) return; setIsSubmitting(true); try { // In a real app, this would be an API call // Mock API response const newAnnotationObj: AnnotationWithUser = { id: Date.now().toString(), workId, translationId, lineNumber: selectedLineNumber, userId: currentUser.id.toString(), user: { name: currentUser.name, avatar: currentUser.avatar || undefined, }, content: newAnnotation, type: "comment", isOfficial: false, createdAt: new Date().toISOString(), likes: 0, liked: false, }; // Optimistically update UI setAnnotations((prev) => [newAnnotationObj, ...prev]); setNewAnnotation(""); toast({ description: "Annotation added successfully", }); // In a real app, this would invalidate the query cache // queryClient.invalidateQueries({ queryKey: [`/api/works/${workId}/annotations/${selectedLineNumber}`] }); } catch (_error) { toast({ title: "Error", description: "Failed to add annotation", variant: "destructive", }); } finally { setIsSubmitting(false); } }; // Like an annotation const handleLikeAnnotation = async (annotationId: string) => { try { // Optimistically update UI setAnnotations((prev) => prev.map((anno) => anno.id === annotationId ? { ...anno, liked: !anno.liked, likes: anno.liked ? (anno.likes || 0) - 1 : (anno.likes || 0) + 1, } : anno, ), ); // In a real app, this would be an API call // await apiRequest('POST', `/api/annotations/${annotationId}/like`, { userId: currentUser.id }); } catch (_error) { // Revert optimistic update if there's an error setAnnotations((prev) => [...prev]); toast({ title: "Error", description: "Failed to update like", variant: "destructive", }); } }; // Delete annotation const handleDeleteAnnotation = async (annotationId: string) => { try { // Optimistically update UI const filteredAnnotations = annotations.filter( (anno) => anno.id !== annotationId, ); setAnnotations(filteredAnnotations); // In a real app, this would be an API call // await apiRequest('DELETE', `/api/annotations/${annotationId}`); toast({ description: "Annotation deleted", }); } catch (_error) { // Revert optimistic update if there's an error toast({ title: "Error", description: "Failed to delete annotation", variant: "destructive", }); } }; // Start editing an annotation const handleStartEdit = (annotation: AnnotationWithUser) => { setEditingAnnotationId(annotation.id); setEditText(annotation.content); }; // Save edited annotation const handleSaveEdit = async (annotationId: string) => { if (!editText.trim()) return; try { // Optimistically update UI setAnnotations((prev) => prev.map((anno) => anno.id === annotationId ? { ...anno, content: editText } : anno, ), ); // Reset edit state setEditingAnnotationId(null); setEditText(""); // In a real app, this would be an API call // await apiRequest('PATCH', `/api/annotations/${annotationId}`, { content: editText }); toast({ description: "Annotation updated", }); } catch (_error) { toast({ title: "Error", description: "Failed to update annotation", variant: "destructive", }); } }; // Cancel editing const handleCancelEdit = () => { setEditingAnnotationId(null); setEditText(""); }; // If no line is selected, don't render anything if (!selectedLineNumber) return null; return (

Line {selectedLineNumber} Annotations

{/* New annotation form */}