pagenation updated blog page
This commit is contained in:
parent
fd451520f2
commit
a2df3a5fea
@ -2,27 +2,13 @@
|
||||
import Link from "next/link";
|
||||
import Layout from "@/components/layout/Layout";
|
||||
import Blogs from "@/utils/constant.utils";
|
||||
import BlogList from "@/components/elements/BlogList";
|
||||
|
||||
export const metadata = {
|
||||
title: "Street-Food Stories from Sixty5 Street",
|
||||
description: "Dive into the Sixty5 Street blog for behind-the-scenes stories, flavor experiments, new dish launches and street-food culture updates.",
|
||||
};
|
||||
|
||||
const truncateWords = (text, limit) => {
|
||||
const words = text.split(" ");
|
||||
return words.length > limit ? words.slice(0, limit).join(" ") + " ..." : text;
|
||||
};
|
||||
|
||||
const stripHtml = (html) => {
|
||||
if (!html) return "";
|
||||
return html.replace(/<[^>]*>/g, "");
|
||||
};
|
||||
|
||||
const truncateTitle = (text, limit) => {
|
||||
if (!text) return "";
|
||||
return text.length > limit ? text.slice(0, limit) + "..." : text;
|
||||
};
|
||||
|
||||
export default function Blog() {
|
||||
return (
|
||||
<Layout
|
||||
@ -31,7 +17,7 @@ export default function Blog() {
|
||||
breadcrumbTitle="Blog"
|
||||
bgImage={"/assets/images/inner-banner/blog-banner.webp"}
|
||||
>
|
||||
<div className="sidebar-page-container">
|
||||
<div className="sidebar-page-container pt-5">
|
||||
<div className="auto-container">
|
||||
<div className="sec-title mb-4 centered">
|
||||
<div className="title">Our Blogs</div>
|
||||
@ -39,47 +25,7 @@ export default function Blog() {
|
||||
<div className="separate"></div>
|
||||
</div>
|
||||
|
||||
<div className="row clearfix">
|
||||
{Blogs.sort((a, b) => new Date(b.date) - new Date(a.date)).map((blog) => (
|
||||
<div
|
||||
key={blog.id}
|
||||
className="col-lg-4 col-md-6 col-sm-12 mb-4"
|
||||
>
|
||||
<div className="blog-card rounded shadow-sm overflow-hidden">
|
||||
{/* Blog Image */}
|
||||
<div className="blog-image">
|
||||
<img
|
||||
src={blog.imageDetail}
|
||||
alt={blog.title}
|
||||
className="img-fluid w-100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Blog Title */}
|
||||
<div className="blog-content p-3">
|
||||
<h3 className="mb-2 text-lg font-semibold blog-title-hover">
|
||||
<Link href={`/blog/${blog.slug}`} className="blog-title-link">
|
||||
{truncateTitle(stripHtml(blog.title), 40)}
|
||||
</Link>
|
||||
</h3>
|
||||
|
||||
{/* Blog Excerpt */}
|
||||
<p className="text-gray-700 mb-3">
|
||||
{truncateWords(stripHtml(blog.para))}
|
||||
</p>
|
||||
|
||||
{/* Read More Button */}
|
||||
<Link
|
||||
href={`/blog/${blog.slug}`}
|
||||
className="font-medium blog-title-link"
|
||||
>
|
||||
Read More
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<BlogList blogs={Blogs} />
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
95
components/elements/BlogList.js
Normal file
95
components/elements/BlogList.js
Normal file
@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Pagination from "./Pagination";
|
||||
import "public/assets/css/Pagination.css";
|
||||
|
||||
const truncateWords = (text, limit = 20) => {
|
||||
const words = text.split(" ");
|
||||
return words.length > limit ? words.slice(0, limit).join(" ") + " ..." : text;
|
||||
};
|
||||
|
||||
const stripHtml = (html) => {
|
||||
if (!html) return "";
|
||||
return html.replace(/<[^>]*>/g, "");
|
||||
};
|
||||
|
||||
const truncateTitle = (text, limit) => {
|
||||
if (!text) return "";
|
||||
return text.length > limit ? text.slice(0, limit) + "..." : text;
|
||||
};
|
||||
|
||||
const BlogList = ({ blogs }) => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const postsPerPage = 12;
|
||||
|
||||
// Sort posts by date descending (newest first) - exactly like original code
|
||||
const sortedPosts = [...blogs].sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
|
||||
// Calculate pagination
|
||||
const totalPages = Math.ceil(sortedPosts.length / postsPerPage);
|
||||
const indexOfLastPost = currentPage * postsPerPage;
|
||||
const indexOfFirstPost = indexOfLastPost - postsPerPage;
|
||||
const currentPosts = sortedPosts.slice(indexOfFirstPost, indexOfLastPost);
|
||||
|
||||
const handlePageChange = (page) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="blog-section"> {/* Wrapper for scroll functionality */}
|
||||
<div className="row clearfix">
|
||||
{currentPosts.map((blog) => (
|
||||
<div
|
||||
key={blog.id}
|
||||
className="col-lg-4 col-md-6 col-sm-12 mb-4 pt-5"
|
||||
>
|
||||
<div className="blog-card rounded shadow-sm overflow-hidden">
|
||||
{/* Blog Image */}
|
||||
<div className="blog-image">
|
||||
<img
|
||||
src={blog.imageDetail}
|
||||
alt={blog.title}
|
||||
className="img-fluid w-100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Blog Title */}
|
||||
<div className="blog-content p-3">
|
||||
<h3 className="mb-2 text-lg font-semibold blog-title-hover">
|
||||
<Link href={`/blog/${blog.slug}`} className="blog-title-link">
|
||||
{truncateTitle(stripHtml(blog.title), 40)}
|
||||
</Link>
|
||||
</h3>
|
||||
|
||||
{/* Blog Excerpt */}
|
||||
<p className="text-gray-700 mb-3">
|
||||
{truncateWords(stripHtml(blog.para))}
|
||||
</p>
|
||||
|
||||
{/* Read More Button */}
|
||||
<Link
|
||||
href={`/blog/${blog.slug}`}
|
||||
className="font-medium blog-title-link"
|
||||
>
|
||||
Read More
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Pagination Component */}
|
||||
{totalPages > 1 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlogList;
|
||||
83
components/elements/Pagination.js
Normal file
83
components/elements/Pagination.js
Normal file
@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
|
||||
const Pagination = ({ currentPage, totalPages, onPageChange }) => {
|
||||
const handlePageClick = (e, page) => {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (page !== currentPage && page >= 1 && page <= totalPages) {
|
||||
onPageChange(page);
|
||||
// Scroll to top of blog section smoothly
|
||||
const blogSection = document.querySelector('.blog-section');
|
||||
if (blogSection) {
|
||||
const offset = 100; // Offset from top
|
||||
const elementPosition = blogSection.getBoundingClientRect().top;
|
||||
const offsetPosition = elementPosition + window.pageYOffset - offset;
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderPageNumbers = () => {
|
||||
const pages = [];
|
||||
const maxVisiblePages = 5;
|
||||
|
||||
let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
|
||||
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
|
||||
|
||||
if (endPage - startPage < maxVisiblePages - 1) {
|
||||
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
||||
}
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(
|
||||
<button
|
||||
key={i}
|
||||
type="button"
|
||||
className={`pagination-number ${currentPage === i ? 'active' : ''}`}
|
||||
onClick={(e) => handlePageClick(e, i)}
|
||||
>
|
||||
{i}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="pagination-wrapper">
|
||||
<div className="pagination-container">
|
||||
{/* Previous Button */}
|
||||
<button
|
||||
type="button"
|
||||
className="pagination-arrow pagination-arrow-prev"
|
||||
onClick={(e) => handlePageClick(e, currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
«
|
||||
</button>
|
||||
|
||||
{/* Page Numbers */}
|
||||
{renderPageNumbers()}
|
||||
|
||||
{/* Next Button */}
|
||||
<button
|
||||
type="button"
|
||||
className="pagination-arrow pagination-arrow-next"
|
||||
onClick={(e) => handlePageClick(e, currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
»
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Pagination;
|
||||
102
public/assets/css/Pagination.css
Normal file
102
public/assets/css/Pagination.css
Normal file
@ -0,0 +1,102 @@
|
||||
:root {
|
||||
--theme: #cf2d1f;
|
||||
--title: #ffffff;
|
||||
--text: #777777;
|
||||
--body-font: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 60px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-arrow {
|
||||
min-width: 45px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #e0e0e0;
|
||||
background-color: #fff;
|
||||
color: var(--theme);
|
||||
font-family: var(--body-font);
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 4px;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.pagination-arrow:hover:not(:disabled) {
|
||||
background-color: var(--theme);
|
||||
border-color: var(--theme);
|
||||
color: var(--title);
|
||||
}
|
||||
|
||||
.pagination-arrow:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pagination-number {
|
||||
min-width: 45px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #e0e0e0;
|
||||
background-color: #fff;
|
||||
color: var(--text);
|
||||
font-family: var(--body-font);
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.pagination-number:hover {
|
||||
background-color: var(--theme);
|
||||
border-color: var(--theme);
|
||||
color: var(--title);
|
||||
}
|
||||
|
||||
.pagination-number.active {
|
||||
background-color: var(--theme);
|
||||
border-color: var(--theme);
|
||||
color: var(--title);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 575px) {
|
||||
.pagination-container {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.pagination-arrow,
|
||||
.pagination-number {
|
||||
min-width: 38px;
|
||||
height: 38px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pagination-arrow {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.pagination-number {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user