astro_avtourist/frontend/src/pages/blog/index.astro
Web-serfer b5d2174fdf fix: исправить типизацию, добавить SEO метатеги, исправить API Rules PB
- Добавлены типы PostVotes, VoteStats, Comment, Consultation, PostResponse
- Заменены все any на конкретные типы
- Исправлены catch (error: any) -> catch (error: unknown)
- Добавлены og: и twitter: метатеги в Layout
- Исправлены API Rules в PocketBase (posts, reviews, post_votes)
2026-05-06 22:33:44 +05:00

189 lines
No EOL
5.1 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
import Layout from '@layouts/Layout.astro';
import { SITE_URL } from '@constants';
import PageHero from '@components/base/PageHero.astro';
import BlogCategories from '@components/blog/BlogCategories.astro';
import BlogCard from '@components/blog/BlogCard.astro';
import Pagination from '@components/base/Pagination.astro';
import SearchModal from '@components/base/SearchModal.astro';
import { getPosts, getAllCategories, getPostImageUrl } from '@lib/pb';
import type { Post } from '@globalInterfaces';
const POSTS_PER_PAGE = 6;
const currentPage = 1;
const { posts, total, totalPages } = await getPosts({ page: currentPage, perPage: POSTS_PER_PAGE });
const categories = await getAllCategories();
const formatDate = (date: string) => {
const d = new Date(date);
const day = d.getDate().toString().padStart(2, '0');
const month = (d.getMonth() + 1).toString().padStart(2, '0');
const year = new Date().getFullYear().toString().slice(-2);
return `${day}/${month}/${year}`;
};
const postsCountText = String(total);
---
<Layout
title="Блог — автоюрист в Сургуте"
description="Полезные статьи и советы по автоспорам, ДТП, ОСАГО, лишению прав и защите прав водителей."
canonicalLink={`${SITE_URL}/blog`}
breadcrumbs={[
{ label: "Главная", href: "/" },
{ label: "Блог" }
]}
>
<PageHero
badgeText="БЛОГ И СТАТЬИ"
titleWhite="Полезные статьи"
titleGold="для автовладельцев"
description="Разбираем сложные юридические вопросы простым языком. Советы юриста по автоспорам, ДТП, ОСАГО и защите прав водителей."
layout="with-image"
sideImage="/images/blog/blogImg.avif"
sideImageAlt="Автоюрист Сургут"
experienceBadge={{
number: postsCountText,
text: "ПОЛЕЗНЫХ СТАТЕЙ"
}}
bgImage="/images/blog/blogBg.avif"
icon="edit"
/>
<BlogCategories categories={categories} activeCategory="Все" currentPage={currentPage} />
<!-- Сетка статей -->
<section class="blog-grid-section">
<div class="site-container">
<div class="blog-grid" id="blog-grid">
{posts.map((post: Post) => (
<article class="blog-card-wrapper" data-category={post.category}>
<BlogCard
title={post.title}
description={post.description}
category={post.category}
categoryColor={post.categoryColor}
date={formatDate(post.date)}
readTime={post.readTime}
readmeTime={post.readmeTime}
image={getPostImageUrl(post)}
slug={`/blog/${post.slug}`}
/>
</article>
))}
</div>
<!-- Пагинация -->
<Pagination
currentPage={currentPage}
totalPages={totalPages}
baseUrl="/blog"
/>
</div>
</section>
<SearchModal />
</Layout>
<style>
.blog-grid-section {
padding: 2rem 0 0;
background: #f8fafc;
}
.blog-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
}
.blog-card-wrapper {
transition: opacity 0.3s ease, transform 0.3s ease, display 0.3s ease;
}
/* ===== RESPONSIVE ===== */
@media (max-width: 1024px) {
.blog-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.blog-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.blog-cta {
padding: 3rem 0;
}
}
</style>
<script>
const setupAnimations = () => {
const observerOptions = {
root: null,
rootMargin: '0px 0px -50px 0px',
threshold: 0.1
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const el = entry.target as HTMLElement;
const delay = parseInt(el.dataset.delay || '0');
setTimeout(() => {
el.classList.add('is-visible');
}, delay);
observer.unobserve(el);
}
});
}, observerOptions);
document.querySelectorAll('.animate-on-scroll').forEach((el) => {
observer.observe(el);
});
};
const setupFilter = () => {
const buttons = document.querySelectorAll('.category-btn');
const cards = document.querySelectorAll('.blog-card-wrapper');
const grid = document.getElementById('blog-grid');
buttons.forEach((btn) => {
btn.addEventListener('click', (e) => {
e.preventDefault();
const category = btn.getAttribute('data-category');
// Обновляем активную кнопку
buttons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Фильтруем карточки
cards.forEach((card) => {
const cardCategory = card.getAttribute('data-category');
const el = card as HTMLElement;
if (category === 'Все' || cardCategory === category) {
el.style.display = '';
el.style.opacity = '0';
el.style.transform = 'translateY(20px)';
requestAnimationFrame(() => {
el.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
el.style.opacity = '1';
el.style.transform = 'translateY(0)';
});
} else {
el.style.display = 'none';
}
});
});
});
};
setupAnimations();
setupFilter();
</script>