121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
import React, { useEffect, useMemo, useState } from 'react'
|
|
import { Link, useParams } from 'react-router-dom'
|
|
import { Helmet } from 'react-helmet'
|
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
|
import { BlogPost } from '@/proxy/blog/blog'
|
|
import { blogService } from '@/services/blog.service'
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
import { useStoreState } from '@/store/store'
|
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
|
import { APP_NAME } from '@/constants/app.constant'
|
|
import { translateLabel } from './designer'
|
|
import { PublicPageLoader } from './shared'
|
|
|
|
const CenteredMessage: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
<div className="flex min-h-screen items-center justify-center bg-gray-50 dark:bg-gray-950">
|
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">{children}</h1>
|
|
</div>
|
|
)
|
|
|
|
const BlogDetail: React.FC = () => {
|
|
const { id } = useParams<{ id: string }>()
|
|
const { translate } = useLocalization()
|
|
const { currentLang } = useStoreState((state) => state.locale)
|
|
|
|
const [blogPost, setBlogPost] = useState<BlogPost | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
const fetchBlogPost = async () => {
|
|
setLoading(true)
|
|
setError(null)
|
|
|
|
if (!id) {
|
|
setError(translateLabel(translate, 'Public.blog.notFound', 'Blog yazisi bulunamadi.'))
|
|
setLoading(false)
|
|
return
|
|
}
|
|
|
|
try {
|
|
setBlogPost(await blogService.getPostBySlug(id))
|
|
} catch (fetchError) {
|
|
console.error('Blog yazisi alinamadi:', fetchError)
|
|
setError(translateLabel(translate, 'Public.blog.notFound', 'Blog yazisi bulunamadi.'))
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
fetchBlogPost()
|
|
// `translate` yalnizca hata metni icin kullanilir; dil degisiminde yeniden istek atilmaz.
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [id])
|
|
|
|
const title = blogPost ? translate('::Public.' + blogPost.title) : ''
|
|
|
|
// Icerik anahtari yoksa `'::' + undefined` cevirisi "undefined" basiyordu.
|
|
const content = useMemo(() => {
|
|
if (!blogPost) {
|
|
return ''
|
|
}
|
|
|
|
const contentKey = currentLang === 'tr' ? blogPost.contentTr : blogPost.contentEn
|
|
|
|
return contentKey ? translate('::' + contentKey) : ''
|
|
}, [blogPost, currentLang, translate])
|
|
|
|
if (loading) {
|
|
return <PublicPageLoader />
|
|
}
|
|
|
|
if (error) {
|
|
return <CenteredMessage>{error}</CenteredMessage>
|
|
}
|
|
|
|
if (!blogPost) {
|
|
return <CenteredMessage>{translate('::Public.blog.notFound')}</CenteredMessage>
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
|
|
<Helmet
|
|
titleTemplate={`%s | ${APP_NAME}`}
|
|
title={title || translate('::App.BlogManagement')}
|
|
defaultTitle={APP_NAME}
|
|
/>
|
|
<div className="relative bg-blue-900 py-12 text-white dark:bg-gray-950" />
|
|
|
|
<div className="container mx-auto px-4">
|
|
<Link
|
|
to={ROUTES_ENUM.public.blog}
|
|
className="mb-4 mt-4 inline-block text-blue-600 hover:underline dark:text-blue-400"
|
|
>
|
|
← {translate('::App.BlogManagement')}
|
|
</Link>
|
|
|
|
{blogPost.coverImage && (
|
|
<img
|
|
src={blogPost.coverImage}
|
|
alt={title}
|
|
className="mb-8 h-96 w-full rounded-lg object-cover"
|
|
/>
|
|
)}
|
|
|
|
<h1 className="mb-6 text-4xl font-bold text-gray-900 dark:text-gray-100">{title}</h1>
|
|
|
|
<div className="mb-8 flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400">
|
|
<span>{blogPost.author}</span>
|
|
<span>{showDbDateAsIs(blogPost.publishedAt || blogPost.creationTime)}</span>
|
|
</div>
|
|
|
|
<div
|
|
className="prose max-w-none pb-12 text-gray-800 dark:prose-invert dark:text-gray-200"
|
|
dangerouslySetInnerHTML={{ __html: content }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default BlogDetail
|