Skip to content

Commit bc5f319

Browse files
committed
Add blog post pagination
1 parent 9f943c3 commit bc5f319

File tree

1 file changed

+82
-23
lines changed

1 file changed

+82
-23
lines changed

src/pages/Blog.tsx

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11

22
import React, { useState, useMemo } from 'react';
33
import { useQuery } from '@tanstack/react-query';
4-
import { Calendar, User, ArrowRight } from 'lucide-react';
4+
import { Calendar, User, ArrowRight, ChevronLeft, ChevronRight } from 'lucide-react';
55
import { Link } from 'react-router-dom';
66
import Navbar from '../components/Navbar';
77
import Footer from '../components/Footer';
88
import BlogFilter from '../components/BlogFilter';
99
import { motion } from 'framer-motion';
10+
import { Button } from '../components/ui/button';
1011

1112
interface BlogPost {
1213
id: string;
@@ -28,29 +29,26 @@ interface BlogPost {
2829
}
2930

3031
const fetchBlogPosts = async (): Promise<BlogPost[]> => {
31-
const response = await fetch('https://blog-api.checkcle.io/api/collections/blog_detail/records');
32+
const response = await fetch('https://blog-api.checkcle.io/api/collections/blog_detail/records?sort=-published_at');
3233
if (!response.ok) {
3334
throw new Error('Failed to fetch blog posts');
3435
}
3536
const data = await response.json();
36-
const items: BlogPost[] = data.items || [];
37-
38-
// Sort posts by published_at (or fallback to created) in descending order
39-
return items.sort((a, b) =>
40-
new Date(b.published_at || b.created).getTime() - new Date(a.published_at || a.created).getTime()
41-
);
37+
return data.items || [];
4238
};
4339

44-
4540
const Blog = () => {
4641
const [selectedCategory, setSelectedCategory] = useState('all');
42+
const [currentPage, setCurrentPage] = useState(1);
43+
const postsPerPage = 6;
44+
4745
const { data: posts, isLoading, error } = useQuery({
4846
queryKey: ['blog-posts'],
4947
queryFn: fetchBlogPosts,
5048
});
5149

52-
const { filteredPosts, postCounts } = useMemo(() => {
53-
if (!posts) return { filteredPosts: [], postCounts: {} };
50+
const { filteredPosts, postCounts, paginatedPosts, totalPages } = useMemo(() => {
51+
if (!posts) return { filteredPosts: [], postCounts: {}, paginatedPosts: [], totalPages: 0 };
5452

5553
const publishedPosts = posts.filter(post => post.status === 'published');
5654

@@ -82,9 +80,19 @@ const Blog = () => {
8280
post.categories.includes(selectedCategory)
8381
);
8482

85-
return { filteredPosts: filtered, postCounts: counts };
86-
}, [posts, selectedCategory]);
83+
// Calculate pagination
84+
const totalPages = Math.ceil(filtered.length / postsPerPage);
85+
const startIndex = (currentPage - 1) * postsPerPage;
86+
const endIndex = startIndex + postsPerPage;
87+
const paginatedPosts = filtered.slice(startIndex, endIndex);
88+
89+
return { filteredPosts: filtered, postCounts: counts, paginatedPosts, totalPages };
90+
}, [posts, selectedCategory, currentPage, postsPerPage]);
8791

92+
// Reset to first page when category changes
93+
React.useEffect(() => {
94+
setCurrentPage(1);
95+
}, [selectedCategory]);
8896

8997
const formatDate = (dateString: string) => {
9098
return new Date(dateString).toLocaleDateString('en-US', {
@@ -121,8 +129,13 @@ const Blog = () => {
121129
);
122130
}
123131

124-
const featuredPosts = filteredPosts.filter(post => post.is_featured);
125-
const regularPosts = filteredPosts.filter(post => !post.is_featured);
132+
const featuredPosts = paginatedPosts.filter(post => post.is_featured);
133+
const regularPosts = paginatedPosts.filter(post => !post.is_featured);
134+
135+
const handlePageChange = (page: number) => {
136+
setCurrentPage(page);
137+
window.scrollTo({ top: 0, behavior: 'smooth' });
138+
};
126139

127140
return (
128141
<div className="flex flex-col min-h-screen bg-black text-white">
@@ -138,7 +151,7 @@ const Blog = () => {
138151
transition={{ duration: 0.8 }}
139152
className="text-5xl md:text-6xl font-bold mb-6 gradient-text"
140153
>
141-
CheckCle Blog
154+
CheckCle Community
142155
</motion.h1>
143156
<motion.p
144157
initial={{ opacity: 0, y: 20 }}
@@ -151,7 +164,6 @@ const Blog = () => {
151164
</div>
152165
</section>
153166

154-
{/* Blog Filter */}
155167
<section className="py-8 bg-[#020617]">
156168
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
157169
<BlogFilter
@@ -166,7 +178,7 @@ const Blog = () => {
166178
{featuredPosts.length > 0 && (
167179
<section className="py-16 bg-[#020617]">
168180
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
169-
{selectedCategory === 'all' && <h3 className="text-2xl font-bold mb-12 text-center">Featured Posts</h3>}
181+
{selectedCategory === 'all' && <h3 className="text-2xl font-bold mb-12 text-center">Featured Posts</h3>}
170182
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
171183
{featuredPosts.map((post, index) => (
172184
<motion.article
@@ -217,8 +229,7 @@ const Blog = () => {
217229
</section>
218230
)}
219231

220-
221-
{regularPosts.length > 0 && (
232+
{regularPosts.length > 0 && (
222233
<section className="py-16">
223234
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
224235
{selectedCategory === 'all' && featuredPosts.length > 0 && <h3 className="text-2xl font-bold mb-12 text-center">Latest Posts</h3>}
@@ -241,8 +252,8 @@ const Blog = () => {
241252
) : (
242253
<div className="text-white text-6xl font-bold opacity-50">R</div>
243254
)}
244-
</div>
245-
<div className="p-6">
255+
</div>
256+
<div className="p-6">
246257
<div className="flex items-center text-sm text-gray-400 mb-3">
247258
<User className="w-4 h-4 mr-2" />
248259
<span>{post.author_name}</span>
@@ -268,9 +279,57 @@ const Blog = () => {
268279
</motion.article>
269280
))}
270281
</div>
282+
</div>
283+
</section>
284+
)}
271285

286+
{/* Pagination */}
287+
{totalPages > 1 && (
288+
<section className="py-8">
289+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
290+
<div className="flex justify-center items-center gap-4">
291+
<Button
292+
variant="outline"
293+
size="default"
294+
onClick={() => handlePageChange(currentPage - 1)}
295+
disabled={currentPage === 1}
296+
className="flex items-center gap-2 px-4 py-2 border-emerald-400/30 text-emerald-400 hover:bg-emerald-400/10 disabled:opacity-50 disabled:cursor-not-allowed"
297+
>
298+
<ChevronLeft className="w-4 h-4" />
299+
Previous
300+
</Button>
301+
302+
<div className="flex items-center gap-2">
303+
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
304+
<Button
305+
key={page}
306+
variant={currentPage === page ? "default" : "ghost"}
307+
size="default"
308+
onClick={() => handlePageChange(page)}
309+
className={`min-w-[40px] h-10 ${
310+
currentPage === page
311+
? "bg-emerald-400 text-black hover:bg-emerald-500"
312+
: "text-emerald-400 hover:bg-emerald-400/10"
313+
}`}
314+
>
315+
{page}
316+
</Button>
317+
))}
318+
</div>
319+
320+
<Button
321+
variant="outline"
322+
size="default"
323+
onClick={() => handlePageChange(currentPage + 1)}
324+
disabled={currentPage === totalPages}
325+
className="flex items-center gap-2 px-4 py-2 border-emerald-400/30 text-emerald-400 hover:bg-emerald-400/10 disabled:opacity-50 disabled:cursor-not-allowed"
326+
>
327+
Next
328+
<ChevronRight className="w-4 h-4" />
329+
</Button>
330+
</div>
272331
</div>
273-
</section>
332+
</section>
274333
)}
275334
</main>
276335

0 commit comments

Comments
 (0)