feat: Refactor blog components and add pagination

This commit is contained in:
Wini_Fy
2026-02-03 17:05:09 +07:00
parent bf652a64b6
commit 29cc0bf2cd
27 changed files with 2051 additions and 429 deletions

View File

@@ -1,92 +1,62 @@
import Link from "next/link";
import type { BlogPost } from "@/types/blog";
import { getCmsImageUrl } from "@/utils";
interface NewsListProps {
blogs?: BlogPost[];
categorySlug?: string;
tagSlug?: string;
}
export default function NewsList({ blogs = [], categorySlug, tagSlug }: NewsListProps) {
// Use blogs from props (already filtered by API)
const posts = blogs;
// Additional client-side filtering if needed (though API should handle this)
if (categorySlug || tagSlug) {
// If filters are provided but blogs are not pre-filtered, filter here
// This is a fallback - ideally API should handle filtering
}
export default function NewsList() {
return (
<div className="col-lg-8 col-12">
{/* News Post 1 */}
<div className="news-standard-post">
<div className="news-image">
<img src="/assets/img/home-1/news/news-13.jpg" alt="img" />
{posts.map((post, index) => (
<div
key={post.slug}
className={`news-standard-post ${index === posts.length - 1 ? "mb-0" : ""}`}
>
<div className="news-image">
<img
src={
post.featuredImage
? getCmsImageUrl(post.featuredImage)
: "/assets/img/inner-page/news-details/details-1.jpg"
}
alt={post.title}
/>
</div>
<div className="news-content">
<ul className="news-list">
<li>
<i className="fa-solid fa-user"></i> By {post.author}
</li>
<li>
<i className="fa-solid fa-calendar-days"></i> {post.publishedAt}
</li>
<li>
<i className="fa-solid fa-comments"></i> {post.commentsCount} Comments
</li>
</ul>
<h3>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</h3>
<p>{post.excerpt}</p>
<Link href={`/blog/${post.slug}`} className="theme-btn">
VIEW MORE <i className="fa-solid fa-arrow-right"></i>
</Link>
</div>
</div>
<div className="news-content">
<ul className="news-list">
<li>
<i className="fa-solid fa-user"></i> By Admin
</li>
<li>
<i className="fa-solid fa-calendar-days"></i> 11 March 2025
</li>
<li>
<i className="fa-solid fa-comments"></i> 0 Comments
</li>
</ul>
<h3>
<Link href="/news-details">How to Avoid Common Mistakes in Visa Applications</Link>
</h3>
<p>
A business consultant provides expert guidance, strategic planning, and problem-solving supporthelping startups avoid mistakes, grow faster, and operate more efficiently from day one.
</p>
<Link href="/blog/news-details" className="theme-btn">
VIEW MORE <i className="fa-solid fa-arrow-right"></i>
</Link>
</div>
</div>
{/* News Post 2 */}
<div className="news-standard-post">
<div className="news-image">
<img src="/assets/img/home-1/news/news-14.jpg" alt="img" />
</div>
<div className="news-content">
<ul className="news-list">
<li>
<i className="fa-solid fa-user"></i> By Admin
</li>
<li>
<i className="fa-solid fa-calendar-days"></i> 11 March 2025
</li>
<li>
<i className="fa-solid fa-comments"></i> 0 Comments
</li>
</ul>
<h3>
<Link href="/news-details">The Role of Immigration Consultants in Your Journey</Link>
</h3>
<p>
Immigration consultants play a vital role in guiding applicants, simplifying complex processes, offering expert advice, and ensuring successful outcomes for study, work, or permanent residency abroad.
</p>
<Link href="/blog/news-details" className="theme-btn">
VIEW MORE <i className="fa-solid fa-arrow-right"></i>
</Link>
</div>
</div>
{/* News Post 3 */}
<div className="news-standard-post mb-0">
<div className="news-image">
<img src="/assets/img/home-1/news/news-15.jpg" alt="img" />
</div>
<div className="news-content">
<ul className="news-list">
<li>
<i className="fa-solid fa-user"></i> By Admin
</li>
<li>
<i className="fa-solid fa-calendar-days"></i> 11 March 2025
</li>
<li>
<i className="fa-solid fa-comments"></i> 0 Comments
</li>
</ul>
<h3>
<Link href="/news-details">Latest Immigration Policy Updates You Should Know</Link>
</h3>
<p>
Stay informed with the latest immigration policy updates, ensuring you understand new rules, visa requirements, and opportunities that impact your study, work, or migration journey abroad.
</p>
<Link href="/blog/news-details" className="theme-btn">
VIEW MORE <i className="fa-solid fa-arrow-right"></i>
</Link>
</div>
</div>
))}
</div>
);
}

View File

@@ -1,15 +1,38 @@
import NewsList from "./NewsList";
import Sidebar from "./Sidebar";
import Pagination from "./Pagination";
import type { BlogPost, BlogPagination } from "@/types";
export default function NewsSection() {
interface NewsSectionProps {
blogs?: BlogPost[];
categorySlug?: string;
tagSlug?: string;
searchQuery?: string;
pagination?: BlogPagination;
}
export default function NewsSection({
blogs,
categorySlug,
tagSlug,
searchQuery,
pagination,
}: NewsSectionProps) {
return (
<section className="news-standard-section section-padding fix">
<div className="container">
<div className="news-standard-wrapper">
<div className="row g-4">
<NewsList />
<Sidebar />
<NewsList blogs={blogs} categorySlug={categorySlug} tagSlug={tagSlug} />
<Sidebar searchQuery={searchQuery} />
</div>
{pagination && pagination.total > 1 && (
<div className="row g-4 mt-4">
<div className="col-12">
<Pagination basePath="/blog" pagination={pagination} searchQuery={searchQuery} />
</div>
</div>
)}
</div>
</div>
</section>

View File

@@ -0,0 +1,79 @@
import Link from "next/link";
import type { BlogPagination } from "@/types";
interface PaginationProps {
basePath: string;
pagination: BlogPagination;
searchQuery?: string;
}
export default function Pagination({ basePath, pagination, searchQuery }: PaginationProps) {
const { current, total } = pagination;
if (total <= 1) return null;
const makeHref = (page: number) => {
const params = new URLSearchParams();
if (page > 1) params.set("page", page.toString());
if (searchQuery) params.set("search", searchQuery);
const qs = params.toString();
return qs ? `${basePath}?${qs}` : basePath;
};
const pages: number[] = [];
for (let i = 1; i <= total; i++) {
if (i === 1 || i === total || (i >= current - 2 && i <= current + 2)) {
pages.push(i);
}
}
const items: (number | "...")[] = [];
for (let i = 0; i < pages.length; i++) {
const page = pages[i];
items.push(page);
if (i < pages.length - 1 && pages[i + 1] !== page + 1) {
items.push("...");
}
}
return (
<nav aria-label="Blog pagination" className="mt-4">
<ul className="pagination justify-content-center">
{current > 1 && (
<li className="page-item">
<Link className="page-link" href={makeHref(current - 1)}>
Previous
</Link>
</li>
)}
{items.map((item, idx) =>
item === "..." ? (
<li key={`ellipsis-${idx}`} className="page-item disabled">
<span className="page-link">...</span>
</li>
) : (
<li key={item} className={`page-item ${item === current ? "active" : ""}`}>
{item === current ? (
<span className="page-link">{item}</span>
) : (
<Link className="page-link" href={makeHref(item as number)}>
{item}
</Link>
)}
</li>
),
)}
{current < total && (
<li className="page-item">
<Link className="page-link" href={makeHref(current + 1)}>
Next
</Link>
</li>
)}
</ul>
</nav>
);
}

View File

@@ -1,14 +1,35 @@
import Link from "next/link";
import { fetchCategories, fetchRecentBlogs, fetchPopularTags } from "@/api/blog";
interface SidebarProps {
searchQuery?: string;
}
export default async function Sidebar({ searchQuery }: SidebarProps) {
// Fetch data from API
const [categoriesResponse, recentBlogsResponse, tagsResponse] = await Promise.all([
fetchCategories(),
fetchRecentBlogs(5),
fetchPopularTags(10),
]);
const categories = categoriesResponse.data;
const recentPosts = recentBlogsResponse.data;
const tags = tagsResponse.data;
export default function Sidebar() {
return (
<div className="col-lg-4 col-12">
<div className="main-sideber">
{/* Search Widget */}
<div className="news-sideber-box">
<div className="search-widget">
<form action="#">
<input type="text" placeholder="Search Blog" />
<form action="/blog" method="GET">
<input
type="text"
name="search"
placeholder="Search Blog"
defaultValue={searchQuery || ""}
/>
<button type="submit">
<i className="fa-solid fa-magnifying-glass"></i>
</button>
@@ -22,11 +43,17 @@ export default function Sidebar() {
</div>
<div className="news-widget-categories">
<ul>
<li><Link href="/news-details"><i className="fa-solid fa-chevrons-right"></i> Permanent Residency (PR)</Link><span>(04)</span></li>
<li><Link href="/news-details"><i className="fa-solid fa-chevrons-right"></i> Immigration Policy Updates</Link><span>(09)</span></li>
<li><Link href="/news-details"><i className="fa-solid fa-chevrons-right"></i> Scholarships & Grants</Link><span>(00)</span></li>
<li><Link href="/news-details"><i className="fa-solid fa-chevrons-right"></i> Citizenship & Naturalization</Link><span>(04)</span></li>
<li><Link href="/news-details"><i className="fa-solid fa-chevrons-right"></i> Visa Interview Preparation</Link><span>(01)</span></li>
{categories.map((category) => {
const postCount = category.postCount || 0;
return (
<li key={category.slug}>
<Link href={`/blog/category/${category.slug}`}>
<i className="fa-solid fa-chevrons-right"></i> {category.name}
</Link>
<span>({String(postCount).padStart(2, "0")})</span>
</li>
);
})}
</ul>
</div>
</div>
@@ -36,45 +63,27 @@ export default function Sidebar() {
<h3>Recent Post</h3>
</div>
<div className="recent-post-area">
<div className="recent-items">
<div className="recent-thumb">
<img src="/assets/img/inner-page/news-details/post-1.jpg" alt="img" />
{recentPosts.map((post) => (
<div key={post.slug} className="recent-items">
<div className="recent-thumb">
<img
src={post.thumbnail || "/assets/img/inner-page/news-details/details-1.jpg"}
alt={post.title}
width={88}
height={80}
style={{ objectFit: 'cover' }}
/>
</div>
<div className="recent-content">
<h6>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</h6>
<ul>
<li>{post.publishedAt}</li>
</ul>
</div>
</div>
<div className="recent-content">
<h6>
<Link href="/news-details">Top Countries for Higher Education in 2025</Link>
</h6>
<ul>
<li>March 26, 2025</li>
</ul>
</div>
</div>
<div className="recent-items">
<div className="recent-thumb">
<img src="/assets/img/inner-page/news-details/post-2.jpg" alt="img" />
</div>
<div className="recent-content">
<h6>
<Link href="/news-details">The Benefits of Hiring a Visa Consultant</Link>
</h6>
<ul>
<li>March 26, 2025</li>
</ul>
</div>
</div>
<div className="recent-items">
<div className="recent-thumb">
<img src="/assets/img/inner-page/news-details/post-3.jpg" alt="img" />
</div>
<div className="recent-content">
<h6>
<Link href="/news-details">How to Prepare for Your Immigration Interview</Link>
</h6>
<ul>
<li>March 26, 2025</li>
</ul>
</div>
</div>
))}
</div>
</div>
{/* Tag Cloud */}
@@ -84,12 +93,11 @@ export default function Sidebar() {
</div>
<div className="news-widget-categories">
<div className="tagcloud">
<Link href="/news-details">WorkVisa</Link>
<Link href="/news-details">FamilyVisa</Link>
<Link href="/news-details">StudentVisa</Link>
<Link href="/news-details">VisaUpdates</Link>
<Link href="/news-details">TravelVisa</Link>
<Link href="/news-details">StudyAbroad</Link>
{tags.map((tag) => (
<Link key={tag.slug} href={`/blog/tag/${tag.slug}`}>
{tag.name}
</Link>
))}
</div>
</div>
</div>