182 lines
8.6 KiB
TypeScript
182 lines
8.6 KiB
TypeScript
"use client";
|
|
import React, { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { BookOpen, Calendar, Eye, MessageSquare, ArrowLeft } from "lucide-react";
|
|
import { motion } from "motion/react";
|
|
import SafeImage from "./SafeImage";
|
|
import { useSettingsStore } from "../lib/store/settingsStore";
|
|
import api, { BASE_DOMAIN } from "../lib/services/api";
|
|
import { toPersian } from "../lib/utils";
|
|
|
|
export interface BlogPost {
|
|
id: string;
|
|
title: string;
|
|
slug: string;
|
|
content: string;
|
|
imageUrl?: string;
|
|
viewCount: number;
|
|
createdAt: string;
|
|
author?: {
|
|
firstName: string;
|
|
lastName: string;
|
|
};
|
|
_count?: {
|
|
comments: number;
|
|
};
|
|
}
|
|
|
|
export default function BlogPreviewSection() {
|
|
const getText = useSettingsStore((state) => state.getText);
|
|
const [blogs, setBlogs] = useState<BlogPost[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
async function loadBlogs() {
|
|
try {
|
|
setIsLoading(true);
|
|
const res = await api.get("/blogs/homepage");
|
|
const list = Array.isArray(res.data?.blogs)
|
|
? res.data.blogs
|
|
: Array.isArray(res.data)
|
|
? res.data
|
|
: [];
|
|
setBlogs(list);
|
|
} catch (err) {
|
|
console.error("Failed to load homepage blogs:", err);
|
|
setBlogs([]);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}
|
|
loadBlogs();
|
|
}, []);
|
|
|
|
if (!isLoading && blogs.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<section className="py-8 sm:py-20 bg-white font-vazir" dir="rtl">
|
|
<div className="max-w-7xl mx-auto px-4">
|
|
{/* Section Header */}
|
|
<div className="flex flex-col sm:flex-row sm:items-end justify-between mb-6 sm:mb-12 pb-4 sm:pb-6 border-b border-medical-gray-200 gap-3 sm:gap-6">
|
|
<div className="max-w-2xl">
|
|
<div className="inline-flex items-center gap-2 px-3 py-1 bg-canina-blue/10 text-canina-blue rounded-full text-[10px] font-black uppercase tracking-widest mb-2 sm:mb-3 font-vazir">
|
|
<BookOpen className="w-3 h-3" />
|
|
<span>{getText("blog_preview_badge", "دانشنامه و مقالات علمی")}</span>
|
|
</div>
|
|
<h2 className="text-2xl sm:text-3xl md:text-4xl font-black text-medical-gray-900 leading-tight font-lalezar tracking-wide">
|
|
{getText("blog_preview_title", "تازهترین مقالات")} <span className="text-canina-blue">{getText("blog_preview_title_highlight", "دامپزشکی و سلامت")}</span>
|
|
</h2>
|
|
<p className="text-sm sm:text-base text-medical-gray-500 font-medium mt-2 sm:mt-3 leading-relaxed">
|
|
{getText("blog_preview_desc", "راهنماهای علمی و کاربردی برای نگهداری، تغذیه و پیشگیری از بیماریهای سگ و گربه بر اساس آخرین تحقیقات بینالمللی.")}
|
|
</p>
|
|
</div>
|
|
<Link
|
|
href="/blog"
|
|
className="inline-flex items-center gap-1.5 sm:gap-2 text-canina-blue font-black text-xs sm:text-sm hover:translate-x-1 transition-all rtl:hover:-translate-x-1 group font-vazir self-start sm:self-auto shrink-0"
|
|
>
|
|
<span>{getText("blog_preview_view_all", "مشاهده همه مقالات")}</span>
|
|
<ArrowLeft className="w-4 h-4" />
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Blog Grid */}
|
|
{isLoading ? (
|
|
<div className="flex md:grid md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8 overflow-x-auto md:overflow-visible pb-4 md:pb-0 micro-hscroll -mx-4 px-4 md:mx-0 md:px-0 select-none snap-x snap-mandatory md:snap-none overscroll-x-contain">
|
|
{[1, 2, 3].map((i) => (
|
|
<div
|
|
key={i}
|
|
className="w-[68vw] sm:w-[50vw] md:w-auto flex-shrink-0 snap-start bg-medical-gray-50 rounded-3xl p-3 sm:p-4 border border-medical-gray-100 animate-pulse space-y-3 sm:space-y-4"
|
|
>
|
|
<div className="h-36 sm:h-48 bg-medical-gray-200 rounded-2xl" />
|
|
<div className="h-5 sm:h-6 bg-medical-gray-200 rounded-md w-3/4" />
|
|
<div className="h-3.5 sm:h-4 bg-medical-gray-200 rounded-md w-1/2" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="flex md:grid md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8 overflow-x-auto md:overflow-visible pb-4 md:pb-0 micro-hscroll -mx-4 px-4 md:mx-0 md:px-0 select-none snap-x snap-mandatory md:snap-none overscroll-x-contain">
|
|
{blogs.map((blog, idx) => {
|
|
const image = blog.imageUrl?.startsWith("http")
|
|
? blog.imageUrl
|
|
: blog.imageUrl
|
|
? `${BASE_DOMAIN}${blog.imageUrl}`
|
|
: "/assets/images/blog-placeholder.png";
|
|
|
|
const authorName = blog.author
|
|
? `${blog.author.firstName || ""} ${blog.author.lastName || ""}`.trim()
|
|
: "تحریریه کنینا";
|
|
|
|
const formattedDate = new Date(blog.createdAt).toLocaleDateString("fa-IR");
|
|
|
|
const blogHref = `/blog/${encodeURIComponent(blog.slug || blog.id)}`;
|
|
|
|
return (
|
|
<div key={blog.id || idx} className="w-[68vw] sm:w-[50vw] md:w-auto flex-shrink-0 snap-start flex">
|
|
<motion.article
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
transition={{ delay: idx * 0.1 }}
|
|
className="bg-medical-gray-50/60 rounded-[1.5rem] sm:rounded-[2rem] border border-medical-gray-100 overflow-hidden hover:shadow-lg hover:-translate-y-1 transition-all flex flex-col group w-full"
|
|
>
|
|
<Link href={blogHref} className="block relative aspect-video w-full overflow-hidden bg-medical-gray-100">
|
|
<SafeImage
|
|
src={image}
|
|
alt={blog.title}
|
|
sizes="(max-width: 768px) 70vw, (max-width: 1200px) 50vw, 33vw"
|
|
className="w-full h-full"
|
|
imgClassName="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
|
/>
|
|
</Link>
|
|
|
|
<div className="p-3.5 sm:p-6 flex-1 flex flex-col justify-between space-y-3 sm:space-y-4">
|
|
<div className="space-y-1.5 sm:space-y-2">
|
|
<div className="flex items-center gap-2.5 sm:gap-4 text-[10px] sm:text-xs text-medical-gray-400 font-medium">
|
|
<span className="flex items-center gap-1">
|
|
<Calendar className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
|
|
{toPersian(formattedDate)}
|
|
</span>
|
|
<span className="flex items-center gap-1">
|
|
<Eye className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
|
|
{toPersian(blog.viewCount || 0)}
|
|
</span>
|
|
<span className="flex items-center gap-1">
|
|
<MessageSquare className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
|
|
{toPersian(blog._count?.comments || 0)}
|
|
</span>
|
|
</div>
|
|
|
|
<Link href={blogHref}>
|
|
<h3 className="text-xs sm:text-lg font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors line-clamp-2 leading-snug">
|
|
{blog.title}
|
|
</h3>
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="pt-2.5 sm:pt-4 border-t border-medical-gray-200/60 flex items-center justify-between">
|
|
<span className="text-[10px] sm:text-xs font-bold text-medical-gray-500 truncate max-w-[100px] sm:max-w-none">
|
|
{authorName}
|
|
</span>
|
|
<Link
|
|
href={blogHref}
|
|
aria-label={`مطالعه مقاله: ${blog.title}`}
|
|
className="text-[10px] sm:text-xs font-black text-canina-blue group-hover:underline flex items-center gap-1 shrink-0"
|
|
>
|
|
{getText("blog_preview_read_article", "خواندن مقاله")}
|
|
<ArrowLeft className="w-2.5 h-2.5 sm:w-3 sm:h-3" />
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</motion.article>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|