import React from 'react';
import { Box, Container, Heading, VStack, Image, Text, Skeleton, LinkBox, HStack, Select, Badge, useColorModeValue } from '@chakra-ui/react';
import { useInfiniteQuery } from '@tanstack/react-query';
import { getArticles, Article, Paginated } from '../services/articles';
import { Link as RouterLink } from 'react-router-dom';
import { assetUrl } from '../utils/url';
import MainLayout from '../components/layout/MainLayout';
import { getCategories, CategoryItem } from '../services/categories';
import SponsorsSection from '../components/common/SponsorsSection';
import NewsletterCTA from '../components/common/NewsletterCTA';
import { Eye, Clock } from 'lucide-react';
const BlogTile: React.FC<{ article: Article }> = ({ article }) => {
const link = article.slug ? `/news/${article.slug}` : `/articles/${article.id}`;
const readTime = article.read_time || article.estimated_read_minutes;
const viewCount = article.view_count;
const bgColor = useColorModeValue('white', 'gray.800');
return (
{/* Stats badges at top */}
{(readTime || (viewCount && viewCount > 0)) && (
{readTime && (
{readTime} min
)}
{viewCount && viewCount > 0 && (
{viewCount}
)}
)}
{article.title}
);
};
const BlogPage: React.FC = () => {
const pageSize = 18;
const [categories, setCategories] = React.useState([]);
const [categoryId, setCategoryId] = React.useState('');
const borderColor = useColorModeValue('gray.200', 'gray.700');
const textColor = useColorModeValue('gray.500', 'gray.400');
React.useEffect(() => {
(async () => {
try {
const list = await getCategories();
setCategories(list || []);
} catch {}
})();
}, []);
const {
data,
isLoading,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
} = useInfiniteQuery>(
['articles-public', { page_size: pageSize, published: true, category_id: categoryId || undefined }],
({ pageParam = 1 }) =>
getArticles({
page: pageParam,
page_size: pageSize,
published: true,
...(categoryId ? { category_id: Number(categoryId) } : {}),
}),
{
getNextPageParam: (lastPage, allPages) => {
const loaded = allPages.reduce((sum, p) => sum + (p?.data?.length || 0), 0);
if (!lastPage) return undefined;
if (loaded < (lastPage.total || 0)) return allPages.length + 1;
return undefined;
},
}
);
const articles = data?.pages?.flatMap((p) => p?.data || []) || [];
// Infinite scroll via intersection observer
const sentinelRef = React.useRef(null);
React.useEffect(() => {
if (!hasNextPage || !sentinelRef.current) return;
const el = sentinelRef.current;
const io = new IntersectionObserver((entries) => {
const first = entries[0];
if (first.isIntersecting && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}, { rootMargin: '400px' });
io.observe(el);
return () => io.disconnect();
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
return (
{/* Header like blog.html */}
Blog
{!!categories.length && (
)}
{/* Masonry using CSS columns */}
{isLoading && Array.from({ length: 9 }).map((_, i) => (
))}
{!isLoading && articles.map((a) => (
))}
{!isLoading && !articles.length && (
Žádné články k zobrazení.
)}
{/* Infinite scroll sentinel */}
{isFetchingNextPage && (
Načítání…
)}
{/* Newsletter CTA */}
{/* Sponsors Section */}
);
};
export default BlogPage;