import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { FileText, Plus } from 'lucide-react' import { Breadcrumbs } from '../components/Breadcrumbs' import { PageTitle } from '../components/PageTitle' import { BlogCard } from '../components/BlogCard' import { BlogCommentsModal } from '../components/BlogCommentsModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { Pagination } from '../components/Pagination' import { useToast } from '../context/ToastContext' import { useT } from '../i18n/useT' import { ApiError } from '../lib/api' import { BLOGS_PER_PAGE, deleteBlog, listBlogs, mapBlogApiToUi, setBlogVerified, isBlogVerified, } from '../services/blogService' import type { Blog } from '../types/blog' import pageStyles from '../components/PageContent.module.css' import styles from './BlogPage.module.css' export function BlogListPage() { const navigate = useNavigate() const { showToast } = useToast() const t = useT() const [blogs, setBlogs] = useState([]) const [totalBlogs, setTotalBlogs] = useState(0) const [currentPage, setCurrentPage] = useState(1) const [isLoading, setIsLoading] = useState(true) const [isDeleting, setIsDeleting] = useState(false) const [error, setError] = useState('') const [commentCounts, setCommentCounts] = useState>({}) const [deleteTarget, setDeleteTarget] = useState<{ id: string; title: string } | null>(null) const [commentsTarget, setCommentsTarget] = useState<{ id: string; title: string } | null>(null) const [verifyingId, setVerifyingId] = useState(null) const totalPages = Math.max(1, Math.ceil(totalBlogs / BLOGS_PER_PAGE)) useEffect(() => { const controller = new AbortController() void loadBlogs(currentPage, controller.signal) return () => controller.abort() }, [currentPage]) async function loadBlogs(page: number, signal?: AbortSignal) { setIsLoading(true) setError('') try { const data = await listBlogs(page, BLOGS_PER_PAGE, signal) setBlogs(data.items) setTotalBlogs(data.total) setCommentCounts( Object.fromEntries(data.items.map((blog) => [blog.id, blog.commentCount ?? 0])), ) } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return if (err instanceof ApiError) { setError(err.message) } else { setError(t('blog.list.errorLoad')) } } finally { setIsLoading(false) } } function handleEdit(id: string) { navigate(`/blog/edit/${id}`) } function handleComments(id: string) { const blog = blogs.find((item) => item.id === id) if (blog) { setCommentsTarget({ id, title: blog.title }) } } function handleRemoveRequest(id: string) { const blog = blogs.find((item) => item.id === id) if (blog) { setDeleteTarget({ id, title: blog.title }) } } async function confirmDelete() { if (!deleteTarget) return setIsDeleting(true) setError('') try { await deleteBlog(deleteTarget.id) showToast(t('blog.list.toast.removed'), 'success') const nextTotal = totalBlogs - 1 const nextTotalPages = Math.max(1, Math.ceil(nextTotal / BLOGS_PER_PAGE)) const nextPage = currentPage > nextTotalPages ? nextTotalPages : currentPage setDeleteTarget(null) setCurrentPage(nextPage) await loadBlogs(nextPage) } catch (err) { if (err instanceof ApiError) { setError(err.message) } else { setError(t('blog.list.errorDelete')) } } finally { setIsDeleting(false) } } function handlePageChange(page: number) { setCurrentPage(page) window.scrollTo({ top: 0, behavior: 'smooth' }) } function handleCommentCountChange(count: number) { if (!commentsTarget) return setCommentCounts((prev) => ({ ...prev, [commentsTarget.id]: count, })) } async function handleToggleVerify(id: string) { const blog = blogs.find((item) => item.id === id) if (!blog) return const nextVerified = !isBlogVerified(blog) setVerifyingId(id) setError('') try { const result = await setBlogVerified(id, nextVerified) setBlogs((prev) => prev.map((item) => (item.id === id ? mapBlogApiToUi(result.blog) : item)), ) showToast( nextVerified ? t('blog.list.toast.verified') : t('blog.list.toast.unverified'), 'success', ) } catch (err) { if (err instanceof ApiError) { setError(err.message) } else { setError(t('blog.list.errorVerify')) } } finally { setVerifyingId(null) } } return (
{t('title.myBlogs')}

{t('blog.list.subtitle', { count: totalBlogs })}

{error && (
{error}
)} {isLoading ? (

{t('blog.list.loading')}

) : blogs.length === 0 ? (

{t('blog.list.empty')}

{t('blog.list.emptyHint')}

) : ( <>
{blogs.map((blog) => ( ))}
)} !isDeleting && setDeleteTarget(null)} /> {commentsTarget && ( setCommentsTarget(null)} onCountChange={handleCommentCountChange} /> )}
) }