Files
next-dj/components/BlogSection.tsx
Beyhan Oğur e881f38e4e first commit
2026-04-26 22:12:36 +03:00

430 lines
16 KiB
TypeScript
Raw Permalink 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.
"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { PostType, Categorie, PaginatedResponse } from "@/Type/post";
import { getBlogPosts, getBlogCategories, extractPageNumber } from "@/lib/blogApi";
export default function BlogSection() {
const [mounted, setMounted] = useState(false);
const [posts, setPosts] = useState<PostType[]>([]);
const [recentPosts, setRecentPosts] = useState<PostType[]>([]);
const [categories, setCategories] = useState<Categorie[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [nextPage, setNextPage] = useState<string | null>(null);
const [prevPage, setPrevPage] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
setMounted(true);
// Initialize WOW.js after mount
if (typeof window !== "undefined" && window.WOW) {
new window.WOW().init();
}
}, []);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
setError(null);
try {
// Fetch posts, recent posts (from different page) and categories in parallel
const [postsData, recentPostsData, categoriesData] = await Promise.all([
getBlogPosts(currentPage),
getBlogPosts(currentPage === 1 ? 2 : 1), // Get from different page
getBlogCategories()
]);
setPosts(postsData.results);
setNextPage(postsData.next);
setPrevPage(postsData.previous);
// Calculate total pages (assuming 10 items per page, adjust if needed)
const itemsPerPage = postsData.results.length || 10;
setTotalPages(Math.ceil(postsData.count / itemsPerPage));
// Set recent posts (exclude current page posts)
const currentPostSlugs = new Set(postsData.results.map(p => p.slug));
const filteredRecentPosts = recentPostsData.results
.filter(post => !currentPostSlugs.has(post.slug))
.slice(0, 3);
// If we don't have enough recent posts, try to get more from page 3
if (filteredRecentPosts.length < 3 && currentPage !== 3) {
try {
const additionalPostsData = await getBlogPosts(3);
const additionalFiltered = additionalPostsData.results
.filter(post => !currentPostSlugs.has(post.slug))
.slice(0, 3 - filteredRecentPosts.length);
setRecentPosts([...filteredRecentPosts, ...additionalFiltered]);
} catch {
setRecentPosts(filteredRecentPosts);
}
} else {
setRecentPosts(filteredRecentPosts);
}
setCategories(categoriesData);
} catch (err) {
console.error("Error fetching blog data:", err);
setError("Blog yazıları yüklenirken bir hata oluştu.");
} finally {
setIsLoading(false);
}
};
fetchData();
}, [currentPage]);
const handlePageChange = (page: number) => {
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
window.scrollTo({ top: 0, behavior: "smooth" });
}
};
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleDateString("tr-TR", {
year: "numeric",
month: "long",
day: "numeric"
});
};
const getPageNumbers = () => {
const pages: (number | string)[] = [];
const maxVisible = 5;
if (totalPages <= maxVisible) {
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
if (currentPage <= 3) {
for (let i = 1; i <= 4; i++) {
pages.push(i);
}
pages.push("...");
pages.push(totalPages);
} else if (currentPage >= totalPages - 2) {
pages.push(1);
pages.push("...");
for (let i = totalPages - 3; i <= totalPages; i++) {
pages.push(i);
}
} else {
pages.push(1);
pages.push("...");
for (let i = currentPage - 1; i <= currentPage + 1; i++) {
pages.push(i);
}
pages.push("...");
pages.push(totalPages);
}
}
return pages;
};
// Get unique tags from all posts
const allTags = Array.from(
new Set(
posts.flatMap(post => post.tags.map(tag => tag.tag))
)
);
// Get category count from API data
const getCategoryCount = (category: Categorie): number => {
if (category.posts && category.posts.length > 0) {
return category.posts.length;
}
return 0;
};
// Flatten categories to show both parent and child categories
const flattenCategories = (categories: Categorie[]): Categorie[] => {
const result: Categorie[] = [];
categories.forEach(category => {
if (category.is_active) {
// Add parent category
result.push(category);
// Add child categories if they exist
if (category.child && category.child.length > 0) {
category.child.forEach(child => {
if (child.is_active) {
result.push(child);
}
});
}
}
});
return result;
};
return (
<div className="blog-section" suppressHydrationWarning>
{/* Divider */}
<div className="divider"></div>
<div className="container">
<div className="row g-5 g-md-4 g-xl-5">
<div className="col-12 col-md-7 col-lg-8">
{/* Blog List Wrapper */}
<div className="blog-list-wrapper pe-lg-3">
{isLoading ? (
<div className="text-center py-5">
<p>Yükleniyor...</p>
</div>
) : error ? (
<div className="text-center py-5">
<p className="text-danger">{error}</p>
</div>
) : posts.length === 0 ? (
<div className="text-center py-5">
<p>Henüz blog yazısı bulunmamaktadır.</p>
</div>
) : (
<>
{posts.map((post, index) => (
<div
key={post.slug}
className={`blog-card-two ${mounted ? 'wow fadeInUp' : ''}`}
data-wow-duration="1000ms"
data-wow-delay={`${(index + 1) * 200}ms`}
suppressHydrationWarning
>
{/* Post Image */}
{post.image && (
<div className="post-img">
<img src={post.image} alt={post.title} />
</div>
)}
{/* Post Body */}
<div className="post-body">
<div className="blog-meta flex-wrap d-flex align-items-center gap-4 mb-3">
<a href="#">
<svg width="20" height="20">
<use xlinkHref="#icon-user-profile"></use>
</svg>
By Admin
</a>
<a href="#">
<svg width="18" height="18">
<use xlinkHref="#icon-message-box"></use>
</svg>
{formatDate(post.created_at)}
</a>
{post.categories.length > 0 && (
<a href="#">
<svg width="18" height="18">
<use xlinkHref="#icon-calendar"></use>
</svg>
{post.categories[0].title}
</a>
)}
</div>
<Link className="post-title" href={`/blog/${post.slug}`}>
{post.title}
</Link>
<p className="mt-3 mb-5">
{post.content.length > 200
? `${post.content.substring(0, 200)}...`
: post.content}
</p>
<Link className="btn btn-primary" href={`/blog/${post.slug}`}>
Read More <i className="ti ti-arrow-up-right"></i>
</Link>
</div>
</div>
))}
{/* Pagination */}
{totalPages > 1 && (
<ul className="softora-pagination list-unstyled justify-content-start">
{prevPage && (
<li>
<a
className="magnet-link"
href="#"
onClick={(e) => {
e.preventDefault();
handlePageChange(currentPage - 1);
}}
>
<i className="ti ti-chevron-left"></i>
</a>
</li>
)}
{getPageNumbers().map((page, index) => {
const isActive = typeof page === "number" && currentPage === page;
return (
<li key={index} className={isActive ? "active" : ""}>
{page === "..." ? (
<span className="magnet-link">{page}</span>
) : (
<a
className="magnet-link"
href="#"
onClick={(e) => {
e.preventDefault();
handlePageChange(page as number);
}}
>
{page}
</a>
)}
</li>
);
})}
{nextPage && (
<li>
<a
className="magnet-link"
href="#"
onClick={(e) => {
e.preventDefault();
handlePageChange(currentPage + 1);
}}
>
<i className="ti ti-chevron-right"></i>
</a>
</li>
)}
</ul>
)}
</>
)}
</div>
</div>
<div className="col-12 col-md-5 col-lg-4">
<div className="d-flex flex-column gap-5">
{/* Widget */}
<div className="blog-widget">
<div className="h4 fw-bold mb-4">Search Here</div>
{/* Form */}
<form action="#" method="get">
<input type="search" placeholder="Search here..." className="form-control" />
<button type="submit">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"
fill="none">
<g clipPath="url(#clip0_1_17841)">
<path
d="M2.5 8.33333C2.5 9.09938 2.65088 9.85792 2.94404 10.5657C3.23719 11.2734 3.66687 11.9164 4.20854 12.4581C4.75022 12.9998 5.39328 13.4295 6.10101 13.7226C6.80875 14.0158 7.56729 14.1667 8.33333 14.1667C9.09938 14.1667 9.85792 14.0158 10.5657 13.7226C11.2734 13.4295 11.9164 12.9998 12.4581 12.4581C12.9998 11.9164 13.4295 11.2734 13.7226 10.5657C14.0158 9.85792 14.1667 9.09938 14.1667 8.33333C14.1667 7.56729 14.0158 6.80875 13.7226 6.10101C13.4295 5.39328 12.9998 4.75022 12.4581 4.20854C11.9164 3.66687 11.2734 3.23719 10.5657 2.94404C9.85792 2.65088 9.09938 2.5 8.33333 2.5C7.56729 2.5 6.80875 2.65088 6.10101 2.94404C5.39328 3.23719 4.75022 3.66687 4.20854 4.20854C3.66687 4.75022 3.23719 5.39328 2.94404 6.10101C2.65088 6.80875 2.5 7.56729 2.5 8.33333Z"
stroke="white" strokeLinecap="round" strokeLinejoin="round" />
<path d="M17.5 17.5L12.5 12.5" stroke="white" strokeLinecap="round"
strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_1_17841">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
</button>
</form>
</div>
{/* Widget - Categories */}
<div className="blog-widget">
<div className="h4 fw-bold mb-4">Categories</div>
<ul className="blog-list style-two">
{categories.map((category) => {
if (!category.is_active) return null;
return (
<li key={category.slug}>
<Link href={`/blog/category/${category.slug}`}>
{category.title}
<span>{getCategoryCount(category)}</span>
</Link>
{/* Child Categories */}
{category.child && category.child.length > 0 && (
<ul className="blog-list style-two ms-3 mt-2 mb-0" style={{ listStyle: 'none', paddingLeft: '1.5rem', borderLeft: '2px solid rgba(31, 30, 33, 0.2)' }}>
{category.child
.filter(child => child.is_active)
.map((child) => (
<li key={child.slug} style={{ marginTop: '0.5rem' }}>
<Link href={`/blog/category/${child.slug}`} style={{ fontSize: '0.95em', color: '#666' }}>
{child.title}
<span>{getCategoryCount(child)}</span>
</Link>
</li>
))}
</ul>
)}
</li>
);
})}
</ul>
</div>
{/* Widget - Recent Posts */}
<div className="blog-widget">
<div className="h4 fw-bold mb-4">Recent Posts</div>
<div className="d-flex flex-column gap-4">
{recentPosts.length > 0 ? (
recentPosts.map((post) => (
<div key={post.slug} className="widget-blog-post">
{post.thumb && (
<div className="blog-thumbnail">
<img src={post.thumb} alt={post.title} />
</div>
)}
<div className="blog-content">
<p className="mb-1 text-primary">{formatDate(post.created_at)}</p>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</div>
</div>
))
) : (
<p className="text-muted">Henüz yazı bulunmamaktadır.</p>
)}
</div>
</div>
{/* Widget - Tags */}
{allTags.length > 0 && (
<div className="blog-widget">
<div className="h4 fw-bold mb-4">Tags</div>
<ul className="tag-list list-unstyled">
{allTags.slice(0, 8).map((tag, index) => (
<li key={index}>
<a href={`/blog/tag/${tag.toLowerCase().replace(/\s+/g, '-')}`}>
{tag}
</a>
</li>
))}
</ul>
</div>
)}
</div>
</div>
</div>
</div>
{/* Divider */}
<div className="divider"></div>
</div>
);
}
// Extend Window interface
declare global {
interface Window {
WOW: any;
}
}