/
masterOK
/
FEF
Обзор
Документация
Войти
/
masterOK
/
FEF
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
BlogPostPage.tsx
179 строк
6 KB
masterOK
upload files
10 ноя 2025, 19:52
10 ноя 2025, 19:52
2cb4a49
Код
Авторство
О чём код?
import { useQuery } from "@tanstack/react-query"; import { useRoute, Link } from "wouter"; import { useEffect } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Calendar, User, ArrowLeft } from "lucide-react"; interface BlogPost { id: string; title: string; slug: string; content: string; excerpt: string | null; category: string | null; publishedAt: string | null; author: { fullName: string; avatarUrl?: string | null; }; } export default function BlogPostPage() { const [, params] = useRoute("/blog/:slug"); const slug = params?.slug; const { data: post, isLoading } = useQuery<BlogPost>({ queryKey: [`/api/blog/${slug}`], enabled: !!slug, }); useEffect(() => { if (post) { const originalTitle = document.title; document.title = `${post.title} | USLUGI.ru`; let metaDescription = document.querySelector('meta[name="description"]'); const originalDescription = metaDescription?.getAttribute('content'); if (!metaDescription) { metaDescription = document.createElement('meta'); metaDescription.setAttribute('name', 'description'); document.head.appendChild(metaDescription); } metaDescription.setAttribute('content', post.excerpt || post.content.substring(0, 160)); let metaOgTitle = document.querySelector('meta[property="og:title"]'); const originalOgTitle = metaOgTitle?.getAttribute('content'); if (!metaOgTitle) { metaOgTitle = document.createElement('meta'); metaOgTitle.setAttribute('property', 'og:title'); document.head.appendChild(metaOgTitle); } metaOgTitle.setAttribute('content', post.title); let metaOgDescription = document.querySelector('meta[property="og:description"]'); const originalOgDescription = metaOgDescription?.getAttribute('content'); if (!metaOgDescription) { metaOgDescription = document.createElement('meta'); metaOgDescription.setAttribute('property', 'og:description'); document.head.appendChild(metaOgDescription); } metaOgDescription.setAttribute('content', post.excerpt || post.content.substring(0, 160)); return () => { document.title = originalTitle; const descMeta = document.querySelector('meta[name="description"]'); if (descMeta) { if (originalDescription) { descMeta.setAttribute('content', originalDescription); } else { descMeta.remove(); } } const ogTitleMeta = document.querySelector('meta[property="og:title"]'); if (ogTitleMeta) { if (originalOgTitle) { ogTitleMeta.setAttribute('content', originalOgTitle); } else { ogTitleMeta.remove(); } } const ogDescMeta = document.querySelector('meta[property="og:description"]'); if (ogDescMeta) { if (originalOgDescription) { ogDescMeta.setAttribute('content', originalOgDescription); } else { ogDescMeta.remove(); } } }; } }, [post]); if (isLoading) { return ( <div className="container mx-auto px-4 py-12 max-w-4xl"> <Skeleton className="h-8 w-32 mb-8" /> <Skeleton className="h-12 w-full mb-4" /> <Skeleton className="h-6 w-full mb-12" /> <Skeleton className="h-96 w-full" /> </div> ); } if (!post) { return ( <div className="container mx-auto px-4 py-12 text-center"> <h2 className="text-2xl font-bold mb-4">Статья не найдена</h2> <Link href="/blog"> <span> <Button>Вернуться к блогу</Button> </span> </Link> </div> ); } return ( <div className="min-h-screen bg-muted/30"> <div className="container mx-auto px-4 py-12 max-w-4xl"> <Link href="/blog"> <span> <Button variant="ghost" className="mb-6 gap-2" data-testid="button-back-to-blog"> <ArrowLeft className="h-4 w-4" /> Назад к блогу </Button> </span> </Link> <article> {post.category && ( <Badge variant="secondary" className="mb-4"> {post.category} </Badge> )} <h1 className="text-4xl font-bold mb-6" data-testid="text-post-title"> {post.title} </h1> <div className="flex items-center gap-4 mb-8 text-muted-foreground"> <div className="flex items-center gap-2"> <Avatar className="h-8 w-8"> <AvatarImage src={post.author.avatarUrl || undefined} /> <AvatarFallback>{post.author.fullName.charAt(0)}</AvatarFallback> </Avatar> <span className="font-medium">{post.author.fullName}</span> </div> {post.publishedAt && ( <div className="flex items-center gap-1"> <Calendar className="h-4 w-4" /> {new Date(post.publishedAt).toLocaleDateString('ru-RU', { year: 'numeric', month: 'long', day: 'numeric' })} </div> )} </div> <Card> <CardContent className="p-8"> <div className="prose prose-slate dark:prose-invert max-w-none" dangerouslySetInnerHTML={{ __html: post.content }} data-testid="text-post-content" /> </CardContent> </Card> </article> </div> </div> ); }