430 lines
16 KiB
TypeScript
430 lines
16 KiB
TypeScript
"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;
|
||
}
|
||
}
|