pagenation updated blog page
This commit is contained in:
parent
fd451520f2
commit
a2df3a5fea
@ -2,27 +2,13 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Layout from "@/components/layout/Layout";
|
import Layout from "@/components/layout/Layout";
|
||||||
import Blogs from "@/utils/constant.utils";
|
import Blogs from "@/utils/constant.utils";
|
||||||
|
import BlogList from "@/components/elements/BlogList";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "Street-Food Stories from Sixty5 Street",
|
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.",
|
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() {
|
export default function Blog() {
|
||||||
return (
|
return (
|
||||||
<Layout
|
<Layout
|
||||||
@ -31,7 +17,7 @@ export default function Blog() {
|
|||||||
breadcrumbTitle="Blog"
|
breadcrumbTitle="Blog"
|
||||||
bgImage={"/assets/images/inner-banner/blog-banner.webp"}
|
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="auto-container">
|
||||||
<div className="sec-title mb-4 centered">
|
<div className="sec-title mb-4 centered">
|
||||||
<div className="title">Our Blogs</div>
|
<div className="title">Our Blogs</div>
|
||||||
@ -39,47 +25,7 @@ export default function Blog() {
|
|||||||
<div className="separate"></div>
|
<div className="separate"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="row clearfix">
|
<BlogList blogs={Blogs} />
|
||||||
{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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</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