diff --git a/client/src/App.tsx b/client/src/App.tsx index b749594..9b4712b 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -18,7 +18,6 @@ import Search from "@/pages/Search"; import Submit from "@/pages/Submit"; import Profile from "@/pages/user/Profile"; import SimpleWorkReading from "@/pages/works/SimpleWorkReading"; -import WorkCompare from "@/pages/works/WorkCompare"; import { queryClient } from "./lib/queryClient"; function Router() { @@ -30,10 +29,6 @@ function Router() { - diff --git a/client/src/components/authors/author-works-display.tsx b/client/src/components/authors/author-works-display.tsx index 7dcd067..e257f24 100644 --- a/client/src/components/authors/author-works-display.tsx +++ b/client/src/components/authors/author-works-display.tsx @@ -8,7 +8,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { Grid3X3, List } from "lucide-react"; -import { useAuthorWorks } from "@/hooks/use-author-api"; +import { useQuery } from "@tanstack/react-query"; import type { Work } from "@shared/schema"; import { WorkFilters } from "./author-works/filters"; import { AuthorWorksSkeleton } from "./author-works/skeleton"; @@ -40,13 +40,15 @@ export function AuthorWorksDisplay({ }); // Use the actual API hook to fetch author's works - const { data: works, isLoading, error } = useAuthorWorks(authorId); + const { data: works, isLoading, error } = useQuery({ + queryKey: [`/api/authors/${authorId}/works`], + }); // Convert works with tag objects back to Work type for components const worksForDisplay: Work[] = useMemo(() => - works?.map(work => ({ + works?.map((work: any) => ({ ...work, - tags: work.tags?.map(tag => + tags: work.tags?.map((tag: any) => typeof tag === 'string' ? tag : tag.name ), })) || [], diff --git a/client/src/components/authors/related-authors.tsx b/client/src/components/authors/related-authors.tsx index 7dbef24..9400a73 100644 --- a/client/src/components/authors/related-authors.tsx +++ b/client/src/components/authors/related-authors.tsx @@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Calendar, ExternalLink, Users } from "lucide-react"; -import { useAuthors } from "@/hooks/use-author-api"; +import { useQuery } from "@tanstack/react-query"; import type { Author } from "@shared/schema"; import { Link } from "wouter"; @@ -19,7 +19,9 @@ export function RelatedAuthors({ limit = 6, }: RelatedAuthorsProps) { // Use the actual API hook to fetch authors - const { data: authors, isLoading, error } = useAuthors({ limit: limit + 5 }); // Fetch more to filter out current author + const { data: authors, isLoading, error } = useQuery({ + queryKey: ["/api/authors", { limit: limit + 5 }], + }); // Filter out the current author and limit results const relatedAuthors = diff --git a/client/src/components/comment/comment-thread.tsx b/client/src/components/comment/comment-thread.tsx deleted file mode 100644 index 7a591fc..0000000 --- a/client/src/components/comment/comment-thread.tsx +++ /dev/null @@ -1,695 +0,0 @@ -import { formatDistanceToNow } from "date-fns"; -import { - AlertTriangle, - ChevronDown, - ChevronUp, - Edit, - Heart, - MessageSquare, - MoreVertical, - Reply, - ThumbsUp, - Trash2, -} from "lucide-react"; -import { useState } from "react"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Textarea } from "@/components/ui/textarea"; -import { cn } from "@/lib/utils"; - -/** - * Comment Thread component for displaying hierarchical comments - * - * @example - * ```tsx - * handleAddComment(text, parentId)} - * onEditComment={(id, text) => handleEditComment(id, text)} - * onDeleteComment={(id) => handleDeleteComment(id)} - * onLikeComment={(id) => handleLikeComment(id)} - * /> - * ``` - */ - -export interface User { - id: string | number; - name: string; - avatar?: string; - role?: string; -} - -export interface Comment { - id: string | number; - user: User; - text: string; - timestamp: string | Date; - parentId?: string | number | null; - entityId: string | number; - entityType: string; - likes: number; - isLiked?: boolean; - replies?: number; - status?: "published" | "pending" | "flagged" | "deleted"; - isPinned?: boolean; - isEdited?: boolean; - metadata?: Record; -} - -export interface CommentThreadProps { - /** - * Array of comments to display - */ - comments: Comment[]; - /** - * ID of the current user - */ - currentUserId?: string | number; - /** - * Whether the thread is loading - */ - isLoading?: boolean; - /** - * Maximum nesting level for replies (default: 3) - */ - maxNestingLevel?: number; - /** - * Whether to allow adding new comments - */ - allowComments?: boolean; - /** - * Whether to show reply form by default - */ - showReplyForm?: boolean; - /** - * Placeholder text for the comment input - */ - commentPlaceholder?: string; - /** - * Whether to collapse long comments - */ - collapseComments?: boolean; - /** - * Maximum comment length before collapsing - */ - maxCommentLength?: number; - /** - * Whether to auto-expand the newest comment - */ - autoExpandNewest?: boolean; - /** - * Callback for adding a new comment - */ - onAddComment?: (text: string, parentId?: string | number | null) => void; - /** - * Callback for editing a comment - */ - onEditComment?: (id: string | number, text: string) => void; - /** - * Callback for deleting a comment - */ - onDeleteComment?: (id: string | number) => void; - /** - * Callback for liking a comment - */ - onLikeComment?: (id: string | number, isLiked: boolean) => void; - /** - * Callback for reporting a comment - */ - onReportComment?: (id: string | number, reason?: string) => void; - /** - * Callback for moderating a comment - */ - onModerateComment?: ( - id: string | number, - action: "approve" | "reject" | "flag", - ) => void; - /** - * Callback for pinning a comment - */ - onPinComment?: (id: string | number, isPinned: boolean) => void; - /** - * CSS class for the container - */ - className?: string; -} - -// Recursive function to build the comment tree -const buildCommentTree = ( - comments: Comment[], - parentId: string | number | null = null, -): Comment[] => { - return comments - .filter((comment) => comment.parentId === parentId) - .sort((a, b) => { - // Sort pinned comments first - if (a.isPinned && !b.isPinned) return -1; - if (!a.isPinned && b.isPinned) return 1; - - // Then sort by timestamp (newest first) - return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(); - }); -}; - -// Format timestamps -const formatTimestamp = (date: string | Date): string => { - return formatDistanceToNow(new Date(date), { addSuffix: true }); -}; - -export function CommentThread({ - comments = [], - currentUserId, - isLoading = false, - maxNestingLevel = 3, - allowComments = true, - showReplyForm = false, - commentPlaceholder = "Add a comment...", - collapseComments = true, - maxCommentLength = 300, - autoExpandNewest = true, - onAddComment, - onEditComment, - onDeleteComment, - onLikeComment, - onReportComment, - onModerateComment, - onPinComment, - className, -}: CommentThreadProps) { - const [replyingTo, setReplyingTo] = useState(null); - const [editingId, setEditingId] = useState(null); - const [topLevelText, setTopLevelText] = useState(""); - const [editText, setEditText] = useState(""); - const [replyText, setReplyText] = useState(""); - const [expandedComments, setExpandedComments] = useState< - Record - >({}); - const [expandedThreads, setExpandedThreads] = useState< - Record - >({}); - - // Get the newest comment ID for auto-expanding if needed - const newestCommentId = - comments.length > 0 - ? comments.reduce((newest, comment) => - new Date(comment.timestamp) > new Date(newest.timestamp) - ? comment - : newest, - ).id - : null; - - // Initialize expanded states if not already set - if (autoExpandNewest && newestCommentId && comments.length > 0) { - expandedComments[newestCommentId] = true; - } - - // Handle submitting a top-level comment - const handleSubmitTopLevelComment = () => { - if (topLevelText.trim() && onAddComment) { - onAddComment(topLevelText.trim()); - setTopLevelText(""); - } - }; - - // Handle submitting a reply - const handleSubmitReply = (parentId: string | number) => { - if (replyText.trim() && onAddComment) { - onAddComment(replyText.trim(), parentId); - setReplyText(""); - setReplyingTo(null); - } - }; - - // Handle submitting an edit - const handleSubmitEdit = (id: string | number) => { - if (editText.trim() && onEditComment) { - onEditComment(id, editText.trim()); - setEditText(""); - setEditingId(null); - } - }; - - // Handle liking a comment - const handleLikeComment = (id: string | number, isLiked: boolean) => { - onLikeComment?.(id, !isLiked); - }; - - // Toggle comment expansion - const toggleCommentExpansion = (id: string | number) => { - setExpandedComments((prev) => ({ - ...prev, - [id]: !prev[id], - })); - }; - - // Toggle thread expansion - const toggleThreadExpansion = (id: string | number) => { - setExpandedThreads((prev) => ({ - ...prev, - [id]: !prev[id], - })); - }; - - // Check if user can edit/delete a comment - const canManageComment = (comment: Comment) => { - if (!currentUserId) return false; - return comment.user.id === currentUserId; - }; - - // Check if user is a moderator - const isModerator = () => { - // In a real app, you'd check the user's role - return false; - }; - - // Render a single comment - const renderComment = (comment: Comment, level: number = 0) => { - const isExpanded = expandedComments[comment.id] || !collapseComments; - const isThreadExpanded = expandedThreads[comment.id] !== false; // Default to expanded - const isEditing = editingId === comment.id; - const isReplying = replyingTo === comment.id; - - // Get replies for this comment - const replies = buildCommentTree(comments, comment.id); - - // Check if comment text needs truncation - const needsTruncation = comment.text.length > maxCommentLength; - const displayText = - needsTruncation && !isExpanded - ? `${comment.text.substring(0, maxCommentLength)}...` - : comment.text; - - // Determine if nesting should continue - const shouldNestReplies = level < maxNestingLevel; - - return ( -
0 && "ml-6 mt-3", - comment.isPinned && - "relative bg-muted/20 p-3 rounded-md border-l-2 border-primary", - )} - key={comment.id} - > - {/* Pinned indicator */} - {comment.isPinned && ( -
- - Pinned - -
- )} - -
- {/* User avatar */} - - - {comment.user.name.charAt(0)} - - - {/* Comment content */} -
- {/* Comment header */} -
-
- {comment.user.name} - - {/* User role badge */} - {comment.user.role && ( - - {comment.user.role} - - )} - - {/* Comment status */} - {comment.status === "pending" && ( - - Pending - - )} - {comment.status === "flagged" && ( - - Flagged - - )} - - - {formatTimestamp(comment.timestamp)} - - - {comment.isEdited && ( - - (edited) - - )} -
- - {/* Comment actions */} - - - - - - {canManageComment(comment) && ( - <> - { - setEditingId(comment.id); - setEditText(comment.text); - }} - > - - Edit - - onDeleteComment?.(comment.id)} - > - - Delete - - - - )} - - {isModerator() && ( - <> - - onPinComment?.(comment.id, !comment.isPinned) - } - > - - {comment.isPinned ? "Unpin" : "Pin"} - - {comment.status === "pending" && ( - - onModerateComment?.(comment.id, "approve") - } - > - - Approve - - )} - onModerateComment?.(comment.id, "flag")} - > - - Flag - - - - )} - - onReportComment?.(comment.id)} - > - - Report - - - -
- - {/* Comment text */} - {isEditing ? ( -
-