import { useMutation, useQuery } from "@tanstack/react-query"; import { format } from "date-fns"; import { Bookmark, BookmarkPlus, BookOpen, Calendar, ChevronLeft, Clock, CornerDownLeft, Edit, MessageSquare, Share2, ThumbsUp, } from "lucide-react"; import { useState } from "react"; import { Link, useParams } from "wouter"; import { PageLayout } from "@/components/layout/PageLayout"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { Textarea } from "@/components/ui/textarea"; import { useToast } from "@/hooks/use-toast"; import { apiRequest, queryClient } from "@/lib/queryClient"; import type { BlogPostWithDetails } from "@/lib/types"; export default function BlogDetail() { const { slug } = useParams(); const { toast } = useToast(); const [comment, setComment] = useState(""); const [isLiked, setIsLiked] = useState(false); const [isBookmarked, setIsBookmarked] = useState(false); // Fetch blog post const { data: post, isLoading, error, } = useQuery({ queryKey: [`/api/blog/${slug}`], }); // Comment mutation const commentMutation = useMutation({ mutationFn: async (commentText: string) => { // In a real app, this would be an API call to add a comment return await apiRequest("POST", "/api/comments", { userId: 1, // Using a mock user for demo content: commentText, entityType: "blogPost", entityId: post?.id, }); }, onSuccess: () => { toast({ description: "Comment added successfully", }); setComment(""); // Invalidate queries to refresh the data queryClient.invalidateQueries({ queryKey: [`/api/blog/${slug}`] }); }, onError: (_error) => { toast({ title: "Error", description: "Failed to add comment", variant: "destructive", }); }, }); // Like mutation const likeMutation = useMutation({ mutationFn: async () => { // In a real app, this would be an API call to like/unlike return await apiRequest("POST", "/api/likes", { userId: 1, entityType: "blogPost", entityId: post?.id, }); }, onSuccess: () => { setIsLiked(!isLiked); toast({ description: isLiked ? "Removed like" : "Added like", }); // Invalidate queries to refresh the data queryClient.invalidateQueries({ queryKey: [`/api/blog/${slug}`] }); }, onError: (_error) => { toast({ title: "Error", description: "Failed to update like", variant: "destructive", }); }, }); // Bookmark mutation const bookmarkMutation = useMutation({ mutationFn: async () => { // In a real app, this would be an API call to bookmark/unbookmark return await apiRequest("POST", "/api/bookmarks", { userId: 1, entityType: "blogPost", entityId: post?.id, }); }, onSuccess: () => { setIsBookmarked(!isBookmarked); toast({ description: isBookmarked ? "Removed from bookmarks" : "Added to bookmarks", }); }, onError: (_error) => { toast({ title: "Error", description: "Failed to update bookmark", variant: "destructive", }); }, }); // Share functionality const handleShare = async () => { try { if (navigator.share) { await navigator.share({ title: post?.title || "Tercul Blog Post", text: post?.excerpt || "Check out this article on Tercul", url: window.location.href, }); } else { // Fallback for browsers that don't support the Web Share API navigator.clipboard.writeText(window.location.href); toast({ description: "Link copied to clipboard", }); } } catch (error) { console.error("Error sharing:", error); } }; // Handle comment submission const handleCommentSubmit = () => { if (!comment.trim()) return; commentMutation.mutate(comment); }; // Format date for display const formatDate = (date: string | null) => { if (!date) return ""; return format(new Date(date), "MMMM d, yyyy"); }; if (isLoading) { return (
); } if (error || !post) { return (

Blog post not found

The article you're looking for could not be found.

); } return (

{post.title}

{formatDate(post.publishedAt || post.createdAt)}
{Math.ceil(post.content.length / 1000)} min read
{post.tags && post.tags.length > 0 && ( <>
{post.tags.map((tag) => ( {tag.name} ))}
)}
{post.author?.displayName?.charAt(0) || "A"}

{post.author?.displayName || "Anonymous"}

{post.author?.role || "Contributor"}