This commit is contained in:
Iliyan Angelov
2025-10-08 13:46:46 +03:00
parent d48c54e2c5
commit 18ae8b9f88
94 changed files with 8882 additions and 1682 deletions

View File

@@ -1,13 +1,73 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import PostFilterItems from "./post-filter/PostFilterItems";
const BlogItems = () => {
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const postsPerPage = 6;
const handlePageChange = (page: number) => {
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
// Scroll to top of posts section
window.scrollTo({ top: 0, behavior: 'smooth' });
}
};
const handleTotalPagesChange = (total: number) => {
setTotalPages(total);
};
// Generate page numbers to display
const getPageNumbers = () => {
const pages: (number | string)[] = [];
const maxPagesToShow = 5;
if (totalPages <= maxPagesToShow) {
// Show all pages if total is small
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
// Show first page
pages.push(1);
// Calculate range around current page
let startPage = Math.max(2, currentPage - 1);
let endPage = Math.min(totalPages - 1, currentPage + 1);
// Add ellipsis after first page if needed
if (startPage > 2) {
pages.push('...');
}
// Add pages around current page
for (let i = startPage; i <= endPage; i++) {
pages.push(i);
}
// Add ellipsis before last page if needed
if (endPage < totalPages - 1) {
pages.push('...');
}
// Show last page
if (totalPages > 1) {
pages.push(totalPages);
}
}
return pages;
};
return (
<section className="fix-top pb-120 blog-m">
<div className="container">
<div className="row align-items-center vertical-column-gap">
<div className="col-12 col-lg-7">
<h2 className="mt-8 fw-7 text-secondary title-anim">Blog</h2>
<h2 className="mt-8 fw-7 text-secondary title-anim">Latest Company Insights</h2>
</div>
<div className="col-12 col-lg-5">
<form action="#" method="post" autoComplete="off">
@@ -30,36 +90,58 @@ const BlogItems = () => {
</form>
</div>
</div>
<PostFilterItems />
<div className="row mt-60">
<div className="col-12">
<div className="section__cta">
<ul className="pagination">
<li>
<button>
<i className="fa-solid fa-angle-left"></i>
</button>
</li>
<li>
<Link href="blog">1</Link>
</li>
<li>
<Link href="blog" className="active">
2
</Link>
</li>
<li>
<Link href="blog">3</Link>
</li>
<li>
<button>
<i className="fa-solid fa-angle-right"></i>
</button>
</li>
</ul>
<PostFilterItems
currentPage={currentPage}
onPageChange={handlePageChange}
onTotalPagesChange={handleTotalPagesChange}
postsPerPage={postsPerPage}
/>
{totalPages > 1 && (
<div className="row mt-60">
<div className="col-12">
<div className="section__cta">
<ul className="pagination">
<li>
<button
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
aria-label="Previous page"
style={{ opacity: currentPage === 1 ? 0.5 : 1, cursor: currentPage === 1 ? 'not-allowed' : 'pointer' }}
>
<i className="fa-solid fa-angle-left"></i>
</button>
</li>
{getPageNumbers().map((page, index) => (
<li key={index}>
{typeof page === 'number' ? (
<button
onClick={() => handlePageChange(page)}
className={currentPage === page ? 'active' : ''}
aria-label={`Go to page ${page}`}
aria-current={currentPage === page ? 'page' : undefined}
>
{page}
</button>
) : (
<span style={{ padding: '0 10px' }}>{page}</span>
)}
</li>
))}
<li>
<button
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
aria-label="Next page"
style={{ opacity: currentPage === totalPages ? 0.5 : 1, cursor: currentPage === totalPages ? 'not-allowed' : 'pointer' }}
>
<i className="fa-solid fa-angle-right"></i>
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
)}
</div>
</section>
);

View File

@@ -1,207 +1,289 @@
"use client";
import { useParams } from "next/navigation";
import Image from "next/legacy/image";
import Link from "next/link";
import poster from "@/public/images/blog/blog-poster.png";
import info from "@/public/images/blog/blog-info.png";
import { useBlogPost } from "@/lib/hooks/useBlog";
import { getValidImageUrl, getValidImageAlt, FALLBACK_IMAGES } from "@/lib/imageUtils";
const BlogSingle = () => {
return (
<section className="tp-post-details fix-top pb-120 fade-wrapper">
<div className="container">
<div className="row">
<div className="col-12">
<div className="tp-post-intro">
<h2 className="title-anim text-xxl fw-7 text-secondary mt-8">
Tackling data of annotation problems in healthcare
</h2>
<div className="mt-60 mb-24 d-flex align-items-center justify-content-between tppr">
<div className="d-flex align-items-center tp-post-tags-container mt-8">
<p className="text-xs">Scope:</p>
<div className="d-flex align-items-center tp-post-tags">
<Link href="blog">AI</Link>
<span></span>
<Link href="blog">Artificial Intelligence</Link>
<span></span>
<Link href="blog">Data Science</Link>
<span></span>
<Link href="blog">Machine Learning</Link>
</div>
const params = useParams();
const slug = params?.slug as string;
const { post, loading, error } = useBlogPost(slug);
if (loading) {
return (
<section className="blog-single-section fix-top pb-120">
<div className="container">
<div className="row justify-content-center">
<div className="col-12 col-lg-10 col-xl-8">
<div className="loading-state text-center py-5">
<div className="spinner-border text-primary mb-3" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div className="tp-post-meta mt-8">
<p className="author text-xs text-tertiary">Denial Lio</p>
<span></span>
<p className="date text-xs text-tertiary">18 Dec 2022</p>
</div>
</div>
<div className="tp-post-poster fade-top">
<div className="parallax-image-wrap">
<div className="parallax-image-inner">
<Image
src={poster}
className="w-100 mh-300 parallax-image"
alt="Image"
/>
</div>
</div>
</div>
<div className="group mt-60">
<p className="cur-lg mb-24">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
nec tortor id erat faucibus tempor id eget turpis. Donec
lobortis et neque eget congue. Mauris laoreet orci ac dictum
interdum. Sed dapibus convallis arcu, a aliquam purus sodales
nec. Integer consequat et magna sit amet porta. Maecenas
consectetur eros sed risus porta convallis eget et massa.
Integer auctor convallis ligula, sit amet sollicitudin justo
tincidunt a. Sed tellus diam.
</p>
<p className="cur-lg mb-24 fw-6">
Bibendum tincidunt orci vel, sollicitudin bibendum ligula.
Pellentesque sollicitudin nulla felis, a ornare tellus
tristique ac. Proin ultricies a turpis sit amet lacinia. Ut
laoreet nunc leo, ac congue enim laoreet in. Aenean suscipit
arcu at ligula tempor porta.
</p>
<p className="cur-lg">
Quisque et fringilla lacus, quis luctus elit. Curabitur eu dui
mattis turpis commodo eleifend. Sed porta ornare nunc et
tristique. Curabitur vel eros a ante cursus lacinia. Nam nisl
leo, aliquet a placerat at, porttitor quis augue. Proin quis
aliquet libero. Pellentesque habitant morbi tristique senectus
et netus et malesuada fames ac turpis egestas. Vestibulum
varius a ipsum ornare blandit. Integer vitae eleifend risus,
id tincidunt elit. Integer tincidunt ipsum vitae sagittis
porta. Aenean ut facilisis dui. Praesent at ultricies purus.
Nam a arcu vel diam ullamcorper tincidunt. Curabitur
vestibulum commodo erat non laoreet. Proin nibh nibh,
scelerisque a nibh nec, scelerisque convallis leo. Nunc eget
elit nunc.
</p>
</div>
<div className="group-info mt-60">
<div className="row align-items-center vertical-column-gap">
<div className="col-12 col-lg-6">
<div className="fade-top">
<div className="parallax-image-wrap">
<div className="parallax-image-inner">
<Image
src={info}
className="w-100 mh-300 parallax-image"
alt="Image"
/>
</div>
</div>
</div>
</div>
<div className="col-12 col-lg-6">
<p className="cur-lg mb-24">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Donec nec tortor id erat faucibus tempor id eget turpis.
Donec lobortis et neque eget congue. Mauris laoreet orci
ac dictum interdum. Sed dapibus convallis arcu, a aliquam
purus sodales nec.
</p>
<p className="cur-lg">
Quisque et fringilla lacus, quis luctus elit. Curabitur eu
dui mattis turpis commodo eleifend. Sed porta ornare nunc
et tristique. Curabitur vel eros a ante cursus lacinia.
Nam nisl leo, aliquet a placerat at, porttitor quis augue.
Proin quis aliquet libero. Pellentesque habitant morbi
tristique senectus et netus et malesuada fames ac turpis
egestas. Vestibulum varius a ipsum ornare blandit.
</p>
</div>
</div>
</div>
<div className="group mt-60">
<h4 className="mt-8 fw-7 text-secondary title-anim mb-24">
The Hidden Markov Model&apos;s Limitations
</h4>
<p className="cur-lg mb-24">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
nec tortor id erat faucibus tempor id eget turpis. Donec
lobortis et neque eget congue. Mauris laoreet orci ac dictum
interdum. Sed dapibus convallis arcu, a aliquam purus sodales
nec.
</p>
<p className="cur-lg">
Quisque et fringilla lacus, quis luctus elit. Curabitur eu dui
mattis turpis commodo eleifend. Sed porta ornare nunc et
tristique. Curabitur vel eros a ante cursus lacinia. Nam nisl
leo, aliquet a placerat at, porttitor quis augue. Proin quis
aliquet libero. Pellentesque habitant morbi tristique senectus
et netus et malesuada fames ac turpis egestas. Vestibulum
varius a ipsum ornare blandit.
</p>
</div>
<div className="group mt-60">
<h4 className="mt-8 fw-7 text-secondary title-anim mb-24">
The Effect
</h4>
<p className="cur-lg mb-24">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec
nec tortor id erat faucibus tempor id eget turpis. Donec
lobortis et neque eget congue. Mauris laoreet orci ac dictum
interdum. Sed dapibus convallis arcu, a aliquam purus sodales
nec.
</p>
<p className="cur-lg">
Quisque et fringilla lacus, quis luctus elit. Curabitur eu dui
mattis turpis commodo eleifend. Sed porta ornare nunc et
tristique. Curabitur vel eros a ante cursus lacinia. Nam nisl
leo, aliquet a placerat at, porttitor quis augue. Proin quis
aliquet libero. Pellentesque habitant morbi tristique senectus
et netus et malesuada fames ac turpis egestas. Vestibulum
varius a ipsum ornare blandit.
</p>
<p className="text-tertiary">Loading insight...</p>
</div>
</div>
</div>
</div>
<div className="row mt-80">
<div className="col-12">
<div className="bd-social">
<p className="fw-5 text-uppercase">Share :</p>
<ul className=" social">
<li>
<Link
href="https://www.facebook.com/"
target="_blank"
aria-label="share us on facebook"
>
<i className="fa-brands fa-facebook-f"></i>
</Link>
</li>
<li>
<Link
href="https://www.twitter.com/"
target="_blank"
aria-label="share us on twitter"
>
<i className="fa-brands fa-twitter"></i>
</Link>
</li>
<li>
<Link
href="https://www.pinterest.com/"
target="_blank"
aria-label="share us on pinterest"
>
<i className="fa-brands fa-linkedin-in"></i>
</Link>
</li>
<li>
<Link
href="https://www.instagram.com/"
target="_blank"
aria-label="share us on instagram"
>
<i className="fa-brands fa-instagram"></i>
</Link>
</li>
</ul>
</section>
);
}
if (error || !post) {
return (
<section className="blog-single-section fix-top pb-120">
<div className="container">
<div className="row justify-content-center">
<div className="col-12 col-lg-10 col-xl-8">
<div className="error-state text-center py-5">
<div className="error-icon mb-4">
<i className="fa-solid fa-exclamation-circle fa-4x text-tertiary"></i>
</div>
<h2 className="text-secondary mb-3">Insight Not Found</h2>
<p className="text-tertiary mb-4">
The insight you're looking for doesn't exist or has been removed.
</p>
<Link href="/insights" className="btn btn-primary">
<i className="fa-solid fa-arrow-left me-2"></i>
Back to Insights
</Link>
</div>
</div>
</div>
</div>
</section>
);
}
return (
<section className="blog-single-section fix-top pb-120">
<div className="container">
{/* Article Content */}
<div className="row">
<div className="col-12">
<article className="blog-single-article">
{/* Article Header */}
<header className="article-header">
{/* Top Meta Bar */}
<div className="article-top-meta d-flex flex-wrap align-items-center justify-content-between mb-4">
<div className="left-meta d-flex align-items-center gap-3">
{/* Category Badge */}
{post.category && (
<Link href={`/insights?category=${post.category.slug}`} className="category-badge">
<i className="fa-solid fa-folder-open me-2"></i>
{post.category.title}
</Link>
)}
{/* Date */}
<div className="meta-item d-flex align-items-center">
<i className="fa-solid fa-calendar me-2"></i>
<span>
{new Date(post.published_at || post.created_at).toLocaleDateString('en-US', {
day: 'numeric',
month: 'short',
year: 'numeric'
})}
</span>
</div>
</div>
<div className="right-meta d-flex align-items-center gap-3">
{/* Reading Time */}
<div className="meta-item d-flex align-items-center">
<i className="fa-solid fa-clock me-2"></i>
<span>{post.reading_time} min</span>
</div>
{/* Views */}
<div className="meta-item d-flex align-items-center">
<i className="fa-solid fa-eye me-2"></i>
<span>{post.views_count}</span>
</div>
</div>
</div>
{/* Title */}
<h1 className="article-title">
{post.title}
</h1>
{/* Author and Tags Bar */}
<div className="article-bottom-meta d-flex flex-wrap align-items-center justify-content-between mt-4 pt-4">
{/* Author */}
<div className="author-meta d-flex align-items-center">
<div className="author-avatar me-3">
{post.author?.avatar ? (
<Image
src={post.author.avatar}
alt={post.author.name}
width={48}
height={48}
className="rounded-circle"
/>
) : (
<div className="avatar-placeholder">
<i className="fa-solid fa-user"></i>
</div>
)}
</div>
<div className="author-info">
<div className="author-label">Written by</div>
<div className="author-name">
{post.author?.name || post.author_name || 'Admin'}
</div>
</div>
</div>
{/* Tags */}
{post.tags && post.tags.length > 0 && (
<div className="article-tags d-flex flex-wrap align-items-center gap-2">
{post.tags.map((tag) => (
<Link
key={tag.id}
href={`/insights?tag=${tag.slug}`}
className="tag-badge"
>
#{tag.name}
</Link>
))}
</div>
)}
</div>
</header>
{/* Featured Image */}
{(post.featured_image || post.thumbnail) && (
<div className="article-featured-image">
<div className="image-wrapper">
<Image
src={getValidImageUrl(
post.featured_image || post.thumbnail,
FALLBACK_IMAGES.BLOG
)}
alt={getValidImageAlt(post.title)}
width={1200}
height={600}
layout="responsive"
className="featured-image"
/>
</div>
</div>
)}
{/* Article Body */}
<div className="article-body">
{/* Excerpt */}
{post.excerpt && (
<div className="article-excerpt">
<p className="lead">{post.excerpt}</p>
</div>
)}
{/* Content */}
{post.content && (
<div
className="article-content enterprise-content"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
)}
</div>
{/* Footer Section */}
<footer className="article-footer">
{/* Share Section */}
<div className="article-share">
<div className="share-container">
<h6 className="share-title">
Share this insight
</h6>
<div className="social-share">
<Link
href={`https://www.linkedin.com/shareArticle?mini=true&url=${typeof window !== 'undefined' ? encodeURIComponent(window.location.href) : ''}&title=${encodeURIComponent(post.title)}`}
target="_blank"
rel="noopener noreferrer"
className="share-btn share-linkedin"
aria-label="Share on LinkedIn"
>
<i className="fa-brands fa-linkedin-in"></i>
<span>LinkedIn</span>
</Link>
<Link
href={`https://twitter.com/intent/tweet?url=${typeof window !== 'undefined' ? encodeURIComponent(window.location.href) : ''}&text=${encodeURIComponent(post.title)}`}
target="_blank"
rel="noopener noreferrer"
className="share-btn share-twitter"
aria-label="Share on Twitter"
>
<i className="fa-brands fa-twitter"></i>
<span>Twitter</span>
</Link>
<Link
href={`https://www.facebook.com/sharer/sharer.php?u=${typeof window !== 'undefined' ? encodeURIComponent(window.location.href) : ''}`}
target="_blank"
rel="noopener noreferrer"
className="share-btn share-facebook"
aria-label="Share on Facebook"
>
<i className="fa-brands fa-facebook-f"></i>
<span>Facebook</span>
</Link>
<button
onClick={() => {
if (typeof window !== 'undefined' && navigator.clipboard) {
navigator.clipboard.writeText(window.location.href);
alert('Link copied to clipboard!');
}
}}
className="share-btn share-copy"
aria-label="Copy link"
>
<i className="fa-solid fa-link"></i>
<span>Copy</span>
</button>
</div>
</div>
</div>
{/* Author Bio */}
{post.author && post.author.bio && (
<div className="author-bio-section">
<div className="author-bio-card">
<div className="author-avatar-container">
{post.author.avatar ? (
<Image
src={post.author.avatar}
alt={post.author.name}
width={90}
height={90}
className="rounded-circle"
/>
) : (
<div className="author-avatar-large">
<i className="fa-solid fa-user"></i>
</div>
)}
</div>
<div className="author-bio-content">
<div className="bio-label">About the Author</div>
<h6 className="author-name">{post.author.name}</h6>
<p className="author-bio-text">{post.author.bio}</p>
</div>
</div>
</div>
)}
{/* Navigation */}
<div className="article-navigation">
<Link href="/insights" className="btn-back-insights">
<i className="fa-solid fa-arrow-left me-2"></i>
Back to All Insights
</Link>
</div>
</footer>
</article>
</div>
</div>
</div>
</section>
);

View File

@@ -4,12 +4,30 @@ import Link from "next/link";
import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay } from "swiper/modules";
import "swiper/swiper-bundle.css";
import one from "@/public/images/blog/related-one.png";
import two from "@/public/images/blog/related-two.png";
import three from "@/public/images/blog/related-three.png";
import four from "@/public/images/blog/related-four.png";
import { useLatestPosts } from "@/lib/hooks/useBlog";
import { getValidImageUrl, getValidImageAlt, FALLBACK_IMAGES } from "@/lib/imageUtils";
const LatestPost = () => {
const { posts, loading, error } = useLatestPosts(8);
if (loading) {
return (
<section className="tp-latest-post pt-120 pb-120 bg-quinary">
<div className="container">
<div className="row">
<div className="col-12">
<p className="text-center">Loading latest posts...</p>
</div>
</div>
</div>
</section>
);
}
if (error || posts.length === 0) {
return null; // Don't show the section if there's an error or no posts
}
return (
<section className="tp-latest-post pt-120 pb-120 bg-quinary">
<div className="container">
@@ -17,14 +35,14 @@ const LatestPost = () => {
<div className="col-12 col-lg-7">
<div className="tp-lp-title text-center text-lg-start">
<h2 className="mt-8 fw-7 text-secondary title-anim">
Related posts
Related Insights
</h2>
</div>
</div>
<div className="col-12 col-lg-5">
<div className="tp-lp-cta text-center text-lg-end">
<Link href="blog" className="btn-line text-uppercase">
See All Posts
<Link href="/insights" className="btn-line text-uppercase">
View All Insights
</Link>
</div>
</div>
@@ -39,7 +57,7 @@ const LatestPost = () => {
slidesPerGroup={1}
freeMode={true}
speed={1200}
loop={true}
loop={posts.length > 3}
roundLengths={true}
modules={[Autoplay]}
autoplay={{
@@ -49,378 +67,43 @@ const LatestPost = () => {
}}
className="tp-lp-slider"
>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={one}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
{posts.map((post) => (
<SwiperSlide key={post.id}>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href={`/insights/${post.slug}`} className="w-100 overflow-hidden">
<Image
src={getValidImageUrl(post.thumbnail, FALLBACK_IMAGES.BLOG)}
width={400}
height={220}
className="w-100 mh-220"
alt={getValidImageAlt(post.title)}
/>
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={two}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={three}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
{post.author_name || 'Admin'}
</p>
<span></span>
<p className="date text-xs text-tertiary">
{new Date(post.published_at || post.created_at).toLocaleDateString('en-US', {
day: 'numeric',
month: 'short',
year: 'numeric'
})}
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href={`/insights/${post.slug}`}>
{post.title}
</Link>
</h5>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={four}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={one}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={two}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={three}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={four}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={one}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={two}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={three}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
<SwiperSlide>
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link href="blog-single" className="w-100 overflow-hidden">
<Image
src={four}
width={400}
height={220}
className="w-100 mh-220"
alt="Image"
/>
</Link>
</div>
<div className="content">
<div className="tp-lp-post__meta mb-24 mt-8">
<p className="author text-xs text-tertiary">
Denial Lio
</p>
<span></span>
<p className="date text-xs text-tertiary">
18 Dec 2022
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href="blog-single">
Tackling data of annotation problems in healthcare
</Link>
</h5>
</div>
</div>
</SwiperSlide>
</SwiperSlide>
))}
</Swiper>
</div>
</div>

View File

@@ -1,23 +1,54 @@
import { useState, useEffect } from "react";
import { BlogCategoryButtons } from "@/public/data/blog-category";
import { useBlogCategories } from "@/lib/hooks/useBlog";
const PostFilterButtons = ({ handleClick, active }: any) => {
const [categories, setCategories] = useState(BlogCategoryButtons);
const { categories: apiCategories, loading, error } = useBlogCategories();
const [categories, setCategories] = useState<any[]>([]);
// TODO: Replace with API call to get blog categories
// useEffect(() => {
// const fetchCategories = async () => {
// try {
// const response = await blogCategoryAPI.getAll();
// if (response.success) {
// setCategories(response.data);
// }
// } catch (error) {
// console.error('Error fetching categories:', error);
// }
// };
// fetchCategories();
// }, []);
useEffect(() => {
if (!loading && apiCategories.length > 0) {
// Add "All" category at the beginning
const allCategory = {
id: 0,
title: "All",
slug: "all",
display_order: 0
};
setCategories([allCategory, ...apiCategories]);
}
}, [apiCategories, loading]);
if (loading) {
return (
<div className="row">
<div className="col-12">
<div className="post-filter__wrapper mt-80">
<p>Loading categories...</p>
</div>
</div>
</div>
);
}
if (error) {
console.error('Error loading categories:', error);
// Fallback to showing "All" button only
return (
<div className="row">
<div className="col-12">
<div className="post-filter__wrapper mt-80">
<button
aria-label="Filter Post"
className="active"
onClick={() => handleClick("all")}
>
All
</button>
</div>
</div>
</div>
);
}
return (
<div className="row">

View File

@@ -5,102 +5,81 @@ import Link from "next/link";
import { AnimatePresence, motion } from "framer-motion";
import { getValidImageUrl, getValidImageAlt, FALLBACK_IMAGES } from "@/lib/imageUtils";
import PostFilterButtons from "./PostFilterButtons";
import { useBlogPosts } from "@/lib/hooks/useBlog";
const PostFilterItems = () => {
interface PostFilterItemsProps {
currentPage: number;
onPageChange: (page: number) => void;
onTotalPagesChange: (totalPages: number) => void;
postsPerPage?: number;
}
const PostFilterItems = ({ currentPage, onPageChange, onTotalPagesChange, postsPerPage = 10 }: PostFilterItemsProps) => {
const [active, setActive] = useState("all");
// Static blog posts data
const allPosts = [
{
id: 1,
title: "Enterprise Software Development Best Practices",
content: "Learn about the latest best practices in enterprise software development...",
slug: "enterprise-software-development-best-practices",
author_id: 1,
category_id: 1,
thumbnail: "/images/blog/one.png",
published: true,
category_title: "Development",
category_slug: "development",
author_name: "John Smith",
created_at: "2024-01-15T10:00:00Z",
updated_at: "2024-01-15T10:00:00Z"
},
{
id: 2,
title: "API Integration Strategies for Modern Enterprises",
content: "Discover effective strategies for API integration in enterprise environments...",
slug: "api-integration-strategies-modern-enterprises",
author_id: 1,
category_id: 2,
thumbnail: "/images/blog/two.png",
published: true,
category_title: "Integration",
category_slug: "integration",
author_name: "Jane Doe",
created_at: "2024-01-10T14:30:00Z",
updated_at: "2024-01-10T14:30:00Z"
},
{
id: 3,
title: "Cloud Migration: A Complete Guide",
content: "Everything you need to know about migrating your enterprise to the cloud...",
slug: "cloud-migration-complete-guide",
author_id: 1,
category_id: 3,
thumbnail: "/images/blog/three.png",
published: true,
category_title: "Cloud",
category_slug: "cloud",
author_name: "Mike Johnson",
created_at: "2024-01-05T09:15:00Z",
updated_at: "2024-01-05T09:15:00Z"
},
{
id: 4,
title: "Digital Transformation in Enterprise",
content: "How digital transformation is reshaping enterprise operations...",
slug: "digital-transformation-enterprise",
author_id: 1,
category_id: 1,
thumbnail: "/images/blog/four.png",
published: true,
category_title: "Development",
category_slug: "development",
author_name: "Sarah Wilson",
created_at: "2024-01-01T16:45:00Z",
updated_at: "2024-01-01T16:45:00Z"
const { posts: allPosts, loading, error, totalCount } = useBlogPosts({
category: active === "all" ? undefined : active,
page: currentPage,
page_size: postsPerPage
});
const [displayData, setDisplayData] = useState<any[]>([]);
useEffect(() => {
if (!loading && allPosts.length > 0) {
setDisplayData(allPosts);
}
];
const [displayData, setDisplayData] = useState(allPosts);
}, [allPosts, loading]);
// Calculate and update total pages when totalCount changes
useEffect(() => {
if (totalCount !== undefined && totalCount !== null) {
const calculatedTotalPages = Math.ceil(totalCount / postsPerPage);
onTotalPagesChange(calculatedTotalPages);
}
}, [totalCount, postsPerPage, onTotalPagesChange]);
const handleCategoryClick = (category: SetStateAction<string>) => {
if (category === active) return;
setActive(category);
setDisplayData([]);
onPageChange(1); // Reset to page 1 when category changes
if (category === "all") {
setDisplayData(allPosts);
return;
}
const filteredData = allPosts.filter(
(item) => item.category_slug === category
);
setTimeout(() => {
setDisplayData(filteredData);
}, 600);
// The API call will be triggered by the change in active state
// which will update allPosts and trigger the useEffect above
};
if (loading && displayData.length === 0) {
return (
<>
<PostFilterButtons active={active} handleClick={handleCategoryClick} />
<div className="row mt-60">
<div className="col-12">
<p className="text-center">Loading posts...</p>
</div>
</div>
</>
);
}
if (error) {
console.error('Error loading posts:', error);
return (
<>
<PostFilterButtons active={active} handleClick={handleCategoryClick} />
<div className="row mt-60">
<div className="col-12">
<p className="text-center text-danger">Error loading posts. Please try again later.</p>
</div>
</div>
</>
);
}
return (
<>
<PostFilterButtons active={active} handleClick={handleCategoryClick} />
<motion.div className="row mt-60 masonry-grid" layout>
<AnimatePresence>
{displayData.slice(0, 8).map((item) => {
{displayData.map((item) => {
return (
<motion.div
className="col-12 col-lg-6 grid-item-main"
@@ -114,7 +93,7 @@ const PostFilterItems = () => {
<div className="tp-lp-slider__single topy-tilt">
<div className="thumb mb-24">
<Link
href={`/blog/${item.slug}`}
href={`/insights/${item.slug}`}
className="w-100 overflow-hidden d-block"
>
<div className="parallax-image-wrap">
@@ -137,7 +116,7 @@ const PostFilterItems = () => {
</p>
<span></span>
<p className="date text-xs text-tertiary">
{new Date(item.created_at).toLocaleDateString('en-US', {
{new Date(item.published_at || item.created_at).toLocaleDateString('en-US', {
day: 'numeric',
month: 'short',
year: 'numeric'
@@ -145,7 +124,7 @@ const PostFilterItems = () => {
</p>
</div>
<h5 className="mt-8 fw-5 text-secondary">
<Link href={`/blog/${item.slug}`}>{item.title}</Link>
<Link href={`/insights/${item.slug}`}>{item.title}</Link>
</h5>
</div>
</div>