first commit

This commit is contained in:
Web-serfer 2026-03-30 20:21:41 +05:00
commit 4a589825c2
297 changed files with 33019 additions and 0 deletions

View file

@ -0,0 +1,152 @@
---
import { MONTHS } from "@lib/constants";
const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || "http://localhost:8090";
export interface Props {
currentPostId?: string;
currentPostSlug?: string;
}
const { currentPostId, currentPostSlug } = Astro.props;
interface PostRecord {
id: string;
title: string;
slug: string;
excerpt: string;
image?: string;
tags?: string;
created: string;
}
interface PocketBaseResponse {
items: PostRecord[];
}
// Получаем похожие посты (исключая текущий)
let relatedPosts: PostRecord[] = [];
try {
const filter = currentPostId ? `id!="${currentPostId}"` : "";
const response = await fetch(
`${POCKETBASE_URL}/api/collections/posts/records?filter=${encodeURIComponent(filter)}&perPage=3`
);
const data: PocketBaseResponse = await response.json();
if (data.items) {
relatedPosts = data.items;
}
} catch (error) {
console.error("Error fetching related posts:", error);
}
// Форматируем дату
function formatDate(dateString: string) {
const date = new Date(dateString);
return `${date.getDate()} ${MONTHS[date.getMonth()]} ${date.getFullYear()}`;
}
// Парсим tags
function parseTags(tagsString?: string): string {
if (!tagsString) return "НОВОСТИ";
try {
const tags = typeof tagsString === "string" ? JSON.parse(tagsString) : tagsString;
return Array.isArray(tags) && tags.length > 0 ? tags[0].toUpperCase() : "НОВОСТИ";
} catch {
return "НОВОСТИ";
}
}
---
<section
class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 border-t border-gray-200"
>
{/* Заголовок */}
<div class="flex items-center justify-between mb-8">
<h2 class="text-2xl font-bold text-gray-900">Похожие публикации</h2>
<a
href="/blog"
class="text-blue-600 hover:text-blue-800 font-medium text-sm flex items-center gap-1 transition-colors group"
>
Все статьи
<svg
class="w-4 h-4 transition-transform group-hover:translate-x-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"></path>
</svg>
</a>
</div>
{/* Сетка карточек */}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{relatedPosts && relatedPosts.length > 0 ? (
relatedPosts.map((post) => {
const imageUrl = post.image
? `${POCKETBASE_URL}/api/files/posts/${post.id}/${post.image}`
: "https://images.unsplash.com/photo-1589829085413-56de8ae18c73?q=80&w=800&auto=format&fit=crop";
return (
<article class="group bg-white rounded-2xl overflow-hidden shadow-sm border border-gray-100 hover:shadow-lg transition-all duration-300">
{/* Изображение */}
<a
href={`/blog/${post.slug}`}
class="block relative overflow-hidden aspect-video"
>
<img
src={imageUrl}
alt={post.title}
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
<div class="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
</a>
{/* Контент */}
<div class="p-6">
<span class="inline-block px-3 py-1 bg-blue-50 text-blue-600 text-xs font-medium rounded-full mb-3">
{parseTags(post.tags)}
</span>
<h3 class="text-lg font-bold text-gray-900 mb-3 line-clamp-2 group-hover:text-blue-600 transition-colors">
<a href={`/blog/${post.slug}`}>{post.title}</a>
</h3>
<p class="text-gray-600 text-sm line-clamp-3 mb-4 leading-relaxed">
{post.excerpt}
</p>
<a
href={`/blog/${post.slug}`}
class="inline-flex items-center gap-2 text-blue-600 font-medium text-sm hover:text-blue-800 transition-colors group/link"
>
Читать далее
<svg
class="w-4 h-4 transition-transform group-hover/link:translate-x-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 8l4 4m0 0l-4 4m4-4H3"
/>
</svg>
</a>
</div>
</article>
);
})
) : (
<p class="col-span-full text-center text-gray-500 py-8">Нет связанных публикаций</p>
)}
</div>
</section>